Spring事務深入剖析--spring事務失效的原因


 

 之前我們講的分布式事務的調用都是在一個service中的事務方法,去調用另外一個service中的業務方法,

如果在一個sevice中存在兩個分布式事務方法,在一個seivice中兩個事務方法相互嵌套調用,對分布式事務有啥影響了

現在TestSevice中存在兩個事務方法,funcA和FunctionB

現在有下面這樣的一個需求

我們來看下具體的業務代碼

package com.atguigu.spring.tx.xml.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.atguigu.spring.tx.BookShopService;
import com.atguigu.spring.tx.xml.BookShopDao;
import com.atguigu.spring.tx.xml.service.TestService;

@Service
public class TestServiceInpl implements TestService {

@Autowired
private BookShopDao bookShopDao;
    
    public void setBookShopDao(BookShopDao bookShopDao) {
        this.bookShopDao = bookShopDao;
    }
    

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void funB(){
        
        bookShopDao.updateUserAccount("AA", 300);
        throw new RuntimeException("funB is throw new RuntimeException ");
    }

    @Override
    @Transactional
    public void purchase(String username, String isbn) {
        // TODO Auto-generated method stub
        //想調用funbB方法
        try{
            funB();
        }catch(Exception e){
            System.out.println(e.getMessage().toString());
        }

        
        bookShopDao.updateUserAccount("AA", 100);
    }
}

 

purchase方法中使用了 @Transactional,然后在執行purchase的真正的業務方法執行調用了同一個類中的funB方法,funB方法使用了@Transactional(propagation=Propagation.REQUIRES_NEW)的注解
在funB方法中拋出了異常,在purchase方法中使用了try catch對異常進行捕獲
在外買的方法中,我們編寫一個測試類,調用purchase方法
applicationContext.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: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/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    
    <context:component-scan base-package="com.atguigu.spring"></context:component-scan>
    
    <!-- 導入資源文件 -->
    <context:property-placeholder location="classpath:db.properties"/>
    
    <!-- 配置 C3P0 數據源 -->
    <bean id="dataSource"
        class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>

        <property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
        <property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
    </bean>
    
    <!-- 配置 Spirng 的 JdbcTemplate -->
    <bean id="jdbcTemplate" 
        class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 配置 NamedParameterJdbcTemplate, 該對象可以使用具名參數, 其沒有無參數的構造器, 所以必須為其構造器指定參數 -->
    <bean id="namedParameterJdbcTemplate"
        class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>    
    </bean> 
    
    <!-- 配置事務管理器 -->
    <bean id="transactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    
    <!-- 啟用事務注解 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
    
</beans>
package com.atguigu.spring.tx.xml.service;

public interface TestService {
    
    public void purchase(String username, String isbn);
    
}
package com.atguigu.spring.tx.xml;

public interface BookShopDao {

    //�����Ż�ȡ��ĵ���
    public int findBookPriceByIsbn(String isbn);
    
    //������Ŀ��. ʹ��Ŷ�Ӧ�Ŀ�� - 1
    public void updateBookStock(String isbn);
    
    //�����û����˻����: ʹ username �� balance - price
    public void updateUserAccount(String username, int price);
}
package com.atguigu.spring.tx.xml;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

@Component
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT price FROM book WHERE isbn = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //�����Ŀ���Ƿ��㹻, ������, ���׳��쳣
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if(stock == 0){
            throw new BookStockException("��治��!");
        }
        
        String sql = "UPDATE book_stock SET stock = stock -1 WHERE isbn = ?";
        jdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //��֤����Ƿ��㹻, ������, ���׳��쳣
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
        System.out.println("balance="+balance);
        
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        jdbcTemplate.update(sql, price, username);
    }

}

 

我們編寫一個測試類,來編譯pruchase方法,我們來看下運行結果
package com.atguigu.spring.tx.xml;

import java.util.Arrays;

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

import com.atguigu.spring.tx.xml.service.BookShopService;
import com.atguigu.spring.tx.xml.service.Cashier;
import com.atguigu.spring.tx.xml.service.TestService;
import com.atguigu.spring.tx.xml.service.impl.TestServiceInpl;

public class CopyOfSpringTransactionTest2222 {

    private ApplicationContext ctx = null;
    private TestService testService = null;
    private Cashier cashier = null;
    
    {
        ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        testService = ctx.getBean(TestService.class);
    }
    

