二.Spring编程式事务
从本节开始,我们来了解一下Spring JDBC中如何进行事务管理。
编程式事务:编程式事务,就是指通过代码手动提交回滚的事务控制方法。Spring JDBC通过TransactionManager事务管理器实现事务控制。事务管理器提供commit/rollback方法进行事务提交与回滚。
下面通过实际的代码,来通过事务管理器来提交和回滚事务。
下面要把10名新员工导入到employee表中。对于这导入的10名新员工,我有一个小要求,要么全部导入成功,要么全部导入失败,什么都不做。
下面新创建一个service包,像上面批量导入的操作是属于业务逻辑中的方法。然后在里面创建EmployeeService类。
package com.haiexijun.service; import com.haiexijun.dao.EmployeeDao; import com.haiexijun.entity.Employee; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import java.util.Date; public class EmployeeService { private EmployeeDao employeeDao; private DataSourceTransactionManager transactionManager; //批量插入10名员工 public void batchImport(){ //定义了事务默认的标准配置 TransactionDefinition definition=new DefaultTransactionDefinition(); TransactionStatus status= transactionManager.getTransaction(definition); try { //我们用for循环来模拟一下 for (int i = 1; i < 11; i++) { Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("员工" + i); employee.setSalary(4000f); employee.setDname("市场部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } //提交事务 transactionManager.commit(status); }catch (Exception e){ //回滚事务 transactionManager.rollback(status); throw e; } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } public DataSourceTransactionManager getTransactionManager() { return transactionManager; } public void setTransactionManager(DataSourceTransactionManager transactionManager) { this.transactionManager = transactionManager; } }
还要在applicationContext.xml中对EmployeeService与事务管理器事务管理器进行配置:
<!--配置EmployeeService--> <bean id="employeeService" class="com.haiexijun.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"/> <property name="transactionManager" ref="transactionManager"/> </bean> <!--编程式事务的配置(配置事务管理器)--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--绑定数据源--> <property name="dataSource" ref="dataSource"/> </bean>
这样就算配置好了。
三.Spring声明式事务
声明式事务是指在不修改源代码的情况下通过配置的形式自动实现事务控制,声明式事务的本质就是AOP环绕通知。当目标方法执行成功时,自动提交事务。当目标方法抛出运行时异常时,自动回滚事务。
声明式事务的整个配置过程都是在applicationContext.xml这个配置文件里面来完成的,无需修改源代码。
配置过程:
1.配置TransactionManager事务管理器。
2.配置事务通知与事务属性。有的方法需要使用事务,而有的则不需要使用事务,比如查询事务。我么要根据不同的情况不同配置。
3.为事务通知绑定PointCut切点。PointCut切点用于说明到底是在系统的哪些类的哪些方法上来应用通知呢?PointCut相当于限定了执行范围。
下面还是通过案例来演示声明式事务。还是回到之前的项目,把之前配置编程式事务的代码和配置给删掉,保留原始的批量新增用户的代码。
上面提到过,声明式事务基于AOP的,那就要导入Spring AOP的相关的aspectjweaer依赖。
<!--aspectjweaer是Spring AOP的底层依赖--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.8.RC2</version> </dependency
然后打开applicationContext这个文件,进行声明式事务的配置:
无论是编程式事务还是声明式事务,都要配置transactionManager事务管理器。我们还要添加一段xml命名空间:**xmlns:ts=“http://www.springframework.org/schema/tx”**和真实schema文件的位置:http://www.springframework.org/schema/txhttps://www.springframework.org/schema/context/spring-tx.xsd
tx这个命名空间专用于事务控制。
除了tx以外,我们还要增加aop这个命名空间。xmlns:aop="http://www.springframework.org/schema/aop"和spring aop的schema的真实地址http://www.springframework.org/schema/aophttps://www.springframework.org/schema/context/spring-aop.xsd
下面是注解,bean,ioc,aop,tx事务管理的约束配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>
下面开始事务通知的配置,决定哪些方法使用事务,哪些方法不使用事务:
<bean id="employeeService" class="com.haiexijun.service.EmployeeService"> <property name="employeeDao" ref="employeeDao"/> </bean> <!--声明式事务的配置--> <!--1.事务管理器的配置用于 |创建事务、提交事务、回滚事务--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!--2.事务通知配置,决定哪些方法使用事务,哪些方法不使用事务--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!--用于设置声明式事务的属性--> <tx:attributes> <!--说明哪些方法要使用事务,哪些方法不使用事务--> <!--name设置要实用事务的方法名,propagation设置事务的传播行为--> <!--百分之九十九的情况下propagation都是使用REQUIRED这个参数--> <tx:method name="batchImport" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--配置通知作用在哪些类上(作用范围)--> <aop:config> <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun.service..*Service.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/> </aop:config>
下面对batchImport方法进行改造,制造一个运行时异常。看运行结果:
package com.haiexijun.service; import com.haiexijun.dao.EmployeeDao; import com.haiexijun.entity.Employee; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; import java.util.Date; public class EmployeeService { private EmployeeDao employeeDao; //批量插入10名员工 public void batchImport(){ for (int i = 1; i < 11; i++) { if (i==3){ throw new RuntimeException("意料之外的异常"); } Employee employee = new Employee(); employee.setEno(8000 + i); employee.setEname("员工" + i); employee.setSalary(4000f); employee.setDname("市场部"); employee.setHiredate(new Date()); employeeDao.insert(employee); } } public EmployeeDao getEmployeeDao() { return employeeDao; } public void setEmployeeDao(EmployeeDao employeeDao) { this.employeeDao = employeeDao; } }
下面把异常去掉,运行看结果:
这时候又引发了一个新的问题,作为EmployeeService的方法中只有一个batchImport方法,但是在未来EmployeeService中会有各种各样的方法,难道我们要定义成百上千个<tx:method name=“batchImport” propagation=“REQUIRED”/>来配置哪些方法使用事务吗?其实大可不必,其实作为method的配置他允许进进行通配符映射。如上面的batchImport可以写成batch*,也是一样的效果。那如果对于查询不需要事务,该这么去配置呢?其实也很简单。配置:<tx: method name=“find*” propagation=“NOT_SUPPORTED” read-only=“true”>就可以了。
四.事务传播行为
事务传播行为在我们日常开发中,使用的比较少。
事务传播行为是指多个拥有事务的方法在嵌套调用时的事务控制方式。
五.注解配置声明式事务
把applicationContext.xml之前的关于声明式事务的配置删除掉,就是如下部分:
<!--2.事务通知配置,决定哪些方法使用事务,哪些方法不使用事务--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="batchImport" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <!--配置通知作用在哪些类上(作用范围)--> <aop:config> <aop:pointcut id="pointcut" expression="execution(public * com.haiexijun.service..*Service.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/> </aop:config>
然后换成注解形式配置声明式事务:
添加如下的代码,就可以了:
<tx:annotation-driven transaction-manager="transactionManager"/>
然后就可以用注解进行配置了。配置声明式事务我们只需要用到@Transactional这一个注解就行了
import java.util.Date; //声明式事务的核心注解 //放在类上,表示将声明式事务配置于当前类的所有方法中,默认事务传播为REQUIRED,也可以配置propagation属性 //也可以写在方法中,表示只对方法生效。 //如:@Transactional((propagation = Propagation.REQUIRED))等 @Transactional public class EmployeeService { ······················· }