栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring整合MyBatis的事务管理

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring整合MyBatis的事务管理

环境
  • Ubuntu 22.04
  • IntelliJ IDEA 2022.1.3
  • JDK 17.0.3
  • Spring 5.3.21
  • MyBatis 3.5.10
准备

创建Maven项目 test0703 。

修改 pom.xml ,添加依赖以及设置静态资源:

    ......
    
        
        
            junit
            junit
            4.13.2
            test
        

        
        
            org.mybatis
            mybatis
            3.5.10
        

        
        
            mysql
            mysql-connector-java
            8.0.29
        

        
        
            org.springframework
            spring-webmvc
            5.3.21
        

        
        
            org.mybatis
            mybatis-spring
            2.0.7
        

        
        
            org.springframework
            spring-jdbc
            5.3.21
        

    

    
        
            
                src/main/java
                
                    ***.xml
                
                false
            
        
    
    ......

创建POJO MyObject.java :

package pojo;

public class MyObject {
    private int c1;
    private int c2;

    public int getC1() {
        return c1;
    }

    public void setC1(int c1) {
        this.c1 = c1;
    }

    public int getC2() {
        return c2;
    }

    public void setC2(int c2) {
        this.c2 = c2;
    }

    @Override
    public String toString() {
        return "MyObject{" +
                "c1=" + c1 +
                ", c2=" + c2 +
                '}';
    }
}

创建Mapper MyMapper.java 和 MyMapper.xml :

package dao;

import pojo.MyObject;

import java.util.List;

public interface MyMapper {
    public List read();

    public void add(int c1, int value);

    public void reduce(int c1, int value);
}





    
        select c1, c2 from t1
    

    
        update t1 set c2 = c2 + #{param2} where c1 = #{param1}
    

    
        update t1 set c2 = c2 - #{param2} where c1 = #{param1}
    

创建Service MyService.java (接口)和 MyServiceImpl1.java (实现类):

package service;

public interface MyService {
    public void show();

    public void change();
}
package service;

import dao.MyMapper;
import pojo.MyObject;

import java.util.List;

public class MyServiceImpl1 implements MyService{
    private MyMapper mapper;

