紧接着上篇《Spring 整合 Mybatis - 一(基础)》,介绍Spring 整合 Mybatis的切面、事务管理。
1 增加切面aop功能
1.1 spring.xml
spring.xml增加aop的命名空间:
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
以及开启Aspect生成代理对象
<aop:aspectj-autoproxy />
spring.xml:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 扫描基本包 -->
<context:component-scan base-package="com.ymqx" />
<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy />
<!-- 加载properties 配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
...
</beans>
1.2 切面类LogAspect
package com.ymqx.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* 日记切面类
*/
@Aspect
@Component
public class LogAspect {
@Pointcut("execution(* com.ymqx.service.*..*.*(..))")
private void logPointcut() {}
/**
* 前置通知
*/
@Before("logPointcut()")
public void beforeLog(JoinPoint joinPoint){
System.out.println("========目标方法("+joinPoint.getSignature().getName()+")执行之前记录日记=======");
}
/**
* 后置通知
*/
@After("logPointcut()")
public void afterLog(JoinPoint joinPoint){
System.out.println("========目标方法执行("+joinPoint.getSignature().getName()+")之后记录日记=======");
}
}
运行结果:
2 配置spring对数据库的事务管理
先看个例子:
StudentServiceImpl增加“先更新再新增”方法updateAndInsert
@Service
public class StudentServiceImpl implements IStudentService {
@Autowired
StudentDao studentDao;
@Override
public Student selectStudentById(int id) {
Student student = studentDao.selectStudentById(id);
return student;
}
@Override
public int addStudent(Student student) {
return studentDao.addStudent(student);
}
@Override
public int updateStudent(Student student) {
return studentDao.updateStudent(student);
}
@Override
public int updateAndInsert(Student student) {
int ret = studentDao.updateStudent(student);
ret = studentDao.addStudent(student);
return ret;
}
}
运行:
数据库id = '1001’的记录修改成功。显然不符合一并提交的需求。
2.1 spring.xml增加事务支持
修改spring.xml配置文件,开启事务注解功能并配置数据库的事务管理者。使用spring-jdbc.jar包提供的DataSourceTransactionManager类配置数据库事务管理者。
1、增加命名空间
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
2、配置Spring框架声明式事务管理
<!--配置事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
3、配置事务通知
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
4、aop 切面配置
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.ymqx.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
</aop:config>
spring.xml:
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 扫描基本包 -->
<context:component-scan base-package="com.ymqx" />
<!-- 开启Aspect生成代理对象-->
<aop:aspectj-autoproxy />
<!-- 加载properties 配置文件 -->
<context:property-placeholder location="classpath:db.properties" />
<!-- 配置c3p0 数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<!-- 配置 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis.xml" />
<property name="mapperLocations" value="classpath:mapper/*.xml" />
</bean>
<!-- 配置扫描器 -->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 扫描com.xxxx.dao这个包以及它的子包下的所有映射接口类 -->
<property name="basePackage" value="com.ymqx.dao" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<!--配置Spring框架声明式事务管理-->
<!--配置事务管理器-->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!-- aop 切面配置 -->
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.ymqx.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
</aop:config>
<!-- 开启事务注解功能 -->
<tx:annotation-driven></tx:annotation-driven>
</beans>
运行结果:
数据库id = '1001’的记录没有修改,因为下面的插入失败,一起回滚事务。
3.2 注解方式事务支持
1、开启事务注解功能
<!-- 开启事务注解功能 -->
<tx:annotation-driven></tx:annotation-driven>
spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
...
<!--配置Spring框架声明式事务管理-->
<!--配置事务管理器-->
<bean id="txManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启事务注解功能 -->
<tx:annotation-driven></tx:annotation-driven>
<!-- <!– 配置事务通知 –>
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="delete*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<!– aop 切面配置 –>
<aop:config>
<aop:pointcut id="servicePointcut" expression="execution(* com.ymqx.service.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut" />
</aop:config>-->
</beans>
2、@Transactional注解
@Transactional
- 可以在类上添加,也可以在方法上添加,添加在方法上只对该方法有效
- 不指定事务管理器的id,默认是"transactionManager"
- 回滚的配置必须要求异常是RuntimeException或其子类才会起作用
- rollbackFor:当异常类型为指定类型时才会回滚事务
- noRollbackFor:当异常类型为指定类型时不回滚事务
@Service
public class StudentServiceImpl implements IStudentService {
@Autowired
StudentDao studentDao;
@Override
public Student selectStudentById(int id) {
Student student = studentDao.selectStudentById(id);
return student;
}
@Override
public int addStudent(Student student) {
return studentDao.addStudent(student);
}
@Override
public int updateStudent(Student student) {
return studentDao.updateStudent(student);
}
@Override
@Transactional(transactionManager = "txManager",
noRollbackFor = {ArithmeticException.class},
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED)
public int updateAndInsert(Student student) {
int ret = studentDao.updateStudent(student);
ret = studentDao.addStudent(student);
return ret;
}
}
运行结果:
数据库id = '1001’的记录没有修改,因为下面的插入失败,一起回滚事务。
注意:如果配置文件指定了事务管理器的id名称,@Transactional(transactionManager = “自定义名称” );如果没有指定事务管理器id,@Transactional默认读取id = "transactionManager"
3、isolation事务隔离级别
- DEFAULT:默认值,使用底层数据库的默认隔离级别。对大部分数据库而言就是READ_COMMITTED。
- READ_UNCOMMITTED:一个事务可以读取另一个事务修改但还没有提交的数据。(不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别)
- READ_COMMITTED:一个事务只能读取另一个事务已经提交的数据。(可以防止脏读,这也是大多数情况下的推荐值)
- REPEATABLE_READ:一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。(可以防止脏读和不可重复读)
- SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰。(可以防止脏读、不可重复读以及幻读)(严重影响程序的性能)
4、propagation事务传播
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
- REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED。
service新建两个服务ServiceA、ServiceB
package com.ymqx.service;
import com.ymqx.dao.StudentDao;
import com.ymqx.entities.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ServiceA {
@Autowired
StudentDao studentDao;
@Autowired
ServiceB serviceB;
public void doServiceA(){
try {
serviceB.doServiceB();
} catch (RuntimeException e) {
System.out.println("A捕获了B的运行时异常");
}
Student student = new Student(1001, "doServiceA", "girl" );
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServiceA".equals(student1.getName())) {
System.out.println("A抛了一个异常");
throw new RuntimeException("A是doServiceA");
}
}
}
package com.ymqx.service;
import com.ymqx.dao.StudentDao;
import com.ymqx.entities.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ServiceB {
@Autowired
StudentDao studentDao;
public void doServiceB(){
Student student = new Student(1001, "doServiceB", "boy");
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServiceB".equals(student.getName())) {
System.out.println("B抛了一个异常");
throw new RuntimeException("B是doServiceB");
}
}
}
程序调用:
package com.ymqx;
import com.ymqx.entities.Student;
import com.ymqx.service.IStudentService;
import com.ymqx.service.ServiceA;
import com.ymqx.service.impl.StudentServiceImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Hello world!
*
*/
public class App
{
public static void main( String[] args )
{
// 加载Spring的配置
BeanFactory factory = new ClassPathXmlApplicationContext("spring.xml");
ServiceA serviceA = (ServiceA) factory.getBean("serviceA");
try {
serviceA.doServiceA();
} catch (RuntimeException e) {
System.out.println("test捕获了A一个运行时异常:"+e.getClass());
}
}
}
运行结果:
2022-04-15 02:31:57,642 [main] DEBUG [com.ymqx.dao.StudentDao.selectStudentById] - ==> Parameters: 1001(Integer)
2022-04-15 02:31:57,678 [main] DEBUG [com.ymqx.dao.StudentDao.selectStudentById] - <== Total: 1
2022-04-15 02:31:57,687 [main] DEBUG [org.mybatis.spring.SqlSessionUtils] - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@7c214cc0]
2022-04-15 02:31:57,687 [main] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource
2022-04-15 02:31:57,688 [main] DEBUG [com.mchange.v2.resourcepool.BasicResourcePool] - trace com.mchange.v2.resourcepool.BasicResourcePool@3b8f0a79 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@7e096ec5)
B抛了一个异常
A捕获了B的运行时异常
...
2022-04-15 02:31:57,695 [main] DEBUG [com.ymqx.dao.StudentDao.selectStudentById] - ==> Preparing: select * from student where id = ?
2022-04-15 02:31:57,696 [main] DEBUG [com.ymqx.dao.StudentDao.selectStudentById] - ==> Parameters: 1001(Integer)
2022-04-15 02:31:57,697 [main] DEBUG [com.ymqx.dao.StudentDao.selectStudentById] - <== Total: 1
2022-04-15 02:31:57,697 [main] DEBUG [org.mybatis.spring.SqlSessionUtils] - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@44d52de2]
2022-04-15 02:31:57,697 [main] DEBUG [org.springframework.jdbc.datasource.DataSourceUtils] - Returning JDBC Connection to DataSource
2022-04-15 02:31:57,697 [main] DEBUG [com.mchange.v2.resourcepool.BasicResourcePool] - trace com.mchange.v2.resourcepool.BasicResourcePool@3b8f0a79 [managed: 3, unused: 1, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@7e096ec5)
A抛了一个异常
test捕获了A一个运行时异常:class java.lang.RuntimeException
数据库id = 1001的记录被修改,数据库结果:1001 doServiceA girl
增加事务传播 ServiceA、ServiceB均抛异常(失败)
ServiceA:
@Service
public class ServiceA {
@Autowired
StudentDao studentDao;
@Autowired
ServiceB serviceB;
@Transactional(transactionManager = "txManager")
public void doServiceA(){
try {
serviceB.doServiceB();
} catch (RuntimeException e) {
System.out.println("A捕获了B的运行时异常");
}
Student student = new Student(1001, "doServiceA", "girl" );
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServiceA".equals(student1.getName())) {
System.out.println("A抛了一个异常");
throw new RuntimeException("A是doServiceA");
}
}
}
ServiceB:
@Service
public class ServiceB {
@Autowired
StudentDao studentDao;
@Transactional(transactionManager = "txManager",propagation = Propagation.NESTED)
public void doServiceB(){
Student student = new Student(1001, "doServiceB", "boy");
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServiceB".equals(student.getName())) {
System.out.println("B抛了一个异常");
throw new RuntimeException("B是doServiceB");
}
}
}
运行结果:
数据库id = 1001的记录没有修改。
修改ServiceB,使其不抛异常(即是成功)
ServiceB:
public class ServiceB {
@Autowired
StudentDao studentDao;
@Transactional(transactionManager = "txManager",propagation = Propagation.NESTED)
public void doServiceB(){
Student student = new Student(1001, "doServiceB", "boy");
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServic".equals(student.getName())) {
System.out.println("B抛了一个异常");
throw new RuntimeException("B是doServiceB");
}
}
}
运行结果:
数据库id = 1001的记录没有修改。因为ServiceA失败,ServiceB回滚。
修改ServiceA,使得ServiceA成功、ServiceB失败
@Service
public class ServiceA {
@Autowired
StudentDao studentDao;
@Autowired
ServiceB serviceB;
@Transactional(transactionManager = "txManager")
public void doServiceA(){
try {
serviceB.doServiceB();
} catch (RuntimeException e) {
System.out.println("A捕获了B的运行时异常");
}
Student student = new Student(1001, "doServiceA", "girl" );
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doService".equals(student1.getName())) {
System.out.println("A抛了一个异常");
throw new RuntimeException("A是doServiceA");
}
}
}
@Service
public class ServiceB {
@Autowired
StudentDao studentDao;
@Transactional(transactionManager = "txManager",propagation = Propagation.NESTED)
public void doServiceB(){
Student student = new Student(1001, "doServiceB", "boy");
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doServiceB".equals(student.getName())) {
System.out.println("B抛了一个异常");
throw new RuntimeException("B是doServiceB");
}
}
}
运行结果:
数据库id = 1001的记录被修改。虽然ServiceB回滚了,但是ServiceA成功。
修改ServiceA,使得ServiceA、ServiceB均成功
ServiceA:
@Service
public class ServiceA {
@Autowired
StudentDao studentDao;
@Autowired
ServiceB serviceB;
@Transactional(transactionManager = "txManager")
public void doServiceA(){
try {
serviceB.doServiceB();
} catch (RuntimeException e) {
System.out.println("A捕获了B的运行时异常");
}
Student student = new Student(1001, "doServiceA", "girl" );
studentDao.updateStudent(student);
Student student1 = studentDao.selectStudentById(1001);
if ("doService".equals(student1.getName())) {
System.out.println("A抛了一个异常");
throw new RuntimeException("A是doServiceA");
}
}
}
数据库id = 1001的记录修改成功。
综上,只有ServiceA 成功,事务才会成功;ServiceA 如果失败,不管ServiceB是否成功,都会回滚。
实际案例场景:
假设我们有一个注册的方法,方法中调用添加积分的方法,如果我们希望添加积分不会影响注册流程(即添加积分执行失败回滚不能使注册方法也回滚)可以使用 Spring事务的传播机制
5、readOnly只读事务
readOnly配置事务只读属性,只读事务用于客户代码只读但不修改数据的情形。
@Transactional(transactionManager = "dataSourceTransactionManager",
readOnly = true)
6、timeout事务超时
timeout配置事务超时,单位是秒,指定一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
@Transactional(transactionManager = "dataSourceTransactionManager",
timeout = 6)