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

2023-02-14,,,,

之前我们讲的分布式事务的调用都是在一个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的事务生效

 

Spring事务深入剖析--spring事务失效的原因的相关教程结束。

《Spring事务深入剖析--spring事务失效的原因.doc》

下载本文的Word格式文档,以方便收藏与打印。