Mirageの例外をSpringのDataAccessExceptionにする
2WaySQLが使えるシンプルなデータベースアクセスライブラリーのMirageとSpring Frameworkの組み合わせで、Mirageが吐く例外をSpringのDataAccessExceptionに変換する方法です。
Mirageプロジェクトは以下のページを参照して下さい。
http://amateras.sourceforge.jp/site/mirage/welcome.html
Spring Frameworkでは@Repositoryアノテーションを付けた場合に、その中で発生した例外をDataAccessExceptionに変換する働きを持っています。
今回は、Mirageの中で例外が発生した場合に、自動でDataAccessException系の例外に変更する方法を紹介します。
普通に書いてみる
最初に、普通にキーの重複エラーが出るようなプログラムを書いてみます。
今回はPostgreSQLで確認しています。
テストで利用するテーブルのDDLです。
CREATE TABLE emp( emp_no INTEGER, name VARCHAR(40), age INTEGER, PRIMARY KEY(emp_no) );
Spring側のソースはSpringの設定ファイルからです。適当にbeans.xmlという名前にしています。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"> <context:annotation-config /> <context:component-scan base-package="*" /> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean> <tx:advice id="transactionAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="select*" read-only="true" /> <tx:method name="*" propagation="REQUIRED" isolation="READ_COMMITTED" timeout="10" read-only="false" /> </tx:attributes> </tx:advice> <aop:config> <aop:advisor advice-ref="transactionAdvice" pointcut="execution(* com.example.spring.*Service.*(..))" /> </aop:config> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.postgresql.Driver" /> <property name="url" value="jdbc:postgresql://127.0.0.1:5432/miragedb" /> <property name="username" value="mirage" /> <property name="password" value="mirage" /> </bean> <bean id="connectionProvider" class="jp.sf.amateras.mirage.integration.spring.SpringConnectionProvider"> <property name="transactionManager" ref="transactionManager" /> </bean> <bean id="dialect" class="jp.sf.amateras.mirage.dialect.HyperSQLDialect" /> <bean id="sqlManager" class="jp.sf.amateras.mirage.SqlManagerImpl"> <property name="connectionProvider" ref="connectionProvider" /> <property name="dialect" ref="dialect" /> </bean> </beans>
EntityのEmpクラスです。
package com.example.spring; import jp.sf.amateras.mirage.annotation.PrimaryKey; import jp.sf.amateras.mirage.annotation.PrimaryKey.GenerationType; public class Emp { @PrimaryKey(generationType = GenerationType.APPLICATION) private int empNo; private String name; private int age; public int getEmpNo() { return empNo; } public void setEmpNo(int empNo) { this.empNo = empNo; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "Test [emp_no=" + empNo + ", name=" + name + ", age=" + age + "]"; } }
キーの重複を起こすServiceクラスです。
package com.example.spring; import jp.sf.amateras.mirage.SqlManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; @Repository public class TransactionSpringService { @Autowired private SqlManager sqlManager; @Transactional public void insert(Emp emp) { // キーの重複をわざと起こす sqlManager.insertEntity(emp); sqlManager.insertEntity(emp); } }
最後に、Serviceを呼び出すmainメソッドです。
package com.example.spring; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class TransactionSpringConnect { public static void main(String... args) { ApplicationContext context = new ClassPathXmlApplicationContext( "/spring/beans.xml"); TransactionSpringService tss = (TransactionSpringService) context .getBean("transactionSpringService"); Emp emp = new Emp(); emp.setEmpNo(11); emp.setName("あいうえお"); emp.setAge(30); tss.insert(emp); } }
この状態で実行すると以下の様なエラーが出力されます。
Exception in thread "main" jp.sf.amateras.mirage.exception.SQLRuntimeException: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "emp_pkey" at jp.sf.amateras.mirage.SqlExecutor.executeUpdateSql(SqlExecutor.java:288) at jp.sf.amateras.mirage.SqlManagerImpl.insertEntity(SqlManagerImpl.java:316) at com.example.spring.TransactionSpringService.insert(TransactionSpringService.java:18) at com.example.spring.TransactionSpringService$$FastClassByCGLIB$$e353229a.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:710) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:643) at com.example.spring.TransactionSpringService$$EnhancerByCGLIB$$becf7388.insert(<generated>) at com.example.spring.TransactionSpringConnect2.main(TransactionSpringConnect2.java:17) Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "emp_pkey" at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2077) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1810) at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257) at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:498) at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:386) at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:332) at jp.sf.amateras.mirage.SqlExecutor.executeUpdateSql(SqlExecutor.java:277) ... 15 more
MirageのSQLRuntimeExceptionが出力されているのがわかるかと思います。もちろんこのままでも、この例外となったSQLExceptionを取得でき、エラーコードなりから重複エラーということはわかります。
DataAccessExceptionをつかう
Springで標準的にサポートされていないDBアクセスライブラリーを用いてDataAccessExceptionを出力するためには、SpringのSQLErrorCodeSQLExceptionTranslatorを使用します。
<bean class="org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator"> <constructor-arg ref="dataSource" /> </bean> <bean class="com.example.spring.MiragePersistenceExceptionTranslator" /> <bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
設定の中で使用しているMiragePersistenceExceptionTranslatorは以下になります。
package com.example.spring; import java.sql.SQLException; import jp.sf.amateras.mirage.exception.SQLRuntimeException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.dao.support.PersistenceExceptionTranslator; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; public class MiragePersistenceExceptionTranslator implements PersistenceExceptionTranslator { @Autowired private SQLErrorCodeSQLExceptionTranslator translator; @Override public DataAccessException translateExceptionIfPossible(RuntimeException ex) { if (ex instanceof SQLRuntimeException) { Throwable t; while ((t = ex.getCause()) != null) { if (t instanceof SQLException) { return translator.translate("", "", (SQLException) t); } } } return null; } }
コンポーネントとして、PersistenceExceptionTranslatorを実装したクラスを作成することで、内部的にDataAccessExceptionに変更して例外を送出できます。
実際に動作させると以下のようになります。
Exception in thread "main" org.springframework.dao.DuplicateKeyException: ; SQL []; ERROR: duplicate key value violates unique constraint "emp_pkey"; nested exception is org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "emp_pkey" at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:239) at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73) at com.example.spring.MiragePersistenceExceptionTranslator.translateExceptionIfPossible(MiragePersistenceExceptionTranslator.java:23) at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59) at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:643) at com.example.spring.TransactionSpringService$$EnhancerByCGLIB$$51c3aa23.insert(<generated>) at com.example.spring.TransactionSpringConnect2.main(TransactionSpringConnect2.java:17) Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "emp_pkey" at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2077) at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:1810) at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:257) at org.postgresql.jdbc2.AbstractJdbc2Statement.execute(AbstractJdbc2Statement.java:498) at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags(AbstractJdbc2Statement.java:386) at org.postgresql.jdbc2.AbstractJdbc2Statement.executeUpdate(AbstractJdbc2Statement.java:332) at jp.sf.amateras.mirage.SqlExecutor.executeUpdateSql(SqlExecutor.java:277) at jp.sf.amateras.mirage.SqlManagerImpl.insertEntity(SqlManagerImpl.java:316) at com.example.spring.TransactionSpringService.insert(TransactionSpringService.java:18) at com.example.spring.TransactionSpringService$$FastClassByCGLIB$$e353229a.invoke(<generated>) at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204) at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:710) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:136) ... 10 more
解説
PersistenceExceptionTranslationPostProcessorでは、PersistenceExceptionTranslationAdvisorを設定しています。
@Override public void setBeanFactory(BeanFactory beanFactory) { if (!(beanFactory instanceof ListableBeanFactory)) { throw new IllegalArgumentException( "Cannot use PersistenceExceptionTranslator autodetection without ListableBeanFactory"); } this.advisor = new PersistenceExceptionTranslationAdvisor( (ListableBeanFactory) beanFactory, this.repositoryAnnotationType); }
PersistenceExceptionTranslationAdvisorでは、adviceとして、PersistenceExceptionTranslationInterceptorを設定しています。
public PersistenceExceptionTranslationAdvisor( PersistenceExceptionTranslator persistenceExceptionTranslator, Class<? extends Annotation> repositoryAnnotationType) { this.advice = new PersistenceExceptionTranslationInterceptor(persistenceExceptionTranslator); this.pointcut = new AnnotationMatchingPointcut(repositoryAnnotationType, true); }
PersistenceExceptionTranslationInterceptorでは、@Repositoryの付いたコンポーネントのメソッドを呼び出し、例外をcatchして処理しています。
@Override public Object invoke(MethodInvocation mi) throws Throwable { try { return mi.proceed(); } catch (RuntimeException ex) { // Let it throw raw if the type of the exception is on the throws clause of the method. if (!this.alwaysTranslate && ReflectionUtils.declaresException(mi.getMethod(), ex.getClass())) { throw ex; } else { if (this.persistenceExceptionTranslator == null) { this.persistenceExceptionTranslator = detectPersistenceExceptionTranslators(this.beanFactory); } throw DataAccessUtils.translateIfNecessary(ex, this.persistenceExceptionTranslator); } } }
例外自体は、以下の処理で適切な例外に変換しています。
protected PersistenceExceptionTranslator detectPersistenceExceptionTranslators(ListableBeanFactory beanFactory) { // Find all translators, being careful not to activate FactoryBeans. Map<String, PersistenceExceptionTranslator> pets = BeanFactoryUtils.beansOfTypeIncludingAncestors( beanFactory, PersistenceExceptionTranslator.class, false, false); ChainedPersistenceExceptionTranslator cpet = new ChainedPersistenceExceptionTranslator(); for (PersistenceExceptionTranslator pet : pets.values()) { cpet.addDelegate(pet); } return cpet; }
ChainedPersistenceExceptionTranslatorで使用できるTranslatorで例外をチェックしていき、DataAccessExceptionのインスタンスになるまで変換します。変換できない場合にはnullを返します。
@Override public DataAccessException translateExceptionIfPossible(RuntimeException ex) { for (PersistenceExceptionTranslator pet : this.delegates) { DataAccessException translatedDex = pet.translateExceptionIfPossible(ex); if (translatedDex != null) { return translatedDex; } } return null; }
ここで使用されるTrnslatorはPersistenceExceptionTranslatorを実装したクラスを作成し、それをBeanとして設定しておくことで、自動的に使用されます。
<bean class="com.example.spring.MiragePersistenceExceptionTranslator" />
Spring3入門 ――Javaフレームワーク・より良い設計とアーキテクチャ
- 作者: 長谷川裕一,大野渉,土岐孝平
- 出版社/メーカー: 技術評論社
- 発売日: 2012/11/02
- メディア: 大型本
- 購入: 8人 クリック: 115回
- この商品を含むブログ (13件) を見る
- 作者: 竹添直樹,島本多可子,小津美夕紀,亀井隆司
- 出版社/メーカー: 翔泳社
- 発売日: 2011/07/16
- メディア: 大型本
- 購入: 6人 クリック: 217回
- この商品を含むブログ (24件) を見る