Spring JDBC-事务方法嵌套调用解读

简介: Spring JDBC-事务方法嵌套调用解读

Spring事务传播机制回顾


关于Spring事务的一个错误的说法:一个事务方法中不应该调用另外一个事务方法,否则将产生两个事务,其实这是不正确的。


这是因为未正确认识Spring事务传播机制而造成的误解。 Spring对事务控制的支持统一在TransactionDefinition类中描述


20170924134027727.jpg


我们来看下该类中的接口方法

  • int getPropagationBehavior() 事务的传播行为
  • int getIsolationLevel(); 事务的隔离级别
  • int getTimeout();事务的过期时间
  • boolean isReadOnly();事务的读、写特性
  • String getName();事务的名称


除了事务的传播行为外,事务的其他特性Spring是借助底层资源的功能来完成的,Spring无非只充当个代理的角色。但是事务的传播行为却是Spring凭借自身的框架提供的功能。


所谓事务传播的行为,就是多个事务方法相互调用时,事务如何在这些方法间传播。 Spring在TransactionDefinition接口中规定了7种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播:


image.png

当使用PROPAGATION_NESTED时,底层的数据源必须基于JDBC 3.0,并且实现者需要支持保存点事务机制。


Spring默认的事务传播行为是PROPAGATION_REQUESTED, 它适合绝大多数情况。


如果多个ServiceX#methodX() 均工作下在事务环境下(均被Spring事务增强),且程序中存在调用链Service1#method1()—->Service2#method2()——>Service#method3(),那么这3个服务类的3个方法通过Spring的事务传播机制都工作在同一个事务中。


相互嵌套的服务方法


我们来举个例子,TeacherService#doSomething()方法内部调用了 调用本类的udpateTeacherInfo还有StudentService#updateSutdent()方法,这两个类都继承于BaseService,类结构如下:



20170925112236189.jpg


package com.xgj.dao.transaction.nestedCall.service;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xgj.dao.transaction.nestedCall.dao.TeacherDao;
import com.xgj.dao.transaction.nestedCall.domain.Student;
import com.xgj.dao.transaction.nestedCall.domain.Teacher;
/**
 * 
 * 
 * @ClassName: TeacherService
 * 
 * @Description: @Service标注的service层 继承BaseService
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月24日 下午4:56:35
 */
@Service
public class TeacherService extends BaseService {
    Logger logger = Logger.getLogger(TeacherService.class);
    private TeacherDao teacherDao;
    private StudentService studentService;
    @Autowired
    public void setTeacherDao(TeacherDao teacherDao) {
        this.teacherDao = teacherDao;
    }
    @Autowired
    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }
    /**
     * 
     * 
     * @Title: init
     * 
     * @Description: 改方法嵌套调用了本类的其他方法以及其他类的方法
     * 
     * 
     * @return: void
     */
    public void doSomething() {
        logger.info("before TeacherService#udpateTeacherInfo");
        // 调用本类的其他方法
        udpateTeacherInfo(simulateTeacher());
        logger.info("after TeacherService#udpateTeacherInfo");
        // 调用其他类的方法
        logger.info("before StudentService#updateSutdent");
        studentService.updateSutdent(simulateStudent());
        logger.info("after StudentService#updateSutdent");
    }
    public void udpateTeacherInfo(Teacher teacher) {
        teacherDao.updateTeacher(teacher);
    }
    /**
     * 
     * 
     * @Title: simulateTeacher
     * 
     * @Description: 模拟获取一个teacher实例
     * 
     * @return
     * 
     * @return: Teacher
     */
    private Teacher simulateTeacher() {
        Teacher teacher = new Teacher();
        teacher.setName("FTT");
        teacher.setAge(88);
        teacher.setSex("FF");
        teacher.setTeacherId(2);
        return teacher;
    }
    private Student simulateStudent() {
        Student student = new Student();
        student.setName("FSS");
        student.setAge(22);
        student.setSex("MM");
        student.setStudentId(2);
        return student;
    }
}

