我正在编写一些带有普通旧的主要是 JDBC 模式的 servlet。我意识到我有几个对象想要共享一个事务,并且我想强制执行一个 HTTP 事务 = 一个数据库事务。
我想我可以通过在 ThreadLocal 变量中传递一个 Connection 来做到这一点,然后让一个 servlet 过滤器处理所述 Connection 的创建/提交/回滚。
是否有一个我不知道的现有框架可以做到这一点,或者这是一种合理的 00 后做事方式?
我正在编写一些带有普通旧的主要是 JDBC 模式的 servlet。我意识到我有几个对象想要共享一个事务,并且我想强制执行一个 HTTP 事务 = 一个数据库事务。
我想我可以通过在 ThreadLocal 变量中传递一个 Connection 来做到这一点,然后让一个 servlet 过滤器处理所述 Connection 的创建/提交/回滚。
是否有一个我不知道的现有框架可以做到这一点,或者这是一种合理的 00 后做事方式?
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 事务设置......不是最好的或推荐的设置,请查看 spring 参考文档或阅读 spring in action。
...所以我想作为一个直接的答案,是的,这是一件合理的事情,这是 Spring 框架长期以来一直在做的事情。
当今大多数 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);
}
}
在应用服务器上,使用 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();
}
}
通常最好使用“从上方参数化”传递对象,使用ThreadLocal
. 在 的情况下ServletFilter
, 的属性ServletRequest
将是一个明显的地方。非 servlet 相关代码的接口可以提取Connection
有意义的上下文。
使用过滤器管理事务是滚动您自己的事务管理的好方法。
Java EE 规范提供了事务管理,而像 Spring 这样的替代框架提供了类似的支持(尽管这不是一种认可;Spring 不一定能很好地做到这一点)。
但是,使用 aThreadLocal
会产生问题。例如,不能保证在整个请求中使用单个线程,任何东西都可以Connection
通过全局变量访问,如果您依赖于要设置的某些全局状态,测试可能会变得更加困难。我会考虑使用依赖注入容器将 a 显式传递Connection
给需要一个的对象。
如果你不能依赖一个“真正的”应用服务器并且你想避免 Spring 的不那么轻量级,那么使用过滤器来提供连接,将它保持在线程上并在请求结束时关闭它确实是一个切实可行的合理解决方案。
您将需要一些(基本上是静态的)访问器类来允许 get() 连接和 setRollbackOnly()。
在请求结束时,从过滤器的角度来看,确保捕获异常(您应该记录并设置为仅回滚)并提交/回滚,相应地关闭事务。
在大多数应用程序和 Web 容器中(JTA 通常做出类似的假设),一个请求将由一个线程处理,并且将一个数据库连接与线程相关联以便在请求期间在层之间重用是正确的做法。