    @Test
    public void testBookShopService(){
        testService.purchase("AA", "1001");
    }
    
}

我們需要通過日志的信息來讓spring框架默認底層幫助我們做了啥,spring框架默認使用commons-login框架

在閱讀spring、springmvc源碼的時候 會看到其中有很多代碼中輸出了日志信息  有時候這些信息對我們閱讀源碼、分析問題的時候有很大的作用,但是我們控制台並沒有看到。那如何使這些日志信息顯示出來呢?

解決:在pom.xml中加入 log4j 和commons-logging的依賴 然后在resources也就是src目錄下下添加log4j.properties文件
---------------------

log4j.properties

log4j.rootLogger=DEBUG, Console  
  
#Console  
log4j.appender.Console=org.apache.log4j.ConsoleAppender  
log4j.appender.Console.layout=org.apache.log4j.PatternLayout  
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n  
  
log4j.logger.java.sql.ResultSet=INFO  
log4j.logger.org.apache=INFO  
log4j.logger.java.sql.Connection=DEBUG  
log4j.logger.java.sql.Statement=DEBUG  
log4j.logger.java.sql.PreparedStatement=DEBUG  

這樣在項目啟動的時候就可以打印spring框架的日志了

 我們通過日志信息來查看事務的執行情況

Initializing transaction synchronization
 Getting transaction for com.atguigu.spring.tx.xml.service.impl.TestServiceInpl.purchase]
=======purchase start======
 =======funB start======
 Executing prepared SQL query
l.TestServiceInpl] - =======purchase end======
 Triggering beforeCommit synchronization
Initiating transaction commit
Committing JDBC transaction on Connection [co

 

 通過日志我們可以看出,在調用purchase方法的時候開啟了一個新的事情,然后調用purchase方法,在調用funB方法的時候,並沒有開啟一個新的事務,而是直接使用之前創建的

事務 @Transactional(propagation=Propagation.REQUIRES_NEW)這個事務實現了,上面調用funB的方法可以簡寫成下面的形式

@Override
    @Transactional
    public void purchase(String username, String isbn) {
        // TODO Auto-generated method stub
        log.debug("=======purchase start======");
        //想調用funbB方法
        try{
            log.debug("=======funB start======");
            bookShopDao.updateUserAccount("AA", 300);
            throw new RuntimeException("funB is throw new RuntimeException ");
        }catch(Exception e){
            System.out.println(e.getMessage().toString());
        }

        
        bookShopDao.updateUserAccount("AA", 100);
        log.debug("=======purchase end======");
    }

 

所以執行的時候:
 bookShopDao.updateUserAccount("AA", 300);
 bookShopDao.updateUserAccount("AA", 100);
都會提交到數據庫中,bookShopDao.updateUserAccount("AA", 300)不會因為拋出異常而回滾因為異常被try
catch出來掉了,所以看日志信息是一種很正常的定位問題的一種好的習慣
上面為啥@Transactional(propagation=Propagation.REQUIRES_NEW)這個事務會失效了,接下來我們進行講解
我們再講事務失效之前,我們在來看這樣的一種常見
@Override
    @Transactional
    public void purchase(String username, String isbn) {
        // TODO Auto-generated method stub
        log.debug("=======purchase start======");
        //想調用funbB方法
        try{
            log.debug("=======funB start======");
            bookShopDao.updateUserAccount("AA", 300);
            throw new RuntimeException("funB is throw new RuntimeException ");
        }catch(Exception e){
            System.out.println(e.getMessage().toString());
//把異常跑出去
throw e; } bookShopDao.updateUserAccount(
"AA", 100); log.debug("=======purchase end======"); }

 

上面我們把異常跑出去,異常會被spring事務的aop框架攔截到異常,對異常進行處理,導致整個purchase方法中的數據庫操作都會回滾

bookShopDao.updateUserAccount("AA", 300);
bookShopDao.updateUserAccount("AA", 100);

都會失敗

接下來我們講解@Transactional(propagation=Propagation.REQUIRES_NEW)這個事務為啥會失效了

根本的原因在於jdk的動態代理導致的事務的失效

我們先編寫一個動態代理

package com.atguigu.spring.tx.xml.service.impl;

public interface DemoService {
    
    public void test();
    public void test1();
    

}
package com.atguigu.spring.tx.xml.service.impl;

public class DemoServiceImpl implements DemoService {

    @Override
    public void test() {
        // TODO Auto-generated method stub
     System.out.println("test is called");
    }

    @Override
    public void test1() {
        // TODO Auto-generated method stub
        System.out.println("test1 is called");
    }

}

 

接下來我們通過jdk的動態代理的方式來生成一個DemoServiceImpl 對象
 
         
package com.atguigu.spring.tx.xml.service.impl;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

public class MyHandler implements InvocationHandler {

    //目標對象
    private Object target;
    
    
    public MyHandler(Object demoService) {
        this.target = demoService;
    }


    @Override
    public Object invoke(Object arg0, Method method, Object[] args)
            throws Throwable {
        // 做另外的業務處理
        if(method.getName().contains("test")){
            System.out.println("====加入了其他處理===");
        }
        return method.invoke(target, args);
    }

    
    public  static void main(String[] args){
        MyHandler myHandler = new MyHandler(new DemoServiceImpl());
        DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);
        proxyInstance.test();
        proxyInstance.test1();
        
    }
}
 
         

 

 

 

