Spring 整合 Mybatis - 二(切面、事务管理)

紧接着上篇《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>

    <!--    &lt;!&ndash; 配置事务通知 &ndash;&gt;
    <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>

    &lt;!&ndash; aop 切面配置 &ndash;&gt;
    <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)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不会叫的狼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值