• 【Java精炼系列篇】【事务】【事务的使用】


    【Java】【Spring】【事务的使用】

    一、前提纲要

    本文章涉及到事务入门、springboot多数据源的配置、隔离级别、事务的传播方式、在springboot中事务的使用以及多数据源分布式事务的配置和使用
    
    • 1

    二、事务

    2.1、事务相关元素组成(了解)

    2.1.1、DataSource(数据源)<可以自定义配置>
    一个数据库对应一个数据源。
    
    • 1
    2.1.2、TransactionDefinition
    事务属性通过TransactionDefinition接口实现定义,主要有事务隔离级别、事务传播行为、事务超时时间、事务是否只读。默认实现类DefaultTransactionDefinition。
    
    • 1
    2.1.3、TransactionStatus
    TransactionStatus表示一个事物的状态。接口提供了控制事务执行和查询事务状态的方法。比如当前调用栈中之前已经存在了一个事物,那么就是通过该接口来判断的,TransactionStatus接口可以让事务管理器控制事务的执行,比如检查事务是否为一个新事务,或者是否只读,TransactionStatus还可以初始化回滚操作。
    接口继承了SavepointManager接口,因此封装了事物中回滚点的相关操作。
    
    • 1
    • 2
    2.1.4、DataSourceTransactionManager(事务管理器)<可以自定义配置>
    负责管理事务行为,包括事务的获取、提交和回滚。
    
    • 1
    2.1.5、TransactionTemplate(事务模板)<可以自定义配置>
    TransactionTemplate简而明之,是事务模板,主要用于编程式事务开发。
    Spring提供的最原始的事务管理方式是基于TransactionDefinition、PlatformTransactionManager、TransactionStatus 编程式事务。
    而TransactionTemplate的编程式事务管理是使用模板方法设计模式对原始事务管理方式的封装。
    具体使用详见2.2.1、编程式事务
    
    • 1
    • 2
    • 3
    • 4
    2.1.6、SqlSession
    SqlSession表示的是数据库客户端和数据库服务端之间的一种会话,并维护了两者之间的状态信息。DefaultSqlSession是它的默认实现类
    
    • 1
    2.1.7、SqlSessionFactory
    SqlSessionFactory也是一个接口,是SqlSession工厂,他的能力就是打开一个SqlSession会话,而且重载了许多不同的参数,你可以改变这些参数自定义会话过程中的一些默认行为。例如:可以设置自动提交事务或是关闭自动提交;可以设置获取数据库连接的线程的类型(重用,每次新产生等等);也可以获取整个Mybatis的配置信息的Configuration对象实例等等。
    SqlSessionFactory默认也有两个实现类,当然你也可以自定义实现类。默认实现是DefaultSqlSessionFactory。总而言之,SqlSessionFactory就是生产SqlSession对象的工厂。
    那也就是说整个Mybatis中,如果只有一个数据库Server要连接,那么只需要一个工厂就够了(只有一个SqlSessionFactory的实例对象),而SqlSession可以自由的被关闭,也就代表SqlSession是需要反复被创建的。
    连接用完关闭SqlSession实例时,只是把数据库连接对象放回到对象池中,并没有直接销毁,使用池技术,大大提高了物力资源利用率,缩短连接时间、减少了资源利用等。
    
    • 1
    • 2
    • 3
    • 4
    2.1.8、SqlSessionTemplate
    SqlSessionTmplate是SqlSession的实现类,而这个实现类中有一个关键的类就是SqlSessionFactory。
    SqlSessionTemplate是SqlSession的实现类,如其名,是sqlSession模板,有了SqlSessionTemplate,我们就能用来执行Dao层的Sql语句。
    即SqlSessionTemplate是SqlSession的一个具体实现。
    
    • 1
    • 2
    • 3
    2.1.9、SqlSessionFactoryBean
    实现了ApplicationListener接口,代表SqlSessionFactoryBean有能力监控 Application发出的一些事件通知。
    实现了FactoryBean接口,代表SqlSessionFactoryBean的实例不再是一个普通的bean对象,而是可以产生自己Bean的一个工厂,并且产生的Bean会被纳入spring的生命周期,这里产生的Bean指的就是SqlSessionFactory。
    即SqlSessionFactoryBean是生产SqlSessionFactory的一种工厂bean。
    
    • 1
    • 2
    • 3

    2.2、使用事务的两种方式

    2.2.1、声明式事务
    在类或者方法上面添加@Transactional注解 比较常用
    
    • 1
    2.2.2、编程式事务
    2.2.2.1、原始方式
    @Resource
    private PlatformTransactionManager transactionManager;
    ---------------------------------------------------------------------------------------------------
    DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
    defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
    TransactionStatus transactionStatus = transactionManager.getTransaction(defaultTransactionDefinition);
    try {
        // 数据库操作
        transactionManager.commit(transactionStatus);
    } catch (Exception e) {
        transactionManager.rollback(transactionStatus);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    2.2.2.2、使用TransactionTemplate
    方法:TransactionTemplate.execute( ... )
    传入的参数有两种选择:
    1、TransactionCallback 有返回值
    2、TransactionCallbackWithoutResult 无返回值
    
    • 1
    • 2
    • 3
    • 4
    1.TransactionCallback(有返回值)
    public Object getObject(String str) {
            /*
             *  执行带有返回值的事务管理
             */
            transactionTemplate.execute(new TransactionCallback<Object>() {
                @Override
                public Object doInTransaction(TransactionStatus transactionStatus) {
                    try {
                        //.......   业务代码
                        return new Object();
                    } catch (Exception e) {
                        //回滚
                        transactionStatus.setRollbackOnly();
                        return null;
                    }
                }
            });
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    2.TransactionCallbackWithoutResult(无返回值)
    public void update(String str) {
             /*
             *  执行无返回值的事务管理
             */
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    try {
                        // ....  业务代码
                    } catch (Exception e){
                        //回滚
                        transactionStatus.setRollbackOnly();
                    }
                }
            });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    三、事务的四大特性

    3.1、原子性

    强调的事务的不可分割。即在事务开启到结束之间,所有的操作要么全部成功,要么一起回滚,这个就是事务的原子性。
    
    • 1

    3.2、隔离性

    强调的事务的并发的访问,一个事务的执行,不应该受到另一个事务的打扰。对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始。而脏读、不可重复读以及幻读产生的原因就是没有考虑到事务的隔离性。
    
    • 1
    3.2.1、隔离性缺失产生的问题(并发事务带来的问题)
    3.2.1.1、脏读
    当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
    
    • 1
    3.2.1.2、不可重复读
     指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
    
    • 1
    3.2.1.3、幻读
    幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
    
    • 1

    注:不可重复度和幻读区别:

    不可重复读的重点是修改,幻读的重点在于新增或者删除。
    
    • 1

    3.3、一致性

     事务必须使数据库从一个一致的状态变到另外一个一致的状态,也就是执行事务之前和之后的状态都必须处于一致的状态。不一致性包含三点:脏读,不可重复读,幻读
    
    • 1

    3.4、持久性

    是事务的保证,事务终结的标志(内存的数据持久到硬盘文件中);即事务结束之后,数据就永久的保存的数据库中。
    
    • 1

    四、事务隔离级别

    4.1、READ_UNCOMMITTED读未提交

    (其他事务还未提交的数据就可以被读取到)

    读读/读写:事务不做任何隔离操作
    写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
    
    • 1
    • 2

    4.2、READ_COMMITTED读已提交

    (其他事务提交或者回滚,它会立即读到)

     读读:事务读的是事务最初的快照 mvcc机制
     读写:读的是快照数据,写的也是快照数据 mvcc机制
     写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
    
    • 1
    • 2
    • 3

    4.3、REPEATABLE_READ可重复读

    (其他事务提交或者回滚,它会立即读到)

     读读:事务读的是事务最初的快照 mvcc机制
     读写:读的是快照数据,写的也是快照数据 mvcc机制
     写写:获取记录的排他锁,不能同时进行,除非一个事务 提交或回滚
    
    • 1
    • 2
    • 3

    4.4、SERIALIZABLE序列化

    读读 :共享锁多个事务可以同时获取
    读写 : 共享锁和排它锁
    写写 : 排它锁和排它锁
    
    • 1
    • 2
    • 3

    五、事务的传播方式(springboot)

    如下图:
    A(){
    	B();
    }
    以上形式的,都可以通过在方法B内部捕获异常来防止事务A的回滚。但是在外部捕获事务B的异常则不一定有用。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    5.1、REQUIRED(required必需的)

    5.1.1、描述
    如果当前没有事务,则自己新建一个事务,如果当前存在事务,则加入这个事务
    
    • 1
    5.1.2、释义
    如下图:
    A(){
    	B();
    }
    方法A里面调用方法B,A方法默认事务传播方式级别为:REQUIRED,基于方法B来释义。
    假设当前方法B为REQUIRED,上述描述如何解释?
    此时,方法A存在事务,简称TA;上面后半句说到“如果当前存在事务,则加入这个事务”,意思是:当前存在事务TA,则方法B由事务TA管理,一旦方法B发生异常,方法A里的其他涉及到数据库的操作也会一同回滚,并且这个回滚无论方法B是否被try...catch捕获异常,即如下格式:
    A(){
    	try{
    		其他涉及数据库的操作
    		B();
    	}catch(Exception e){
    		...
    	}
    }
    这种情况也没有用,A方法里,在B之前修改数据库成功的操作也会回滚。
    注意,如果你是在方法B内部捕获异常则不会回滚,即如下格式:
    A(){
    	其他涉及数据库的操作
    	B();
    }
    B(){
    	try{
    		B方法内部操作
    	}catch(Exception e){
    		...
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    5.2、SUPPORTS(supports支持)

    5.2.1、描述
    当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
    
    • 1
    5.2.2、释义
    “当前存在事务,则加入当前事务”这一句和上面REQUIRED传播方式里处理逻辑方式是一样的。
    如下图:
    A(){
    	B();
    }
    方法A里面调用方法B,A方法默认事务传播方式级别为:REQUIRED,基于方法B来释义。
    假设当前方法B为SUPPORTS,
    此时,方法A存在事务,简称TA;上面说到“当前存在事务,则加入这个事务”,意思是:当前存在事务TA,则方法B由事务TA管理,一旦方法B发生异常,方法A里的其他涉及到数据库的操作也会一同回滚,并且这个回滚无论方法B是否被try...catch捕获异常,即如下格式:
    A(){
    	try{
    		其他涉及数据库的操作
    		B();
    	}catch(Exception e){
    		...
    	}
    }
    这种情况也没有用,A方法里,在B之前修改数据库成功的操作也会回滚。
    注意,如果你是在方法B内部捕获异常则不会回滚,即如下格式:
    A(){
    	其他涉及数据库的操作
    	B();
    }
    B(){
    	try{
    		B方法内部操作
    	}catch(Exception e){
    		...
    	}
    }
    “如果当前没有事务,就以非事务方法执行”又是如何解释的呢?
    什么叫“以非事务方法执行”,就是执行一步操作事务数据库,就立即commit提交(此时数据库里的数据已经被修改)。
    SUPPORTS有点“嫁鸡随鸡嫁狗随狗的味道”,外层方法存在事务就加入这个事务,不存在就直接已非事务方式执行。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    5.3、MANDATORY(mandatory强制性的)

    5.3.1、描述
    当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常
    
    • 1
    5.3.2、释义
    “当前存在事务,则加入当前事务”同REQUIRED和SUPPORTS,不再赘述。
    “如果当前事务不存在,则抛出异常”如字面释义,外层方法如果不存在事务则会抛出异常
    
    • 1
    • 2

    注:以上三种,都是最多只有一个事务,如果在外部使用try…catch捕获,会发生一下异常org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

    5.4、REQUIRES_NEW(requires_new)

    5.4.1、描述
    创建一个新事务,如果存在当前事务,则挂起该事务
    
    • 1
    5.4.2、释义
    @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
    public void testMethod(){
         dosomethingBefore...
         methodA();
         dosomgthingAfter...
    } 
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public void methodA(){
         updateSomething();
    } 
    
    这种级别的传播方式,意味着将会有两个事务。
    此时:
        1.dosomethingBefore发生异常,testMethod直接回滚,methodA执行不到。
        2.methodA方法内部的updateSomething方法发生异常,methodA方法回滚,testMethod方法回滚,导致dosomethingBefore执行的操作无效
        3.methodA方法内部的updateSomething方法发生异常,但是对methodA进行了异常捕获,dosomethingBefore和dosomgthingAfter正常提交,不受影响。以下两种方式都可:
            3.1、外部捕获
                @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
                public void testMethod(){
                     dosomethingBefore...
                    try{
                        methodA();
                    }catch(Exception e){
                        ...
                    }
                    dosomgthingAfter...
                } 
                @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
                public void methodA(){
                    updateSomething();
                } 
            3.2、内部捕获
                @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
                public void testMethod(){
                     dosomethingBefore...
                    methodA();
                    dosomgthingAfter...
                } 
                @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
                public void methodA(){
                    try{
                      updateSomething();
                    }catch(Exception e){
                        ...
                    }
                } 
        4.dosomgthingAfter发生异常,此时methodA方法已经执行完毕,不受影响(因为methodA拥有自己的事务,testMethod事务的回滚,无法影响method方法的事务),dosomethingBefore的操作无效
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    5.5、NOT_SUPPORTED(not_supported)

    5.5.1、描述
    始终以非事务方式执行,如果当前存在事务,则挂起当前事务
    
    • 1
    5.5.2、释义
    @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
    public void testMethod(){
         dosomethingBefore...
         methodA();
         dosomgthingAfter...
    } 
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
    public void methodA(){
         updateSomething1();
         updateSomething2();
    }
    “挂起当前事务”什么意思呢?你可以理解为testMethod方法的执行成功,会受到methodA方法的影响,需要等待methodA方法执行完毕,所以要暂时挂起当前testMethod的事务
    当前传播方式为NOT_SUPPORTED
    此时:
        1.dosomethingBefore发生异常,testMethod直接回滚,methodA执行不到。
        2.dosomgthingAfter发生异常,methodA执行正常,testMethod方法事务回滚,methodA方法不受影响。
        3.methodA方法中 updateSomething1正常,updateSomething2异常,会导致testMethod方法事务回滚,但是updateSomething1方法执行不受影响。
        4.methodA方法中 updateSomething1异常,会导致testMethod方法事务回滚,updateSomething2执行不到。
        5.只要methodA发生异常,都会导致testMethod方法事务回滚,可以通过try...catch捕获methodA方法的异常,来防止testMethod事务回滚,以下两种方式都可:
            5.1、外部捕获
                @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
                public void testMethod(){
                     dosomethingBefore...
                    try{
                        methodA();
                    }catch(Exception e){
                        ...
                    }
                    dosomgthingAfter...
                } 
                @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
                public void methodA(){
                    updateSomething1();
         			updateSomething2();
                } 
            5.2、内部捕获
                @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
                public void testMethod(){
                     dosomethingBefore...
                    methodA();
                    dosomgthingAfter...
                } 
                @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
                public void methodA(){
                    try{
                      updateSomething1();
         			updateSomething2();
                    }catch(Exception e){
                        ...
                    }
                } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    注:以上两种传播方式,外部事务事务无法影响自身的执行结果,但是自己能够影响外部事务

    5.6、NESTED(nested嵌套的)

    5.6.1、描述
    如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样(自己新建一个事务)
    启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交
    
    • 1
    • 2
    5.6.2、释义
    @Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
    public void testMethod(){
         dosomethingBefore...
         methodA();
         dosomgthingAfter...
    } 
    
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    public void methodA(){
         updateSomething();
    } 
    
    这种传播方式,是真正意义上的嵌套事务,methodA的事务是testMethod的子事务;
    此时:
    1.dosomethingBefore发生异常,testMethod直接回滚,methodA执行不到。
    2.当methodA方法发生异常时,testMethod会回滚事务,dosomethingBefore执行无效;可以通过try...catch捕获methodA方法的异常,来防止testMethod事务回滚,以下两种方式都可:
        2.1、外部捕获
        	@Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
    		public void testMethod(){
                 dosomethingBefore...
         		try{
                    methodA();
                }catch(Exception e){
                    ...
                }
         		dosomgthingAfter...
    		} 
    		@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    		public void methodA(){
         		updateSomething();
    		} 
    	2.2、内部捕获
        	@Transactional(rollbackFor = Exception.class)//默认级别REQUIRED
    		public void testMethod(){
                 dosomethingBefore...
         		methodA();
         		dosomgthingAfter...
    		} 
    		@Transactional(rollbackFor = Exception.class,propagation = Propagation.NESTED)
    		public void methodA(){
                try{
                    updateSomething();
                }catch(Exception e){
                    ...
                }
    		} 
    3.dosomgthingAfter发生异常会导致methodA事务回滚
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    5.7、NEVER(never决不)

    5.7.1、描述
     不使用事务,如果当前事务存在,则抛出异常
    
    • 1
    5.7.2、释义
    • 1

    六、事务失效的场景

    6.1、访问权限的问题

    spring要求被代理的方法必须是public的
    
    • 1

    6.2、方法被final修饰

    spring事务底层使用的是aop,也就是哦通过jdk动态代理或者cglib动态代理,如果某个方法使用final修饰后,在代理类中就无法重写。
    
    • 1

    6.3、方法内部调用

    比如在service某个方法中调用了另外一个事务方法,事务会失效。
    
    • 1

    6.4、服务层没有写@Service

    即没有被Spring管理
    
    • 1

    6.5、多线程调用

    因为不同的线程调用的是不同的数据库链接,这个数据库连接是用ThreadLocal保存的, 多个线程会有多个ThreadLocal,所以链接不同了,事务也就失效了
    
    • 1

    6.6、表不支持事务

     比如MyISAM就不支持事务
    
    • 1

    6.7、未使用@Transactional注解

    未使用@Transactional注解
    
    • 1

    七、单体应用多数据源分布式事务配置

    7.1、yml文件配置

    spring: 
      datasource:
        crm:
          url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
          min-idle: 2
          time-between-eviction-runs-millis: 300000
          test-while-idle: true
          max-active: 10
          validation-query: SELECT 1 FROM DUAL
          max-wait: 60000
          max-idle: 2
          test-on-borrow: false
          driverClassName: com.mysql.jdbc.Driver
          min-evictable-idle-time-millis: 1800000
          initial-size: 2
          test-on-return: false
        erp:
          url: jdbc:mysql://127.0.0.1:3306/test1?useSSL=false&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
          username: root
          password: 123456
          type: com.alibaba.druid.pool.DruidDataSource
          min-idle: 2
          time-between-eviction-runs-millis: 300000
          test-while-idle: true
          max-active: 10
          validation-query: SELECT 1 FROM DUAL
          max-wait: 60000
          max-idle: 2
          test-on-borrow: false
          driverClassName: com.mysql.jdbc.Driver
          min-evictable-idle-time-millis: 1800000
          initial-size: 2
          test-on-return: false
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    7.2、项目结构要求

    image-20220517095858113

    7.3、多数据源config文件配置

    
    import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Primary;
    import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.TransactionDefinition;
    import org.springframework.transaction.support.DefaultTransactionDefinition;
    import org.springframework.transaction.support.TransactionTemplate;
    
    import javax.sql.DataSource;
    
    /**
     * erp数据库配置
     *
     * @ClassName ErpDataSourceConfig
     * @Auther: wangjin
     * @Date: 2022/04/25/11:57
     * @Description: erp数据源为默认数据源 erpTransactionManager为默认事务管理器(在DefaultTransactionManagementConfig中设置)
     * @Version 1.0
     */
    @Configuration
    @MapperScan(basePackages = "com.test.task.dao.erp", sqlSessionFactoryRef = "erpSqlSessionFactory", sqlSessionTemplateRef = "erpSqlSessionTemplate")
    public class ErpDataSourceConfig {
        static final String MAPPER_LOCATION = "classpath:mapper/erp/*.xml";
    
        @Value("${spring.datasource.erp.url}")
        private String url;
    
        @Value("${spring.datasource.erp.username}")
        private String username;
    
        @Value("${spring.datasource.erp.password}")
        private String password;
    
    
        /**
         * 数据源
         * 

    @Primary表示默认数据源

    * @return */
    @Primary @Bean(name = "erpDataSource") @ConfigurationProperties("spring.datasource.erp") public DataSource erpDataSource() { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(url); mysqlXaDataSource.setPassword(password); mysqlXaDataSource.setUser(username); //mysqlXaDataSource.set //将本地事务注册到Atomikos全局事务 AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("erpDataSource"); return xaDataSource; } /** * 事务管理器 * @param dataSource * @return */ @Bean(name = "erpTransactionManager") public DataSourceTransactionManager erpTransactionManager(@Qualifier("erpDataSource") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "erpSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("erpDataSource") DataSource dataSource) throws Exception { SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean(); sessionFactoryBean.setDataSource(dataSource); sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(MAPPER_LOCATION)); return sessionFactoryBean.getObject(); } /** * 创建模板 * * @param sqlSessionFactory * @return */ @Bean(name = "erpSqlSessionTemplate") public SqlSessionTemplate erpSqlSessionTemplate(@Qualifier("erpSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } /** * 定义事务模板 * * @param transactionManager 事务管理器 * @return 事务模板 * @throws Exception 异常 */ @Bean(name = "erpTransactionTemplate") public TransactionTemplate erpTransactionTemplate( @Qualifier("erpTransactionManager") DataSourceTransactionManager transactionManager) throws Exception { DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRED); defaultTransactionDefinition.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT); defaultTransactionDefinition.setTimeout(60); // 秒钟 return new TransactionTemplate(transactionManager, defaultTransactionDefinition); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115

    注:另一个数据源类似

    7.4、默认事务管理器配置

    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.annotation.TransactionManagementConfigurer;
    
    import javax.annotation.Resource;
    
    /**
     * 默认事务管理器配置
     *
     * @ClassName DefaultTransactionManagementConfig
     * @Auther: wangjin
     * @Date: 2022/05/10/10:33
     * @Description: 实现接口 TransactionManagementConfigurer 方法,其返回值代表在拥有多个事务管理器的情况下默认使用的事务管理器
     * @Version 1.0
     */
    @Configuration
    public class DefaultTransactionManagementConfig implements TransactionManagementConfigurer {
        @Resource(name = "erpTransactionManager")
        private PlatformTransactionManager txManager;
    
        /**
         * 默认事务管理器
         *
         * @return
         */
        @Override
        public PlatformTransactionManager annotationDrivenTransactionManager() {
            return txManager;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    7.5、分布式事务管理器配置

    7.5.1、jar包引入
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-jta-atomikosartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    7.5.2、配置文件
    
    import com.atomikos.icatch.jta.UserTransactionImp;
    import com.atomikos.icatch.jta.UserTransactionManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.DependsOn;
    import org.springframework.transaction.PlatformTransactionManager;
    import org.springframework.transaction.jta.JtaTransactionManager;
    
    import javax.transaction.TransactionManager;
    import javax.transaction.UserTransaction;
    
    /**
     * 分布式事务配置
     * 

    使用事务时,可以采用以下方式来选择使用不同的事务

    *

    @Transactional(transactionManager ="transactionManager") 分布式事务

    *

    @Transactional(transactionManager "erpTransactionManager") :单独erp数据库 erp事务管理器

    *

    @Transactional(transactionManager "crmTransactionManager"):单独crm数据库 crm事务管理器

    * * @ClassName TransactionManagerConfig * @Auther: wangjin * @Date: 2022/05/10/11:30 * @Description: 分布式事务 * @Version 1.0 */
    @Configuration public class TransactionManagerConfig { @Bean("userTransaction") public UserTransaction userTransaction() throws Throwable { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(10000); return userTransactionImp; } @Bean("atomikosTransactionManager") public TransactionManager atomikosTransactionManager() { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; } /** * 分布式事务管理器 * * @return * @throws Throwable */ @Bean("transactionManager") @DependsOn({"userTransaction", "atomikosTransactionManager"}) public PlatformTransactionManager transactionManager() throws Throwable { return new JtaTransactionManager(userTransaction(), atomikosTransactionManager()); } }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    7.6、启动类注解添加

    @SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
    
    • 1

    image-20220517111417447

    7.7、多数据源的事务管理使用

    如果默认事务管理器不是分布式事务管理器,则在使用分布式管理器时需要指定分布式事务管理器。
    如果存在嵌套事务时,外层事务和内层事务必须为同一个事务管理器,不然会抛异常。且相关事务无法回滚。
    使用:
    @Transactional(rollbackFor = Exception.class,transactionManager = "transactionManager")
    
    示例:
    涉及到不同数据源时,如果两个操作分别需要使用不同的数据源,但是你只用了一个数据源,例如
    T(){
    A操作 TA数据库(对应着事务A事务管理器)
    B操作 TB数据库(对应着事务B事务管理器)此操作有异常
    }
    则A的操作无法回滚,只会回滚B操作
    
    应该在T方法、A方法以及B方法上都添加分布式事务管理器
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    八、大事务场景解决

    大事务是指运行时间比较长,操作的数据比较多的事务。
    这类型的事务容易给数据库带来负担:
    锁定了过多的数据,造成不必要的拥塞堵塞。
    在事务执行的过程中需要堵塞容易引起主从数据同步不一致的情况发生。
    当我们执行事务的操作过大,例如说delete某张表里买呢一亿条数据的时候,如果加入了事务保护,那么假设期间出现了异常,整段事务的回滚将会非常消耗机器的性能和耗时。通常可以让研发将大事务分解为多个小事务进行优化处理。
    在mysql的二进制日志里面,当多个会话同时访问server执行事务性sql语句的请求时候,binlog会给每个会话单独开启一个线程进行事务性sql的缓存处理。直至当相应的sql执行完毕之后再写入到binlog日志中。
    这样做的好处在于能够将不同的事物进行分隔出来处理。并且保证每个写入binlog的事务sql都是完整且正常执行的一个单位。而且如果事务在执行的过程中发生了回滚的话,可以直接在内存中间数据删除,不需要再在日志里面进行记录删除操作。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    写给技术人的管理入门知识1:什么是管理
    《剑指Offer》搜索算法题篇——更易理解的思路~
    SpringBoot集成WebSocket
    【人工智能,机器学习,统计学习,科学表征】开源商用与研发合作
    一文总结 C++ 常量表达式、constexpr 和 const
    Axios 请求响应结果的结构
    DP(动态规划)【2】 最大连续子列和 最长不降子序列
    Java正则表达式 提取文本中所有的匹配数据
    晶体管级数字电路设计专栏目录
    分页存储逻辑地址转物理地址
  • 原文地址:https://blog.csdn.net/qq_39248747/article/details/126877113