    public void setMyMapper(MyMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public void show() {
        List list = mapper.read();
        list.forEach(System.out::println);
    }

    @Override
    public void change() {
        int value = 100;

        try {
            mapper.reduce(1, value);
            mapper.add(2, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

创建 applicationContext.xml (在 resources 目录):




    
    
        
        
        
        
    

    
    
        
        
        
    

    
        
        
    

    
        
    

创建测试 MyTest.java (在 src/test/java 目录):

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.MyService;

import java.io.IOException;

public class MyTest {
    @Test
    public void test1() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        MyService service = context.getBean("myService1", MyService.class);

        System.out.println("==================== Before: ====================");
        service.show();

        service.change();

        System.out.println("==================== After: ====================");
        service.show();
    }
}

运行测试,如下:

==================== Before: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
==================== After: ====================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}

本例模拟了一个简单的转账操作, c1 账户转出100元,同时 c2 账户转入100元。

没有事务管理的问题

假定转入操作失败了。本例中,为了模拟操作失败,我们故意把SQL写错:

    ......
    
        updateaaa t1 set c2 = c2 + #{param2} where c1 = #{param1}
    
    ......

再次运行测试:

==================== Before: ====================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......

==================== After: ====================
MyObject{c1=1, c2=9800}
MyObject{c1=2, c2=10100}

可见,这样就会出现问题, c1 账户已经转出100元,而 c2账户转入100元的操作失败了。

转账是一个典型的事务处理操作,应该使用事务管理,如果某一个操作失败,应该把整个事务回滚。

Spring的事务管理 声明式事务

Spring的声明式事务基于AOP实现,它的优点是只需修改Spring配置文件,无需修改Service组件的代码逻辑。

spring-webmvc 已经包含了对Spring AOP的依赖,但是我们还需要添加对 aspectjweaver 的依赖:

        ......
        
        
            org.aspectj
            aspectjweaver
            1.9.9.1
            runtime
        
        ......

修改 applicationContext.xml 配置文件:

    ......
    
    ......
    
        
    

    
        
            
        
    

    
        
        
    
    ......
  • 无论是声明式事务,还是编程式事务,都要添加 transactionManager ,格式是固定的,照抄就行(对于所需的 dataSource ,本例中使用的是构造器注入,如果换成属性注入也行);
  • txAdvice 的格式基本也是固定的:
    • 对于 method 属性,本例中使用了 name="*" ,表示所有方法都适用,可以改成比如 name="get*" ,则表示适用于 get 开头的方法;
    • propagation="REQUIRED" 是默认属性,不写也可以,表示如果当前没有事务,就新建一个事务,这是最常见的选择;
    • 此外,还有 read-only , tmeout 等属性,基本上顾名思义;
  • myPointcut :expression="execution(* service.*.*(..))" 表示 service 包下的所有类的所有方法,参数不限(本例中其实只有 change() 方法需要做事务管理);

现在就可以运行测试了,但有一点需要注意,虽然说声明式事务是非侵入式的,不用修改任何代码就能直接运行,但是在本例中,service里使用了 try 块:

        ......
        try {
            mapper.reduce(1, value);
            mapper.add(2, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
        ......

这会对事务失败的判断产生影响。具体说来,因为代码中捕获并处理了异常,事务管理器就不会认为事务失败并回滚事务了,就跟没有事务管理的效果一样,所以这里一定要把 try 去掉:

//        try {
            mapper.reduce(1, value);
            mapper.add(2, value);
//        } catch (Exception e) {
//            e.printStackTrace();
//        }

为了使得异常之后的代码能继续运行,可以把 try 块放到测试里:

        ......
        try {
            service.change();
        } catch (Exception e) {
            e.printStackTrace();
        }
        ......

把两条记录的 c2 值都还原为 10000 ,重新运行测试,如下:

==================== Before: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......

==================== After: ====================
MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}

可见,出现异常后,事务回滚了。具体来说,因为转入操作失败,则之前转出的操作回滚了。

编程式事务

修改 applicationContext.xml 文件:

    ......
    
        
    
    ......
  • transactionManager :和声明式事务一样,需要配置 transactionManager ;

创建 MyServiceImpl2.java :

package service;

import dao.MyMapper;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import pojo.MyObject;

import java.util.List;

public class MyServiceImpl2 implements MyService{
    private final PlatformTransactionManager transactionManager;

    private MyMapper mapper;

    public MyServiceImpl2(PlatformTransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    public void setMyMapper(MyMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public void show() {
        List list = mapper.read();
        list.forEach(System.out::println);
    }

    @Override
    public void change() {
        TransactionStatus txStatus =
                transactionManager.getTransaction(new DefaultTransactionDefinition());
        try {
            int value = 100;
            mapper.reduce(1, value);
            System.out.println("===================================");
            mapper.add(2, value);
        } catch (Exception e) {
            transactionManager.rollback(txStatus);
            throw e;
        }
        transactionManager.commit(txStatus);
    }
}

注:为了防止事务一开始就报错引起混淆,代码里加了一行输出,如果看到这行输出,说明转出操作已经做了。

注意:该方法需要显式 try ,捕获异常,并回滚事务。而前面介绍的声明式事务则不能捕获异常,要留给事务管理器来判断异常并回滚事务。

在 applicationContext.xml 中注册 MyServiceImpl2 :

    ......
    
        
        
    
    ......

创建测试如下:

    @Test
    public void test2() throws IOException {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        MyService service = context.getBean("myService2", MyService.class);
        service.show();

        try {
            service.change();
        } catch (Exception e) {
            e.printStackTrace();
        }

        service.show();
    }

把SQL改成正确写法,把两条记录的 c2 值都还原为 10000 ,运行test2,如下:

MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
===================================
MyObject{c1=1, c2=9900}
MyObject{c1=2, c2=10100}

把SQL改成错误写法,把两条记录的 c2 值都还原为 10000 ,运行test2,如下:

MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}
===================================

org.springframework.jdbc.BadSqlGrammarException: 
### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2' at line 1
### The error may exist in file [/home/ding/IdeaProjects/test0703/target/classes/dao/MyMapper.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: updateaaa t1 set c2 = c2 + ? where c1 = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'updateaaa t1 set c2 = c2 + 100 where c1 = 2'
......

MyObject{c1=1, c2=10000}
MyObject{c1=2, c2=10000}

可见,转入操作失败,事务回滚了。

编程式事务有一个变种的用法:

    ......
    @Override
    public void change() {
        TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

        transactionTemplate.execute(txStatus -> {
            int value = 100;
            mapper.reduce(1, value);
            System.out.println("===================================");
            mapper.add(2, value);
            return null;
        });
    }
    ......

效果和前面一样,不再赘述。

注意:这里跟声明式事务类似,千万不要 try 并且捕获异常,否则事务管理器就不会认为事务失败了。

还有一点需要注意的是,在配置 SqlSessionFactory 时,不要添加如下属性:

  
    
  

否则会报错: org.springframework.dao.TransientDataAccessResourceException: SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization

该属性值表示由容器管理事务,而不使用Spring的事务管理。

总结
  • 声明式事务使用AOP来管理事务,用法相对比较简单,而且是非侵入式的,推荐使用;
  • 编程式事务需要在代码逻辑里显式管理事务,相对比较麻烦;
  • 对于声明式事务以及编程式事务的变种写法,千万记得不要 try 并且捕获异常,否则事务管理器就不会认为事务失败了。当然,可以在外层捕获异常;
参考
  • http://mybatis.org/spring
  • http://mybatis.org/spring/zh/index.html (中文版)
  • https://www.bilibili.com/video/BV1WE411d7Dv?p=27&vd_source=2ec1bf89a54b0d3afd52d89760b0dd55
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1002912.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号