ThreadLocal + java.sql.Connection + servlet filter = 2009?

时间:2009-06-11 17:14:15

标签: java servlets jdbc servlet-filters thread-local

我正在编写一些使用普通旧的JDBC模式的servlet。我意识到我有几个想要共享单个事务的对象,我想强制执行一个HTTP事务=一个数据库事务。

我想我可以通过在ThreadLocal变量中传递Connection,然后让servlet过滤器处理所述Connection的创建/提交/回滚来实现这一点。

是否存在一个我不了解的现有框架,或者这是一个合理的晚00的做事方式?

5 个答案:

答案 0 :(得分:4)

Spring事务管理完全按照你的描述进行,乍一看可能有点过分,但你需要的只是(对于最简单的情况):

org.springframework.jdbc.datasource.DataSourceTransactionManager org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy org.springframework.transaction.support.TransactionTemplate

连接现有的DataSource并将其包装在TransctionAwareDataSourceProxy中,然后使用包装的数据源创建一个DataSourceTransactionManager,将它们保存在ServletContext中。然后为每个事务创建一个TransactionTemplate传递事务管理器并调用execute(TransactionCallback)方法来运行您的代码。例如:

new TransactionTemplate(transactionManager).execute(new TransactionCallback(){
    public void doInTransaction(TransactionStatus ts){
        // run your code here...use the dataSource to get a connection and run stuff
        Connection c = dataSourceProxy.getConnection();
        // to rollback ... throw a RuntimeException out of this method or call 
        st.setRollbackOnly();
    }
});

连接将绑定到本地线程,因此只要您始终从相同的数据源(即包装的数据源)获得连接,您将在同一事务中获得相同的连接。

请注意,这是最简单的弹簧交易设置...不是最好的或推荐的弹簧交易设置,因为它可以查看弹簧参考文档或阅读弹簧的实际操作。

...所以我想作为一个直接的答案,这是一个合理的事情,这是Spring框架长期以来一直在做的事情。

答案 1 :(得分:1)

今天的大多数appServer都支持JTA(Java Transaction Api):跨越多个打开/关闭jdbc连接的事务。它为您执行“threadLocal”操作,并且符合J2EE标准。 您可以在过滤器中使用它:

    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    UserTransaction transaction = null;
    try {
        transaction = (UserTransaction)new InitialContext().lookup("java:comp/UserTransaction");
        transaction.begin();
        chain.doFilter(request, response);
        transaction.commit();
    } catch (final Exception errorInServlet) {
        try {
            transaction.rollback();
        } catch (final Exception rollbackFailed) {
            log("No ! Transaction failed !",rollbackFailed);
        }
        throw new ServletException(errorInServlet);
    }
}

在app-server上,声明一个带有jndi名称的数据源,并在代码中使用它来检索连接(不要生成cx.commit(),cx.rollback()或cx.setAutocommit()的东西,它会干扰JTA)。您可以在同一个HTTP事务中多次打开和关闭连接,JTA会处理它:

public void doingDatabaseStuff() throws Exception {
    DataSource datasource = (DataSource)new InitialContext().lookup("/path/to/datasource");
    Connection connection = datasource.getConnection();
    try {
        // doing stuff
    } finally {
        connection.close();
    }
}

答案 2 :(得分:0)

通常最好通过“参数化从上面”传递对象,使用ThreadLocal进行模糊处理。在ServletFilter的情况下,ServletRequest的属性将是一个显而易见的地方。非servlet依赖代码的接口可以将Connection提取到有意义的上下文。

答案 3 :(得分:0)

使用过滤器管理事务是滚动自己的事务管理的好方法。

Java EE规范提供了事务管理,而像Spring这样的替代框架提供了类似的支持(虽然这不是认可; Spring并不一定能做得很好)。

但是,使用ThreadLocal会产生问题。例如,不能保证在整个请求中使用单个线程,任何东西都可以通过全局变量访问Connection,如果依赖于要设置的某个全局状态,测试会变得更加困难。我考虑使用依赖注入容器将Connection显式传递给需要一个的对象。

答案 4 :(得分:0)

如果您不能依赖“真正的”应用服务器并且您希望避免Spring的不那么轻量级,使用过滤器来提供连接,请将其保留在线程上并在请求结束时关闭它确实是一个实际而合理的解决方案。

你需要一些(基本上是静态的)访问器类,它允许get()一个连接和一个setRollbackOnly()。

在请求结束时,从过滤器的角度来看,确保捕获异常(您应该记录并设置为仅回滚)和提交/回滚,相应地关闭事务。

在大多数应用程序和Web容器(和JTA通常做出类似的假设)中,请求将由一个线程处理,并且将一个数据库连接与线程相关联以在请求期间在层之间重用是正确的事情。做。