程序運行的輸出結果為

====加入了其他處理===
test is called
====加入了其他處理===
test1 is called

 

現在上面中purahase調用了funB方法,等價於在test方法中調用了test1方法
package com.atguigu.spring.tx.xml.service.impl;

public class DemoServiceImpl implements DemoService {

    @Override
    public void test() {
        // TODO Auto-generated method stub
        this.test1();
     System.out.println("test is called");
    }

    @Override
    public void test1() {
        // TODO Auto-generated method stub
        System.out.println("test1 is called");
    }

}

 

我們運行看下日志的打印
package com.atguigu.spring.tx.xml.service.impl;

import java.lang.reflect.Method;

import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.cglib.proxy.Proxy;

public class MyHandler implements InvocationHandler {

    //目標對象
    private Object target;
    
    
    public MyHandler(Object demoService) {
        this.target = demoService;
    }


    @Override
    public Object invoke(Object arg0, Method method, Object[] args)
            throws Throwable {
        // 做另外的業務處理
        if(method.getName().contains("test")){
            System.out.println("====加入了其他處理===");
        }
        return method.invoke(target, args);
    }

    
    public  static void main(String[] args){
        MyHandler myHandler = new MyHandler(new DemoServiceImpl());
        DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);
        proxyInstance.test();
        
    }
}

====加入了其他處理===
test1 is called
test is called

通過日志我們可以看出,在執行test方法的時候被jdk代理了,執行this.test1()方法的時候沒有被jdk的動態代理所攔截

所以打印日志僅僅輸出 了一條test1 is called

這里我們要一定主要下面的兩點:

1、test()方法是被jdk動態產生的代理對象所調用的;所以打印出來了====加入了其他處理===日志信息

2、test1()方法不是被jdk的動態代理對象調用的,而是被真實的new DemoServiceImpl()出來的對象所調用的

上面我們加日志信息進行調試,可以看出來

DemoService proxyInstance = (DemoService) Proxy.newProxyInstance(MyHandler.class.getClassLoader(), new Class[]{DemoService.class}, myHandler);
proxyInstance.test();

proxyInstance就是代理對象調用了test方法

我們在test方法中加入日志信息。看當前的對象是

package com.atguigu.spring.tx.xml.service.impl;

public class DemoServiceImpl implements DemoService {

    @Override
    public void test() {
        // TODO Auto-generated method stub
        //打印出當前對象的信息
        System.out.println(this.getClass().getName()); this.test1();
     System.out.println("test is called");
    }

    @Override
    public void test1() {
        // TODO Auto-generated method stub
        System.out.println("test1 is called");
    }

}

 

打印的日志為:

====加入了其他處理===
com.atguigu.spring.tx.xml.service.impl.DemoServiceImpl
test1 is called
test is called

可以看出調用test1方法的是當前的this對象,就是com.atguigu.spring.tx.xml.service.impl.DemoServiceImpl,就是而是被真實的new DemoServiceImpl()出來的對象所調用的

沒有被真實的代理對象調用,導致test1上面的注解的事務失效

spring的事務是通過jdbc的動態代理實現的,只有被代理對象調用的方法上面的事務才有效,只有被代理對象直接調用的方法上面的事務才有效果

現在如果要讓上面的funB的事務生效,如果是代理對象來proxy對象直接調用funB方法就可以讓funB的事務生效

 

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM