Сделки с Guice и JDBC - обсуждение решения

В моем приложении мне нужно использовать чистый JDBC вместе с Guice. Однако Guice не предоставляет никакой встроенной поддержки для управления транзакциями. guice-persist предоставляет поддержку только на основе JPA, которую я не могу использовать.

поэтому я попытался реализовать простое решение для управления транзакциями с помощью Guice и JDBC. вот первая версия:

используйте 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();
}

}

реализует менеджер транзакций для 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();
    }
}

}

реализовать обертку для 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);
}

}

затем создайте DataSourceProvider, чтобы мы могли внедрить DataSource в любой POJO, используя guice:

открытый класс 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:

Открытый класс 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 кажется довольно сложной.

Я хотел бы спросить, есть ли у кого-нибудь предложение для этого решения.

Благодарю.

Ответы на вопрос(0)

Ваш ответ на вопрос