不使用Spring @Transactional,如何在JAX-RS中管理事务?
当然可以!不用Spring也能给你的Jersey(JAX-RS)RESTful服务添加事务支持,下面是几个实用的方案,完全不依赖Spring生态:
JTA是Java EE的标准事务API,几乎所有Java EE应用服务器(比如WildFly、GlassFish、TomEE)都自带支持。如果用的是轻量容器(如Tomcat),也可以通过第三方JTA实现(比如Atomikos、Bitronix)来启用。
核心思路是注入UserTransaction,在资源方法中手动控制事务的开启、提交和回滚:
import javax.transaction.UserTransaction; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.inject.Inject; import javax.ws.rs.WebApplicationException; @Path("/orders") public class OrderResource { @Inject private UserTransaction utx; @Inject private OrderRepository orderRepository; @POST public void createOrder(Order order) { try { // 开启事务 utx.begin(); // 执行数据库操作 orderRepository.save(order); // 可以添加其他关联操作,比如更新库存 // 提交事务 utx.commit(); } catch (Exception e) { // 异常时回滚事务 try { if (utx.getStatus() == javax.transaction.Status.STATUS_ACTIVE) { utx.rollback(); } } catch (Exception rollbackEx) { rollbackEx.printStackTrace(); } throw new WebApplicationException("事务执行失败", e); } } }
如果你的应用运行在Java EE兼容的容器中,还可以使用容器管理事务(Container-Managed Transactions),不用手动写事务控制代码,容器会自动帮你处理事务的生命周期。
这里要注意,我们用的是Java EE标准的javax.transaction.Transactional注解,不是Spring的org.springframework.transaction.annotation.Transactional,完全不依赖Spring:
import javax.transaction.Transactional; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.inject.Inject; @Path("/orders") public class OrderResource { @Inject private OrderRepository orderRepository; @POST // 指定事务传播行为,这里用REQUIRED表示如果没有事务就新建一个 @Transactional(Transactional.TxType.REQUIRED) public void createOrder(Order order) { orderRepository.save(order); // 其他数据库操作,容器会自动在方法结束时提交事务,异常时回滚 } }
如果你的服务直接使用JDBC连接,也可以完全手动控制事务,不需要任何框架支持。这种方式最底层,适合简单场景:
import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.sql.DataSource; import javax.inject.Inject; import javax.ws.rs.WebApplicationException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; @Path("/orders") public class OrderResource { @Inject private DataSource dataSource; @POST public void createOrder(Order order) { Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 关闭自动提交,开启事务 // 执行插入操作 String insertSql = "INSERT INTO orders (id, name, amount) VALUES (?, ?, ?)"; try (PreparedStatement stmt = conn.prepareStatement(insertSql)) { stmt.setInt(1, order.getId()); stmt.setString(2, order.getName()); stmt.setBigDecimal(3, order.getAmount()); stmt.executeUpdate(); } // 提交事务 conn.commit(); } catch (SQLException e) { // 异常回滚 if (conn != null) { try { conn.rollback(); } catch (SQLException rollbackEx) { rollbackEx.printStackTrace(); } } throw new WebApplicationException("创建订单失败", e); } finally { // 关闭连接 if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
这种方式代码有点繁琐,你可以自己封装一个事务模板类来复用事务控制逻辑,减少重复代码。
如果你想实现类似Spring @Transactional的声明式事务,但又不想用Spring,可以用原生AspectJ来织入事务切面。
首先自定义一个事务注解:
import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTransactional { }
然后编写AspectJ切面,拦截标注了@MyTransactional的方法,自动处理事务:
import javax.transaction.UserTransaction; import javax.inject.Inject; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class TransactionAspect { @Inject private UserTransaction utx; @Around("@annotation(MyTransactional)") public Object handleTransaction(ProceedingJoinPoint joinPoint) throws Throwable { try { utx.begin(); Object result = joinPoint.proceed(); // 执行目标方法 utx.commit(); return result; } catch (Exception e) { if (utx.getStatus() == javax.transaction.Status.STATUS_ACTIVE) { utx.rollback(); } throw e; } } }
最后在你的资源方法上标注@MyTransactional即可:
@POST @MyTransactional public void createOrder(Order order) { orderRepository.save(order); // 其他操作 }
总结
- 如果你用Java EE容器,优先选方案2(CMT),最省心;
- 轻量容器下可以用方案1(JTA+第三方实现);
- 简单场景或不想依赖JTA的话,用方案3(手动JDBC事务);
- 想要声明式事务又不想用Spring,选方案4(AspectJ)。
内容的提问来源于stack exchange,提问作者ankitmittal