配置文件:

<?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:p="http://www.springframework.org/schema/p"
    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">
    <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
    <context:component-scan base-package="com.xgj.dao.transaction.nestedCall" />
    <!-- 使用context命名空间,配置数据库的properties文件 -->
    <context:property-placeholder location="classpath:spring/jdbc.properties" />
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close" 
        p:driverClassName="${jdbc.driverClassName}"
        p:url="${jdbc.url}" 
        p:username="${jdbc.username}" 
        p:password="${jdbc.password}" />
    <!-- 配置Jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"
        p:dataSource-ref="dataSource" />
    <!--事务管理器,通过属性引用数据源 -->
    <bean id="jdbcManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource"/>
    <!-- 通过以下配置为继承BaseService的所有子类的所有public方法添加事务增强 -->
    <aop:config  proxy-target-class="true">
        <!-- 切点 -->
        <aop:pointcut  id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.nestedCall.service.BaseService+)"/>
        <!-- 切面 -->
        <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="txAdvice"/>
    </aop:config>
    <!-- 增强,供aop:advisor引用 -->
    <tx:advice id="txAdvice" transaction-manager="jdbcManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
</beans>


通过Spring配置为TeacherService以及StudentService中的所有公共方法都添加Spring AOP的事务增强,让TeacherService的doSomething()和udpateTeacherInfo()以及StudentService的updateSutdent方法都工作在事务环境下。


关键代码:

<aop:pointcut  id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.nestedCall.service.BaseService+)"/>

我们将日志级别调整为DEBUG,运行测试类

package com.xgj.dao.transaction.nestedCall.service;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TeacherServiceTest {
    ClassPathXmlApplicationContext ctx = null;
    TeacherService teacherService = null;
    @Before
    public void initContext() {
        // 启动Spring 容器
        ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/dao/transaction/nestedCall/conf_tx_nestedCall.xml");
        teacherService = ctx.getBean("teacherService", TeacherService.class);
        System.out.println("initContext successfully");
    }
    @Test
    public void testNestedCallInOneTransaction() {
        teacherService.doSomething();
    }
    @After
    public void closeContext() {
        if (ctx != null) {
            ctx.close();
        }
        System.out.println("close context successfully");
    }
}


通过Spring配置为TeacherService以及StudentService中的所有公共方法都添加Spring AOP的事务增强,让TeacherService的doSomething()和udpateTeacherInfo()以及StudentService的updateSutdent方法都工作在事务环境下。


关键代码:

<aop:pointcut  id="serviceJdbcMethod" expression="within(com.xgj.dao.transaction.nestedCall.service.BaseService+)"/>

我们将日志级别调整为DEBUG,运行测试类

package com.xgj.dao.transaction.nestedCall.service;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TeacherServiceTest {
    ClassPathXmlApplicationContext ctx = null;
    TeacherService teacherService = null;
    @Before
    public void initContext() {
        // 启动Spring 容器
        ctx = new ClassPathXmlApplicationContext(
                "classpath:com/xgj/dao/transaction/nestedCall/conf_tx_nestedCall.xml");
        teacherService = ctx.getBean("teacherService", TeacherService.class);
        System.out.println("initContext successfully");
    }
    @Test
    public void testNestedCallInOneTransaction() {
        teacherService.doSomething();
    }
    @After
    public void closeContext() {
        if (ctx != null) {
            ctx.close();
        }
        System.out.println("close context successfully");
    }
}

观察日志:

2017-09-24 18:36:23,428 DEBUG [main] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.nestedCall.service.TeacherService.doSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-24 18:36:23,777 DEBUG [main] (DataSourceTransactionManager.java:248) - Acquired Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] for JDBC transaction
2017-09-24 18:36:23,781 DEBUG [main] (DataSourceTransactionManager.java:265) - Switching JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] to manual commit
2017-09-24 18:36:23,820  INFO [main] (TeacherService.java:52) - before TeacherService#udpateTeacherInfo
2017-09-24 18:36:23,824 DEBUG [main] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-24 18:36:23,825 DEBUG [main] (JdbcTemplate.java:616) - Executing prepared SQL statement [update teacher set  name = ? ,age = ? ,sex = ?  where id = ?]
2017-09-24 18:36:23,974 DEBUG [main] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-24 18:36:23,978  INFO [main] (TeacherDaoImpl.java:64) - updateTeacher successfully
2017-09-24 18:36:23,978  INFO [main] (TeacherService.java:55) - after TeacherService#udpateTeacherInfo
2017-09-24 18:36:23,978  INFO [main] (TeacherService.java:58) - before StudentService#updateSutdent
2017-09-24 18:36:23,978 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
2017-09-24 18:36:24,004 DEBUG [main] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-24 18:36:24,005 DEBUG [main] (JdbcTemplate.java:616) - Executing prepared SQL statement [update student set  name = ? ,age = ? ,sex = ?  where id = ?]
2017-09-24 18:36:24,007 DEBUG [main] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-24 18:36:24,007  INFO [main] (StudentDaoImpl.java:82) - updateStudent  successfully
2017-09-24 18:36:24,008  INFO [main] (TeacherService.java:60) - after StudentService#updateSutdent
2017-09-24 18:36:24,008 DEBUG [main] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-24 18:36:24,008 DEBUG [main] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]



Creating new transaction with name [com.xgj.dao.transaction.nestedCall.service.TeacherService.doSomething]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT


为TeacherService#doSomething开启一个事务, 然后直接执行udpateTeacherInfo方法,由于doSomething和udpateTeacherInfo在一个类中,没有观察到有事务传播行为的发生,


然而当执行到updateSutdent方法时,我们观察到一个事务传播行为: Participating in existing transaction ,这说明 StudentService#updateSutdent方法添加到了TeacherService#doSomething()方法的事务上下文中,二者共享同一个事务。 所以最终的结果是TeacherService的doSomething 和 udpateTeacherInfo 以及 StudentService#updateSutdent()方法工作在同一个事务中。


最后 Initiating transaction commit—-提交事务


源码


代码已托管到Github—> https://github.com/yangshangwei/SpringMaster


相关文章
|
1月前
|
监控 Java 数据处理
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
【Spring云原生】Spring Batch:海量数据高并发任务处理!数据处理纵享新丝滑!事务管理机制+并行处理+实例应用讲解
|
1月前
|
缓存 Java API
【云原生】Spring Cloud Gateway的底层原理与实践方法探究
【云原生】Spring Cloud Gateway的底层原理与实践方法探究
|
1月前
|
存储 NoSQL Java
Spring Boot统计一个Bean中方法的调用次数
Spring Boot统计一个Bean中方法的调用次数
35 1
|
1月前
|
Java 数据库 Spring
Spring事务失效的场景详解
Spring事务失效的场景详解
33 0
|
1月前
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
36 0
|
1月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
使用JDBCTemplate实现与Spring结合,方法公用 ——测试(EmpDaoImplTest)
9 0
|
2月前
|
Java 关系型数据库 数据库连接
Spring源码解析--深入Spring事务原理
本文将带领大家领略Spring事务的风采,Spring事务是我们在日常开发中经常会遇到的,也是各种大小面试中的高频题,希望通过本文,能让大家对Spring事务有个深入的了解,无论开发还是面试,都不会让Spring事务成为拦路虎。
35 1
|
1月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——Emp实现类(EmpDaoImpl)
使用JDBCTemplate实现与Spring结合,方法公用 ——Emp实现类(EmpDaoImpl)
8 0
|
1月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——共用实现类(BaseImpl)
使用JDBCTemplate实现与Spring结合,方法公用 ——共用实现类(BaseImpl)
10 1
|
1月前
|
Java Spring
使用JDBCTemplate实现与Spring结合,方法公用 ——接口(BaseDao)
使用JDBCTemplate实现与Spring结合,方法公用 ——接口(BaseDao)
9 0