事务的属性,ACID
|
JDBC基于连接进行事务管理,默认开启自动提交。缺点在于不能跨数据库;且在使用连接池时,难以保证线程安全,可能出现死锁等问题;如果不使用连接池,又容易出现效率问题。
try { conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demoDb", username, userpassword); if(conn.getAutoCommit()) conn.setAutoCommit(false); // 禁止自动提交,设置回滚点 stmt = conn.createStatement(); stmt.executeUpdate("alter table …"); //数据库更新操作1 stmt.executeUpdate("insert into table …"); //数据库更新操作2 conn.commit(); //事务提交 }catch(Exception ex) { ex.printStackTrace(); try { conn.rollback(); //操作不成功则回滚 } catch(Exception ex) { ex.printStackTrace(); } }
[参考]
- Java Transaction API,Java事务API,支持分布式事务服务。即在两个或多个网络计算机资源上访问并且更新数据,这些数据可以分布在多个数据库上。JDBC驱动程序的JTA支持极大地增强了数据访问能力。
- 如果使用 JTA 界定事务,就需要有一个实现 javax.sql.XADataSource 、javax.sql.XAConnection 和 javax.sql.XAResource 接口的 JDBC 驱动程序。一个实现了这些接口的驱动程序将可以参与 JTA 事务。一个 XADataSource 对象就是一个 XAConnection 对象的工厂。XAConnections 是参与 JTA 事务的 JDBC 连接。需要用应用服务器的管理工具设置 XADataSource 。从应用服务器和 JDBC 驱动程序的文档中可以了解到相关的指导。J2EE 应用程序用 JNDI 查询数据源。一旦应用程序找到了数据源对象,它就调用 javax.sql.DataSource.getConnection() 以获得到数据库的连接。
- XA 连接与非 XA 连接不同。XA 连接参与了 JTA 事务。这意味着 XA 连接不支持 JDBC 的自动提交功能。应用程序应该使用 UserTransaction.begin()、 UserTransaction.commit() 和 serTransaction.rollback() ,而不能对 XA 连接调用 java.sql.Connection.commit() 或者 java.sql.Connection.rollback() 。
- JTA事务管理缺点在于实现复杂,侵入性大
- 容器事务主要是Java EE应用服务器提供的,大多基于JTA,需要JNDI的支持,实现复杂。
- 相对编码实现JTA 事务管理,可以通过EJB容器提供的容器事务管理机制(CMT)完成同一个功能,这项功能由Java EE应用服务器提供,可以简单的指定将哪个方法加入事务,一旦指定,容器将负责事务管理任务。通过这种方式可以将事务代码排除在逻辑编码之外,同时将所有困难交给 Java EE容器去解决。
- 使用EJB CMT的另外一个好处就是程序员无需关心JTA API的编码,不过,理论上必须使用EJB。
- 为不同的事务API提供统一的编程模型,事务API包括JTA、JDBC、Hibernate、JPA、JDO
- 支持声明式事务管理
- 提供比JTA更简单的编程式事务管理API
- 天然集成Spring数据访问抽象
- Spring并不直接管理事务,而是把事务管理的职责委托给JTA或其他持久化机制提供的平台相关的事务实现。
- PlatformTransactionManager是Spring事务的基础,可通过其实现管理事务,其实现例如:DataSourceTransactionManager、HibernateTransactionManager、JpaTransactionManager、JtaTransactionManager、JmsTransactionManager
public interface PlatformTransactionManager { TransactionStatus /* 线程状态设置和读取 */ getTransaction(TransactionDefinition definition /* 定义txn的隔离、传播、超时、只读参数 */) throws TransactionException; // 非检查型的Exception void commit(TransactionStatus status) throws TransactionException; // 提交 void rollback(TransactionStatus status) throws TransactionException; // 回滚 } public interface TransactionStatus extends SavepointManager { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); void flush(); boolean isCompleted(); }
<!-- 配置各种TxnManager --> <!-- JDBC数据源 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- Hibernate数据源 --> <bean id="txManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <!-- JTA数据源 --> <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
// 使用TxnManager DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setName("SomeTxName"); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus status = txManager.getTransaction(def); try { // 业务逻辑代码 ... } catch (MyException ex) { txManager.rollback(status); throw ex; } txManager.commit(status);
- 使用DataSourceUtils (JDBC), EntityManagerFactoryUtils (JPA), SessionFactoryUtils (Hibernate), PersistenceManagerFactoryUtils (JDO)
// 当前线程中只会取得一个连接 // 而dataSource.getConnection()不同,每次可能取得不同的连接,视数据源例如连接池而定 Connection conn = DataSourceUtils.getConnection(dataSource); DataSourceUtils.releaseConnection(conn, dataSource);
- 使用TransactionAwareDataSourceProxy,它是DataSource的代理,类似于JNDI dataSource。是非常底层的实现,不建议使用
- 使用基于Spring数据访问模板例如JdbcTemplate的持久化集成API
- 使用事务感知工厂Bean或代理来调用ORM自身的API
- 使用TransactionTemplate,更为推荐
public class UserService implements Service { private TransactionTemplate txnTempl; public UserService(PlatformTransactionManager txnMgr) { this.txnTempl = new TransactionTemplate(txnMgr); } // 有返回值的操作 public Object someAction1() { return txnTempl.execute(new TransactionCallback() { public Object doInTransaction(TransactionStatus status) { updateOperation1(); return resultOfUpdateOperation2(); } }; } // 无返回值的操作 public Object someAction2() { return txnTempl.execute(new TransactionCallbackWithoutResult() { public Object doInTransaction(TransactionStatus status) { updateOperation1(); updateOperation2(); } }; } }
- 使用PlatformTransactionManager,例子见1.5.2
- Spring声明式事务管理类似于EJB CMT,但既可以支持JTA事务,也可以支持JDBC、JPA、Hibernate等;可以应用于非EJB的类;提供声明式的回滚设置;还可以为指定异常类型配置回滚机制;不支持跨远程调用(用户线程)的事务管理
- Spring 2.0之后无需使用TransactionProxyFactoryBean
- 通过AOP Proxy实现
<!-- XML设置方法 --> <!-- 业务方法 --> <bean id="stockService" class="x.y.z.service.DefaultStockService"/> <!-- 数据源 --> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 事务管理器 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 事务通知,这是xml设置方式,还支持tx注解,启用方法<tx:annotation-driven/> --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- read-only,事务只读;rollback-for,默认为非检查型例如RuntimeException回滚,检查型不回滚 --> <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/> <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/> <!-- propagation,传播性,默认为Required;timeout默认为-1即永不超时 --> <tx:method name="*" propagation="REQUIRED" timeout="30" /> </tx:attributes> </tx:advice> <!-- 事务切面 --> <aop:config> <aop:pointcut id="stockServiceOperation" expression="execution(* x.y.service.StockService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="stockServiceOperation"/> </aop:config>
// 启用注解 @EnableTransactionManagement @Configuration public AppConfig { // ... } // 注解设置方法 // 在public方法上设置,AspectJ支持非public方法 @Transactional(propagation = Propagation.REQUIRED, timeout=30, isolation=Isolation.DEFAULT) public void updateStock() { // ... } // 推荐为具体类而非接口设置注解 @Transactional(readOnly = true) public class QueryStockService implements Service { }
各方法运行在各自的事务管理器中
public class TransactionalService { @Transactional("order") public void setSomething(String name) { ... } @Transactional("account") public void doSomething() { ... } }
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> <qualifier value="order"/> </bean> <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> <qualifier value="account"/> </bean>
@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional("order") public @interface OrderTx { } @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Transactional("account") public @interface AccountTx { } @OrderTx public void setSomething(String name) { ... } @AccountTx public void doSomething() { ... }
类型 | 说明 |
PROPAGATION_REQUIRED | 如果存在一个事务,则在当前事务中运行。无则开启;Spring默认 |
PROPAGATION_SUPPORTS | 如果存在一个事务,则在当前事务中运行。无则无事务运行 |
PROPAGATION_MANDATORY | 如果存在一个事务,则在当前事务中运行。无则抛出异常 |
PROPAGATION_REQUIRES_NEW | 如果存在一个事务,则挂起,并开启一个新事务运行 |
PROPAGATION_NOT_SUPPORTED | 如果存在一个事务,则挂起,并无事务运行 |
PROPAGATION_NEVER | 如果存在一个事务,则抛出异常 |
PROPAGATION_NESTED | 如果存在一个事务,则开启一个嵌套的事务运行,嵌套事务不影响外部的事务;无则开启事务运行 |
- Spring框架提供程序级别的隔离特性,有别于数据库自身提供的隔离特性
Dirty Reads 脏读,读到了其他事务未提交的更新数据 |
Non-Repeatable Reads 不可重复读,在一次事务中,前后读取的数据不一致,通常是后面一次读取到了其他事务的更新数据 |
Phantom Reads 幻读,在一次事务中,前后读取的数据不一致,通常是后面一次读取到了其他事务的新增数据 |
|
Serializable | N | N | N |
Repeatable Read | N | N | Y |
Read Committed (Spring默认) | N | Y | Y |
Read Uncommitted | Y | Y | Y |
- 大多数数据库的默认隔离级别为Read Commited,如Sql Server、Oracle
- 少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