与Guice和JDBC的交易 - 解决方案讨论

时间:2015-02-04 09:10:54

标签: jdbc transactions guice transactionmanager

在我的应用程序中,我需要将纯JDBC与Guice一起使用。但是,Guice没有提供任何内置的支持来管理交易。 guice-persist只提供基于JPA的支持,我不能使用它。

所以我尝试实现一个简单的解决方案来管理Guice和JDBC的事务。这是第一个版本:

  1. 使用TransactionHolder存储每个线程的事务。

    公共类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();
    }
    

    }

  2. 实现JDBC的事务管理器,现在只有begin(),getTransaction(),commit()和rollback()方法:

    公共类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();
        }
    }
    

    }

  3. 实现DataSource的包装器,如果已启动事务,它可以从事务持有者获取当前连接:

    公共类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);
    }
    

    }

  4. 然后创建一个DataSourceProvider,以便我们可以使用guice将DataSource注入任何POJO:

    公共类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;
    }
    
  5. 然后实现TransactionalMethodInterceptor以使用Transactional注释管理方法的事务:

    公共类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;
        }
    }
    

    }

  6. 最后,将所有内容放在一起以便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);
    
  7. 我使用c3p0作为数据源池。所以,它在我的测试中运行得很好。

    我找到了另一个相关问题:Guice, JDBC and managing database connections

    但到目前为止,除了SpringFramework中的内容之外,我还没有找到任何类似的方法。但即使是Spring中的实现也显得非常复杂。

    我想问一下是否有人对此解决方案有任何建议。

    感谢。

0 个答案:

没有答案