(三)Spring框架之事务管理
一、编程式事务管理
- Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。
事务属性包含了5个方面,如图所示:
下面详细介绍一下各个事务属性:
1.1 传播行为
事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务 |
(1)PROPAGATION_REQUIRED 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
案例:
//事务属性 PROPAGATION_REQUIRED methodA{ …… methodB(); …… } //事务属性 PROPAGATION_REQUIRED methodB{ …… }
- Spring保证在methodB方法中所有的调用都获得到一个相同的连接。在调用methodB时,没有一个存在的事务,所以获得一个新的连接,开启了一个新的事务。 单独调用MethodA时,在MethodA内又会调用MethodB.
1.2 隔离级别
事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。
(1)并发事务引起的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。
- 脏读(Dirty reads)——脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。
- 不可重复读(Nonrepeatable read)——不可重复读发生在一个事务执行相同的查询两次或两次以上,但是每次都得到不同的数据时。这通常是因为另一个并发事务在两次查询期间进行了更新。
- 幻读(Phantom read)——幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。
不可重复读与幻读的区别
不可重复读的重点是修改:
同样的条件, 你读取过的数据, 再次读取出来发现值不一样了
例如:在事务1中,Mary 读取了自己的工资为1000,操作并没有完成
con1 = getConnection();
select salary from employee empId ="Mary";
在事务2中,这时财务人员修改了Mary的工资为2000,并提交了事务.
con2 = getConnection(); update employee set salary = 2000; con2.commit();
在事务1中,Mary 再次读取自己的工资时,工资变为了2000
//con1 select salary from employee empId ="Mary";
在一个事务中前后两次读取的结果并不一致,导致了不可重复读。
幻读的重点在于新增或者删除:
同样的条件, 第1次和第2次读出来的记录数不一样
例如:目前工资为1000的员工有10人。事务1,读取所有工资为1000的员工。
con1 = getConnection();
Select * from employee where salary =1000;
共读取10条记录
这时另一个事务向employee表插入了一条员工记录,工资也为1000
con2 = getConnection(); Insert into employee(empId,salary) values("Lili",1000); con2.commit();
事务1再次读取所有工资为1000的员工
//con1 select * from employee where salary =1000;
共读取到了11条记录,这就产生了幻像读。
从总的结果来看, 似乎不可重复读和幻读都表现为两次读取的结果不一致。但如果你从控制的角度来看, 两者的区别就比较大。
对于前者, 只需要锁住满足条件的记录。
对于后者, 要锁住满足条件及其相近的记录。
(2)隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITTED | 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的 |
1.3 只读
事务的第三个特性是它是否为只读事务。如果事务只对后端的数据库进行该操作,数据库可以利用事务的只读特性来进行一些特定的优化。通过将事务设置为只读,你就可以给数据库一个机会,让它应用它认为合适的优化措施。
1.4 事务超时
为了使应用程序很好地运行,事务不能运行太长的时间。因为事务可能涉及对后端数据库的锁定,所以长时间的事务会不必要的占用数据库资源。事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。
1.5 回滚规则
事务五边形的最后一个方面是一组规则,这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。
1.6 事务状态
上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:
public interface TransactionStatus{ boolean isNewTransaction(); // 是否是新的事物 boolean hasSavepoint(); // 是否有恢复点 void setRollbackOnly(); // 设置为只回滚 boolean isRollbackOnly(); // 是否为只回滚 boolean isCompleted; // 是否已完成 }
可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。
二、案例
- 案例一 :(不用Spring事务管理)一个方法内的的事务管理
package transaction_1; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import util.DBUtil; /** * 一个方法内的的事务管理 * @author 半颗柠檬、 * */ public class UserDao_1 { public void insert_1(){ Connection conn=null; Statement stat=null; StringBuffer SQL=new StringBuffer(); try { /** * 插入到user表 */ conn=DBUtil.getConn(); SQL.append("insert into user values('111',11,'11')"); conn.setAutoCommit(false); stat=conn.createStatement(); stat.executeUpdate(SQL.toString()); /** * 插入到角色表 */ SQL.setLength(0); SQL.append("insert into role1 values(3,'普通用户','普通用户备注')"); stat.executeUpdate(SQL.toString()); //全部正确则提交 conn.commit(); } catch (Exception e) { //出现异常则回滚 e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally{ //释放资源 } } public static void main(String[] args) { UserDao_1 userDao_1=new UserDao_1(); userDao_1.insert_1(); } }
- 案例二:(不用spring事务管理)一个类中两个方法事务管理
解决方法 :方法 1. 将Conenction作为参数传到两个方法中
方法 2. 将Connection放到ThreadLocal中管理
UserDao_2.java
package transaction_1; import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import util.DBUtil; /** * 一个类中两个方法事务管理(本例中采用方法2) * 解决方法: 方法 1. 将Conenction作为参数传到两个方法中 * 方法 2. 将Connection放到ThreadLocal中管理
* 注意: 一定要在insert_2_1和insert_2_2方法中把异常抛出去。 * @author 半颗柠檬、 * */ public class UserDao_2 { public void insert_2(){ UserDao_2 userDao_2=new UserDao_2(); Connection conn=DBUtil.getConn(); try { conn.setAutoCommit(false); userDao_2.insert_2_1(conn); userDao_2.insert_2_2(conn); conn.commit(); } catch (Exception e) { e.printStackTrace(); try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } }finally{ //释放资源 } }
public void insert_2_1(Connection conn){ System.out.println("conn2="+conn); Statement stat=null; StringBuffer SQL=new StringBuffer(); try { /** * 插入到user表 */ SQL.append("insert into user values('111',11,'11')"); stat=conn.createStatement(); stat.executeUpdate(SQL.toString()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ //释放资源 } } public void insert_2_2(Connection conn){ System.out.println("conn3="+conn); Statement stat=null; StringBuffer SQL=new StringBuffer(); try { stat=conn.createStatement(); /** * 插入到角色表 */ SQL.setLength(0); SQL.append("insert into role values(3,'普通用户','普通用户备注')"); stat.executeUpdate(SQL.toString()); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ //释放资源 } } public static void main(String[] args) { UserDao_2 dao_2=new UserDao_2(); dao_2.insert_2(); } }
- DBUtil.java(使用ThreadLocal存放Connectino对象,保证使用的是同一个Connection)
package util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static ThreadLocal<Connection> local=new ThreadLocal<Connection>(); private static final String DRIVER="com.mysql.jdbc.Driver"; private static final String USER="root"; private static final String PASSWD=""; private static final String URL="jdbc:mysql://127.0.0.1:3306/user?useUnicode=true&characterEncoding=UTF-8"; static{ try { Class.forName(DRIVER); } catch (Exception e) { throw new RuntimeException("无法加载驱动包"); } } public static Connection getConn(){ Connection conn=null; try { if(local.get() !=null ){ conn=local.get(); }else{ conn= DriverManager.getConnection(URL,USER,PASSWD); local.set(conn); } } catch (SQLException e) { e.printStackTrace(); } return conn; } }
- 案例三:(编程式事务):无法在多个方法或者多个类中,共享一个事务。
- 本例中update_1方法和update_2方法没有办法共享同一个事务。
-
UserDao_TransactionManager.java
package transaction_2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; /** * 编程式事务:无法在多个方法或者多个类中,共享一个事务。 * 本例中update_1方法和update_2方法没有办法共享同一个事务,即执行update方法,如果update_2出现异常,那么update_1还是会执行,不会被回滚。 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager extends JdbcDaoSupport{ private void update() { this.update_1(); this.update_2(); } public void update_1(){ //创建事务的平台管理器 PlatformTransactionManager transactionManager=new DataSourceTransactionManager(this.getDataSource()); //定义事务的属性 DefaultTransactionDefinition definition=new DefaultTransactionDefinition(); //设置超时时间 definition.setTimeout(1000); //设置不只读 definition.setReadOnly(false); //设置事务隔离级别 definition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_UNCOMMITTED); //设置事务的传播属性 definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); //获取事务的状态 TransactionStatus status=transactionManager.getTransaction(definition); try { //执行语句 this.getJdbcTemplate().update("update user set age=11 where userName='张三'"); this.getJdbcTemplate().update("update user set age=22 where userName='李四'"); transactionManager.commit(status); } catch (Exception e) { e.printStackTrace(); }finally{ //释放资源 } } public void update_2(){ //创建事务的平台管理器 PlatformTransactionManager transactionManager=new DataSourceTransactionManager(this.getDataSource()); //定义事务的属性 DefaultTransactionDefinition definition=new DefaultTransactionDefinition(); //设置超时时间 definition.setTimeout(1000); //设置不只读 definition.setReadOnly(false); //设置事务隔离级别 definition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_UNCOMMITTED); //设置事务的传播属性 definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); //获取事务的状态 TransactionStatus status=transactionManager.getTransaction(definition); try { //执行语句 this.getJdbcTemplate().update("update user set sex='男' where userName='张三'"); this.getJdbcTemplate().update("update user1 set sex='男' where userName='李四'"); transactionManager.commit(status); } catch (Exception e) { e.printStackTrace(); }finally{ //释放资源 } } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_TransactionManager dao_TransactionManager=(UserDao_TransactionManager)context.getBean("UserDao_TransactionManager"); dao_TransactionManager.update(); } }
spring.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <constructor-arg index="0" name="driverClassName" value="com.mysql.jdbc.Driver" ></constructor-arg> <constructor-arg index="1" name="url" value="jdbc:mysql://127.0.0.1:3306/user?useUnicode=true&characterEncoding=UTF-8" ></constructor-arg> <constructor-arg index="2" name="username" value="root"></constructor-arg> <constructor-arg index="3" name="password" value=""></constructor-arg> </bean> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <bean id="UserDao_TransactionManager" class="transaction_2.UserDao_TransactionManager"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean>
- 案例四:(编程式事务):无法在多个方法或者多个类中,共享一个事务。
- 本例将一个平台事务管理器作为参数传到方法里,但仍旧无法使这两个方法共享一个事务。
- 本例中update_1方法和update_2方法没有办法共享同一个事务。即如果update方法里的update_2出现异常,那么update_1方法不会回滚。
UserDao_TransactionManager_2.java
package transaction_2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; /** * 编程式事务:无法在多个方法或者多个类中,共享一个事务。 * 本例中update_1方法和update_2方法没有办法共享同一个事务。即如果update方法里的update_2出现异常,那么update_1方法不会回滚。 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager_2 extends JdbcDaoSupport{ /** * 此方法中将一个平台事务管理器作为参数传到方法里,但仍旧无法使这两个方法共享一个事务。 */ private void update() { PlatformTransactionManager platformTransactionManager=new DataSourceTransactionManager(this.getDataSource()); this.update_1(platformTransactionManager); this.update_2(platformTransactionManager); } public void update_1(PlatformTransactionManager platformTransactionManager ){ //创建事务的平台管理器 //定义事务的属性 DefaultTransactionDefinition definition=new DefaultTransactionDefinition(); //设置超时时间 definition.setTimeout(1000); //设置不只读 definition.setReadOnly(false); //设置事务隔离级别 definition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_UNCOMMITTED); //设置事务的传播属性 definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); //获取事务的状态 TransactionStatus status=platformTransactionManager.getTransaction(definition); try { //执行语句 this.getJdbcTemplate().update("update user set age=11 where userName='张三'"); this.getJdbcTemplate().update("update user set age=11 where userName='李四'"); platformTransactionManager.commit(status); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ //释放资源 } } public void update_2(PlatformTransactionManager platformTransactionManager){ //定义事务的属性 DefaultTransactionDefinition definition=new DefaultTransactionDefinition(); //设置超时时间 definition.setTimeout(1000); //设置不只读 definition.setReadOnly(false); //设置事务隔离级别 definition.setIsolationLevel(DefaultTransactionDefinition.ISOLATION_READ_UNCOMMITTED); //设置事务的传播属性 definition.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); //获取事务的状态 TransactionStatus status=platformTransactionManager.getTransaction(definition); try { //执行语句 this.getJdbcTemplate().update("update user set sex='女' where userName='张三'"); this.getJdbcTemplate().update("update user set sex='女' where userName='李四'"); platformTransactionManager.commit(status); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); }finally{ //释放资源 } } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_TransactionManager_2 dao_TransactionManager=(UserDao_TransactionManager_2)context.getBean("UserDao_TransactionManager_2"); dao_TransactionManager.update(); } }
spring.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <constructor-arg index="0" name="driverClassName" value="com.mysql.jdbc.Driver" ></constructor-arg> <constructor-arg index="1" name="url" value="jdbc:mysql://127.0.0.1:3306/user?useUnicode=true&characterEncoding=UTF-8" ></constructor-arg> <constructor-arg index="2" name="username" value="root"></constructor-arg> <constructor-arg index="3" name="password" value=""></constructor-arg> </bean> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <bean id="UserDao_TransactionManager_2" class="transaction_2.UserDao_TransactionManager_2"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean>
- 案例五:(声明式事务)Spring的事务中第一种配置方式: 一个Bean对应一个Bean的代理类。 为UserDao_TransactionManager_3产生一个代理类
package transaction_2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; /** * 使用声明式事务 * 本例中使用第一种声明式事务,可以实现在多个方法或者多个类中,共享一个事务。即如果update方法里的update_2出现异常,那么update_1方法回滚。 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager_3 extends JdbcDaoSupport implements UserDao_Interface{ public void update() { this.update_1(); this.update_2(); } public void update_1(){ this.getJdbcTemplate().update("update user set age=111 where userName='张三'"); this.getJdbcTemplate().update("update user set age=221 where userName='李四'"); } public void update_2(){ this.getJdbcTemplate().update("update user set sex='女1' where userName='张三'"); this.getJdbcTemplate().update("update user1 set sex='女1' where userName='李四'"); } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_Interface userDao_Interface=(UserDao_Interface)context.getBean("UserDao_TransactionManager_3_proxy"); userDao_Interface.update(); } }
spring.xml
<bean id="UserDao_TransactionManager_3" class="transaction_2.UserDao_TransactionManager_3"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <bean id="UserDao_TransactionManager_3_proxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="target" ref="UserDao_TransactionManager_3"></property> <property name="transactionManager" ref="transactionManager"></property> <property name="transactionAttributes"> <props> <!-- update*方法使用的是同一个事务,因为传播属性为PROPAGATION_REQUIRED,有事务则使用本事务,没有事务则创建一个新事务 --> <prop key="update*">PROPAGATION_REQUIRED</prop> <prop key="add*">PROPAGATION_REQUIRED</prop> <prop key="delete*">PROPAGATION_REQUIRED</prop> <prop key="list*">readOnly</prop> </props> </property> </bean>
- 案例六:(声明式事务)spring声明式事务的第二种配置方式,即定义一个事务的基类
package transaction_2; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; /** * 编程式事务:无法在多个方法或者多个类中,共享一个事务。 * 本例中使用第二种声明式事务,可以实现在多个方法或者多个类中,共享一个事务。即如果update方法里的update_2出现异常,那么update_1方法回滚。 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager_4 extends JdbcDaoSupport implements UserDao_Interface{ public void update() { this.update_1(); this.update_2(); } public void update_1(){ this.getJdbcTemplate().update("update user set age=111 where userName='张三'"); this.getJdbcTemplate().update("update user set age=221 where userName='李四'"); } public void update_2(){ this.getJdbcTemplate().update("update user set sex='女1' where userName='张三'"); this.getJdbcTemplate().update("update user set sex='女1' where userName='李四'"); } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_Interface userDao_Interface=(UserDao_Interface)context.getBean("UserDao_TransactionManager_4"); userDao_Interface.update(); } }
spring.xml
<bean id="baseProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <!-- false:只能针对接口做代理。true表示可以对接口和类做代理 --> <property name="proxyTargetClass" value="true"></property> <property name="transactionManager" ref="transactionManager"></property> <property name="transactionAttributes"> <props> <prop key="update*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="UserDao_TransactionManager_4" parent="baseProxy"> <property name="target" > <bean class="transaction_2.UserDao_TransactionManager_4"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> </property> </bean>
- 案例七:(生命式事务)Spring事务的第3种配置方式,使用Spring中拦截器的方式
UserDao_TransactionManager_5.java
package transaction_3; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import transaction_2.UserDao_Interface; /** * 编程式事务:无法在多个方法或者多个类中,共享一个事务。 * 本例中使用第三种声明式事务,可以实现在多个方法或者多个类中,共享一个事务。即如果update方法里的update_2出现异常,那么update_1方法回滚。 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager_5 extends JdbcDaoSupport implements UserDao_Interface{ public void update() { this.update_1(); this.update_2(); } public void update_1(){ this.getJdbcTemplate().update("update user set age=111 where userName='张三'"); this.getJdbcTemplate().update("update user set age=221 where userName='李四'"); } public void update_2(){ this.getJdbcTemplate().update("update user set sex='女1' where userName='张三'"); this.getJdbcTemplate().update("update user1 set sex='女1' where userName='李四'"); } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_Interface userDao_Interface=(UserDao_Interface)context.getBean("UserDao_TransactionManager_5"); userDao_Interface.update(); } }
spring.xml
<!-- id为advice_1的事务,其管理器为transactionManager --> <tx:advice id="advice_1" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="append*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="modify*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="repair" propagation="REQUIRED" /> <tx:method name="delAndRepair" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="load*" propagation="SUPPORTS" /> <tx:method name="search*" propagation="SUPPORTS" /> <tx:method name="datagrid*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config> <!-- Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. --> <aop:pointcut expression="execution(* transaction_3.*.*(..))" id="point_1"/> <aop:advisor advice-ref="advice_1" pointcut-ref="point_1"/> </aop:config> <bean id="UserDao_TransactionManager_5" class="transaction_3.UserDao_TransactionManager_5"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean>
-
execution(* transaction_3.*.*(..))Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. execution(* transaction_3.*.*(..))意思是范围值为任意的在transaction_3
包里的任意子类中的任意方法(参数也是任意)。
- 关于<aop:pointcut expression="" id="point_1"/> 中expression的使用规则:
其中execution 是用的最多的,其格式为:
- execution(权限修饰符 返回值类型 方法所在类的所在路径(方法参数) 异常 )
- 其中 返回值类型和方法所在类的所在路径和方法参数是必须填的,其他可以不填。
- 返回值类型:可以为*表示任何返回值,全路径的类名等.
方法所在类的所在路径:指定方法名,*代表所以,set*,代表以set开头的所有方法.
方法参数:指定方法参数(声明的类型),(..)代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个为String类型. - 举例:
- 任意公共方法的执行:
execution(public * *(..))
任何一个以“set”开始的方法的执行:
execution(* set*(..))
AccountService 接口的任意方法的执行:
execution(* com.xyz.service.AccountService.*(..))
定义在service包里的任意方法的执行:
execution(* com.xyz.service.*.*(..))
定义在service包和所有子包里的任意类的任意方法的执行:
execution(* com.xyz.service..*.*(..))
- 案例八: (声明式事务)实现在者多个类中,共享一个事务
- 本例中使用第三种声明式事务,实现在者多个类中,共享一个事务。即如果Roledao类的update_2方法异常,则Userdao类的update_1也进行回滚操作
<aop:pointcut expression="execution(* transaction_3.*.*(..))" id="point_1"/> 中的事务定义一定要在service层(本例中为transaction_3包),
才能共享一个事务,如果expression="execution(* dao.*.*(..))" 则无法共享事务
- 本例中使用第三种声明式事务,实现在者多个类中,共享一个事务。即如果Roledao类的update_2方法异常,则Userdao类的update_1也进行回滚操作
UserDao_TransactionManager_6.java
package transaction_3; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import dao.RoleDao; import dao.UserDao; import transaction_2.UserDao_Interface; /** * 本例中使用第三种声明式事务,实现在者多个类中,共享一个事务。即如果Roledao类的update_2方法异常,则Userdao类的update_1也进行回滚操作 * <aop:pointcut expression="execution(* transaction_3.*.*(..))" id="point_1"/> 中的事务定义一定要在service层(本例中为transaction_3包), * 才能共享一个事务,如果expression="execution(* dao.*.*(..))" 则无法共享事务 * @author 半颗柠檬、 * */ public class UserDao_TransactionManager_6 extends JdbcDaoSupport implements UserDao_Interface{ private UserDao userdao; private RoleDao roledao; public void setUserdao(UserDao userdao) { this.userdao = userdao; } public void setRoledao(RoleDao roledao) { this.roledao = roledao; } public void update() { userdao.update_1(); roledao.update_2(); } public static void main(String[] args) { ApplicationContext context=new ClassPathXmlApplicationContext("spring.xml"); UserDao_Interface userDao_Interface=(UserDao_Interface)context.getBean("UserDao_TransactionManager_6"); userDao_Interface.update(); } }
UserDao.java
package dao; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class UserDao extends JdbcDaoSupport{ public void update_1(){ this.getJdbcTemplate().update("update user set age=111 where userName='张三'"); this.getJdbcTemplate().update("update user set age=111 where userName='李四'"); } }
RoleDao.java
package dao; import org.springframework.jdbc.core.support.JdbcDaoSupport; public class RoleDao extends JdbcDaoSupport { public void update_2(){ this.getJdbcTemplate().update("update role set role_name='aa' where role_id=1 "); this.getJdbcTemplate().update("update role1 set role_name='bb' where role_id=2 "); } }
spring.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <constructor-arg index="0" name="driverClassName" value="com.mysql.jdbc.Driver" ></constructor-arg> <constructor-arg index="1" name="url" value="jdbc:mysql://127.0.0.1:3306/user?useUnicode=true&characterEncoding=UTF-8" ></constructor-arg> <constructor-arg index="2" name="username" value="root"></constructor-arg> <constructor-arg index="3" name="password" value=""></constructor-arg> </bean> <bean id="jdbctemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- Spring事务的第3种配置方式,使用Spring中拦截器的方式 --> <!-- id为advice_1的事务,其管理器为transactionManager --> <tx:advice id="advice_1" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="append*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="modify*" propagation="REQUIRED" /> <tx:method name="edit*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="repair" propagation="REQUIRED" /> <tx:method name="delAndRepair" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" /> <tx:method name="find*" propagation="SUPPORTS" /> <tx:method name="load*" propagation="SUPPORTS" /> <tx:method name="search*" propagation="SUPPORTS" /> <tx:method name="datagrid*" propagation="SUPPORTS" /> <tx:method name="*" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <aop:config proxy-target-class="true"> <!-- Pointcut 是指那些方法需要被执行"AOP",是由"Pointcut Expression"来描述的. --> <aop:pointcut expression="execution(* transaction_3.*.*(..))" id="point_1"/> <aop:advisor advice-ref="advice_1" pointcut-ref="point_1"/> </aop:config> <!-- end --> <!-- UserDao_TransactionManager_6 配置 --> <bean id="userdao" class="dao.UserDao"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <bean id="roledao" class="dao.RoleDao"> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <bean id="UserDao_TransactionManager_6" class="transaction_3.UserDao_TransactionManager_6"> <property name="roledao" ref="roledao"></property> <property name="userdao" ref="userdao"></property> <property name="jdbcTemplate" ref="jdbctemplate"></property> </bean> <!-- end -->
路径如图:
- spring事务管理的机制:默认spring事务只在发生未被捕获的 runtimeexcetpion时才回滚。
spring aop异常捕获原理:被拦截的方法需显式抛出异常,并不能经任何处理,这样aop代理才能捕获到方法的异常,才能进行回滚,默认情况下aop只捕获runtimeexception的异常,但可以通过 配置来捕获特定的异常并回滚
换句话说在service的方法中不使用try catch 或者在catch中最后加上throw new runtimeexcetpion(),这样程序异常时才能被aop捕获进而回滚
- 解决方案:
方案1.例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new
RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理
方案2.在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)
本章所有代码都在: 链接 (数据库文件也在这里,文件名为role.sql 和 user.sql)