私のアプリケーションでは、純粋な JDBC を Guice と共に使用する必要があります。ただし、Guice はトランザクションを管理するための組み込みサポートを提供していません。guice-persist は、私が使用できない JPA に基づくサポートのみを提供します。
そこで、Guice と JDBC を使用してトランザクションを管理するための簡単なソリューションを実装しようとしました。最初のバージョンは次のとおりです。
TransactionHolder を使用して、スレッドごとにトランザクションを保存します。
public class JdbcTransactionHolder {
private static ThreadLocal<JdbcTransaction> currentTransaction = new ThreadLocal<JdbcTransaction>(); public static void setCurrentTransaction(JdbcTransaction transaction) { currentTransaction.set(transaction); } public static JdbcTransaction getCurrentTransaction() { return currentTransaction.get(); } public static void removeCurrentTransaction() { currentTransaction.remove(); }
}
JDBC のトランザクション マネージャーを実装します。現在のところ、begin()、getTransaction()、commit()、および rollback() メソッドのみです。
public class JdbcTransactionManager は TransactionManager を実装します {
@Inject private DataSource dataSource; @Override public void begin() throws NotSupportedException, SystemException { logger.debug("Start the transaction"); try { JdbcTransaction tran = JdbcTransactionHolder.getCurrentTransaction(); Connection conn = null; if(tran == null) { conn = dataSource.getConnection(); } else { conn = tran.getConnection(); } // We have to put the connection in the holder so that we can get later // from the holder and use it in the same thread logger.debug("Save the transaction for thread: {}.", Thread.currentThread()); JdbcTransactionHolder.setCurrentTransaction(new JdbcTransaction(conn)); } catch (Exception e) { throw new RuntimeException(e); } } @Override public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { logger.debug("Commit the transaction"); try { logger.debug("Get the connection for thread: {}.", Thread.currentThread()); Transaction transaction = JdbcTransactionHolder.getCurrentTransaction(); transaction.commit(); } catch(Exception e) { throw new RuntimeException(e); } finally { JdbcTransactionHolder.removeCurrentTransaction(); } } @Override public Transaction getTransaction() throws SystemException { logger.debug("Get transaction."); final JdbcTransaction tran = JdbcTransactionHolder.getCurrentTransaction(); if(tran == null) { throw new DBException("No transaction is availble. TransactionManager.begin() is probably not yet called."); } return tran; } @Override public void rollback() throws IllegalStateException, SecurityException, SystemException { logger.debug("Rollback the transaction"); try { logger.debug("Get the transaction for thread: {}.", Thread.currentThread()); Transaction conn = JdbcTransactionHolder.getCurrentTransaction(); conn.commit(); } catch(Exception e) { throw new RuntimeException(e); } finally { JdbcTransactionHolder.removeCurrentTransaction(); } }
}
トランザクションが開始されている場合、トランザクション ホルダーから現在の接続を取得できる DataSource のラッパーを実装します。
public class JdbcDataSource は DataSource を実装します {
private final static org.slf4j.Logger logger = LoggerFactory.getLogger(JdbcDataSource.class); private DataSource dataSource; public JdbcDataSource(DataSource dataSource) { this.dataSource = dataSource; } @Override public PrintWriter getLogWriter() throws SQLException { return dataSource.getLogWriter(); } @Override public int getLoginTimeout() throws SQLException { return dataSource.getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return dataSource.getParentLogger(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { this.dataSource.setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { this.dataSource.setLoginTimeout(seconds); } @Override public boolean isWrapperFor(Class<?> arg0) throws SQLException { return this.isWrapperFor(arg0); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return this.unwrap(iface); } @Override public Connection getConnection() throws SQLException { JdbcTransaction transaction = JdbcTransactionHolder.getCurrentTransaction(); if(transaction != null) { // we get the connection from the transaction logger.debug("Transaction exists for the thread: {}.", Thread.currentThread()); return transaction.getConnection(); } Connection conn = this.dataSource.getConnection(); conn.setAutoCommit(false); return conn; } @Override public Connection getConnection(String username, String password) throws SQLException { JdbcTransaction transaction = JdbcTransactionHolder.getCurrentTransaction(); if(transaction != null) { // we get the connection from the transaction logger.debug("Transaction exists for the thread: {}.", Thread.currentThread()); return transaction.getConnection(); } return this.dataSource.getConnection(username, password); }
}
次に、DataSourceProvider を作成して、Guice を使用して DataSource を任意の POJO に注入できるようにします。
public class DataSourceProvider は Provider を実装します {
private static final Logger logger = LoggerFactory.getLogger(DataSourceProvider.class); private DataSource dataSource; public DataSourceProvider() { JdbcConfig config = getConfig(); ComboPooledDataSource pooledDataSource = new ComboPooledDataSource(); try { pooledDataSource.setDriverClass(config.getDriver()); } catch (Exception e) { throw new RuntimeException(e); } pooledDataSource.setJdbcUrl(config.getUrl()); pooledDataSource.setUser(config.getUsername()); pooledDataSource.setPassword(config.getPassword() ); pooledDataSource.setMinPoolSize(config.getMinPoolSize()); pooledDataSource.setAcquireIncrement(5); pooledDataSource.setMaxPoolSize(config.getMaxPoolSize()); pooledDataSource.setMaxStatements(config.getMaxStatementSize()); pooledDataSource.setAutoCommitOnClose(false); this.dataSource = new JdbcDataSource(pooledDataSource); } private JdbcConfig getConfig() { JdbcConfig config = new JdbcConfig(); Properties prop = new Properties(); try { //load a properties file from class path, inside static method prop.load(JdbcConfig.class.getResourceAsStream("/database.properties")); //get the property value and print it out config.setDriver(prop.getProperty("driver")); config.setUrl(prop.getProperty("url")); config.setUsername(prop.getProperty("username")); config.setPassword(prop.getProperty("password")); String maxPoolSize = prop.getProperty("maxPoolSize"); if(maxPoolSize != null) { config.setMaxPoolSize(Integer.parseInt(maxPoolSize)); } String maxStatementSize = prop.getProperty("maxStatementSize"); if(maxStatementSize != null) { config.setMaxStatementSize(Integer.parseInt(maxStatementSize)); } String minPoolSize = prop.getProperty("minPoolSize"); if(minPoolSize != null) { config.setMinPoolSize(Integer.parseInt(minPoolSize)); } } catch (Exception ex) { logger.error("Failed to load the config file!", ex); throw new DBException("Cannot read the config file: database.properties. Please make sure the file is present in classpath.", ex); } return config; } @Override public DataSource get() { return dataSource; }
次に、TransactionalMethodInterceptor を実装して、Transactional アノテーションを使用してメソッドのトランザクションを管理します。
public class TransactionalMethodInterceptor は MethodInterceptor を実装します {
private final static Logger logger = LoggerFactory.getLogger(TransactionalMethodInterceptor.class); @Inject private JdbcTransactionManager transactionManager; @Override public Object invoke(MethodInvocation method) throws Throwable { try { // Start the transaction transactionManager.begin(); logger.debug("Start to invoke the method: " + method); Object result = method.proceed(); logger.debug("Finish invoking the method: " + method); transactionManager.commit(); return result; } catch (Exception e) { logger.error("Failed to commit transaction!", e); try { transactionManager.rollback(); } catch(Exception ex) { logger.warn("Cannot roll back transaction!", ex); } throw e; } }
}
最後に、Guice がインスタンスを注入できるようにすべてをまとめるコード:
bind(DataSource.class).toProvider(DataSourceProvider.class).in(Scopes.SINGLETON); bind(TransactionManager.class).to(JdbcTransactionManager.class); TransactionalMethodInterceptor transactionalMethodInterceptor = new TransactionalMethodInterceptor(); requestInjection(transactionalMethodInterceptor); bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), transactionalMethodInterceptor); bind(TestDao.class).to(JdbcTestDao.class); bind(TestService.class).to(TestServiceImpl.class);
データソース プールには c3p0 を使用します。したがって、私のテストでは問題なく動作します。
関連する別の質問を見つけました:Guice、JDBC、およびデータベース接続の管理
しかし、これまでのところ、SpringFramework の何かを除いて、同様のアプローチは見つかりませんでした。しかし、Spring での実装でさえかなり複雑に思えます。
この解決策について何か提案があるかどうかを尋ねたいと思います。
ありがとう。