Spring JDBC-事务管理中的多线程问题

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Spring JDBC-事务管理中的多线程问题

概述


众所周知,Spring 的事务管理器是通过线程相关的 ThreadLocal 来保存数据访问基础设施,再结合 IOC 和 AOP 实现高级声明式事务的功能,所以 Spring 的事务天然地和线程有着千丝万缕的联系。


我们知道 Web 容器本身就是多线程的,Web 容器为一个 Http 请求创建一个独立的线程,所以由此请求所牵涉到的 Spring 容器中的 Bean 也是运行于多线程的环境下。在绝大多数情况下,Spring 的 Bean 都是单实例的(singleton),单实例 Bean 的最大的好处是线程无关性,不存在多线程并发访问的问题,也即是线程安全的。


一个类能够以单实例的方式运行的前提是“无状态”:即一个类不能拥有状态化的成员变量。在传统的编程中,DAO 必须执有一个 Connection,而 Connection 即是状态化的对象。所以传统的 DAO 不能做成单实例的,每次要用时都必须 new 一个新的实例。传统的 Service 由于将有状态的 DAO 作为成员变量,所以传统的 Service 本身也是有状态的。


但是在 Spring 中,DAO 和 Service 都以单实例的方式存在。Spring 是通过 ThreadLocal 将有状态的变量(如 Connection 等)本地线程化,达到另一个层面上的“线程无关”,从而实现线程安全。Spring 不遗余力地将状态化的对象无状态化,就是要达到单实例化 Bean 的目的。


由于 Spring 已经通过 ThreadLocal 将 Bean 无状态化,所以 Spring 中单实例 Bean 对线程安全问题拥有了一种天生的免疫能力。不但单实例的 Service 可以成功运行于多线程环境中,Service 本身还可以自由地启动独立线程以执行其它的 Service。


示例:启动独立线程调用事务方法


我们在MulitThreadService.java 在事务方法中启动独立线程运行另一个事务方法

配置文件

<?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.multiThreadinTrans" />
    <!-- 使用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" />
    <!--基于数据源的事务管理器,通过属性引用数据源  可以使用dataSource-ref引用不同的数据源,我们这里只展示同一个-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="dataSource"/>
    <aop:config proxy-target-class="true">
        <!-- 切点 -->
        <aop:pointcut id="serviceMethod" 
                      expression="within(com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadBaseService+)" />
        <!-- 切面 -->
        <aop:advisor  pointcut-ref="serviceMethod"  
                      advice-ref="txAdvice"
                      order="0" />
    </aop:config>
    <!-- 事务增强 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*"/>
        </tx:attributes>    
    </tx:advice>
</beans>


验证代码

package com.xgj.dao.transaction.multiThreadinTrans.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.xgj.dao.transaction.multiThreadinTrans.domain.Student;
/**
 * 
 * 
 * @ClassName: MulitThreadService
 * 
 * @Description: 继承抽象基类MulitThreadBaseService
 * 
 * @author: Mr.Yang
 * 
 * @date: 2017年9月26日 下午4:44:18
 */
@Service
public class MulitThreadService extends MulitThreadBaseService {
    private AnotherService anotherService;
    private StudentService studentService;
    @Autowired
    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }
    @Autowired
    public void setAnotherService(AnotherService anotherService) {
        this.anotherService = anotherService;
    }
    /**
     * 
     * 
     * @Title: executeLogic
     * 
     * @Description: (1)在executeLogic方法体中启动一个独立的线程,
     *               在该独立的线程中执行AnotherService#doAnotherThing ()方法
     * 
     * 
     * @return: void
     */
    public void executeLogic() {
        System.out.println("logon method...");
        // 调用本类的其他方法
        method1();
        // 调用其他类的方法,在同一个线程中调用anotherService#doAnotherThing(),将运行在同一个事务中
        anotherService.doAnotherThing();
        // 在一个新的线程中调用anotherService#doAnotherThing(),将启动一个新的事务
        // 注意: 这里需要使用 extend Thread的方式 ,通过implements Runnable ,经验证不会开启新的事务
        new MyThread().start();
        // 必须休眠几秒,否则执行不到 新线程中的 方法
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // new MyThread1().run(); 这个会加到存在的事务中,原因暂时未知
    }
    public void method1() {
        System.out.println("method1 begins");
        System.out.println("模拟执行jdbc操作");
        System.out.println("method1 finish");
    }
    /**
     * 
     * 
     * @ClassName: MyThread
     * 
     * @Description: 负责执行AnotherService#doAnotherThing()的线程
     * 
     * @author: Mr.Yang
     * 
     * @date: 2017年9月26日 下午4:50:29
     */
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("before studentService.updateSutdent method..."
                    + anotherService.getClass().getName());
            // anotherService.doAnotherThing();
            Student student = new Student();
            student.setName("1XXX");
            student.setAge(112);
            student.setSex("1B");
            student.setStudentId(1);
            studentService.updateSutdent(student);
            System.out.println("after studentService.updateSutdent method...");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
    class MyThread1 implements Runnable {
        @Override
        public void run() {
            System.out.println("before studentService.updateSutdent method..."
                    + anotherService.getClass().getName());
            // anotherService.doAnotherThing();
            Student student = new Student();
            student.setName("XXX");
            student.setAge(12);
            student.setSex("B");
            student.setStudentId(1);
            studentService.updateSutdent(student);
            System.out.println("after studentService.updateSutdent method..."
                    + anotherService.getClass().getName());
        }
    }
}


单元测试

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

关键日志分析

2017-09-26 18:17:25,890 DEBUG [main] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadService.executeLogic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-26 18:17:26,243 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-26 18:17:26,248 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
logon method...
method1 begins
模拟执行jdbc操作
method1 finish
2017-09-26 18:17:26,291 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
AnotherService#doAnotherThing executed
before studentService.updateSutdent method...com.xgj.dao.transaction.multiThreadinTrans.service.AnotherService$$EnhancerBySpringCGLIB$$1f0e2630
2017-09-26 18:17:26,311 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.StudentService.updateSutdent]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2017-09-26 18:17:26,375 DEBUG [Thread-1] (DataSourceTransactionManager.java:248) - Acquired Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] for JDBC transaction
2017-09-26 18:17:26,375 DEBUG [Thread-1] (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-26 18:17:26,393 DEBUG [Thread-1] (JdbcTemplate.java:869) - Executing prepared SQL update
2017-09-26 18:17:26,394 DEBUG [Thread-1] (JdbcTemplate.java:616) - Executing prepared SQL statement [update student set  name = ? ,age = ? ,sex = ?  where id = ?]
2017-09-26 18:17:26,535 DEBUG [Thread-1] (JdbcTemplate.java:879) - SQL update affected 1 rows
2017-09-26 18:17:26,541  INFO [Thread-1] (StudentDaoImpl.java:80) - updateStudent  successfully
2017-09-26 18:17:26,542 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-26 18:17:26,542 DEBUG [Thread-1] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]
2017-09-26 18:17:26,552 DEBUG [Thread-1] (DataSourceTransactionManager.java:368) - Releasing JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] after transaction
2017-09-26 18:17:26,552 DEBUG [Thread-1] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource
after studentService.updateSutdent method...
2017-09-26 18:17:31,311 DEBUG [main] (AbstractPlatformTransactionManager.java:759) - Initiating transaction commit
2017-09-26 18:17:31,311 DEBUG [main] (DataSourceTransactionManager.java:310) - Committing JDBC transaction on Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver]
2017-09-26 18:17:31,313 DEBUG [main] (DataSourceTransactionManager.java:368) - Releasing JDBC Connection [jdbc:oracle:thin:@172.25.246.11:1521:testbed, UserName=CC, Oracle JDBC driver] after transaction
2017-09-26 18:17:31,313 DEBUG [main] (DataSourceUtils.java:327) - Returning JDBC Connection to DataSource

日志分析

Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.MulitThreadService.executeLogic]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT


执行executeLogic前,Spring开启了一个新的事务,

然后执行了

logon method...
method1 begins
模拟执行jdbc操作
method1 finish


我们在代码中调用

    // 调用其他类的方法,在同一个线程中调用anotherService#doAnotherThing(),将运行在同一个事务中
        anotherService.doAnotherThing();


日志显示,加入了现有的这个事务中

2017-09-26 18:17:26,291 DEBUG [main] (AbstractPlatformTransactionManager.java:476) - Participating in existing transaction
AnotherService#doAnotherThing executed
before studentService.updateSutdent method...com.xgj.dao.transaction.multiThre


然后 Initiating transaction commit 提交了当前的事务

紧接着,我们开启了一个新的线程

new MyThread().start();


日志显示,Spring开启了一个新的线程

before studentService.updateSutdent method...com.xgj.dao.transaction.multiThreadinTrans.service.AnotherService$$EnhancerBySpringCGLIB$$1f0e2630
2017-09-26 18:17:26,311 DEBUG [Thread-1] (AbstractPlatformTransactionManager.java:367) - Creating new transaction with name [com.xgj.dao.transaction.multiThreadinTrans.service.StudentService.updateSutdent]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT



执行结束后,提交新开启的这个事务

Initiating transaction commit



结论


相同线程中进行相互嵌套调用的事务方法工作于相同的事务中。如果这些相互嵌套调用的方法工作在不同的线程中,不同线程下的事务方法工作在独立的事务中。


示例源码


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


相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
XML Java 数据库连接
Spring高手之路25——深入解析事务管理的切面本质
本篇文章将带你深入解析Spring事务管理的切面本质,通过AOP手动实现 @Transactional 基本功能,并探讨PlatformTransactionManager的设计和事务拦截器TransactionInterceptor的工作原理,结合时序图详细展示事务管理流程,最后引导分析 @Transactional 的代理机制源码,帮助你全面掌握Spring事务管理。
46 2
Spring高手之路25——深入解析事务管理的切面本质
|
1月前
|
安全 Java 开发者
Spring容器中的bean是线程安全的吗?
Spring容器中的bean默认为单例模式,多线程环境下若操作共享成员变量,易引发线程安全问题。Spring未对单例bean做线程安全处理,需开发者自行解决。通常,Spring bean(如Controller、Service、Dao)无状态变化,故多为线程安全。若涉及线程安全问题,可通过编码或设置bean作用域为prototype解决。
43 1
|
4月前
|
Java Spring
spring多线程实现+合理设置最大线程数和核心线程数
本文介绍了手动设置线程池时的最大线程数和核心线程数配置方法,建议根据CPU核数及程序类型(CPU密集型或IO密集型)来合理设定。对于IO密集型,核心线程数设为CPU核数的两倍;CPU密集型则设为CPU核数加一。此外,还讨论了`maxPoolSize`、`keepAliveTime`、`allowCoreThreadTimeout`和`queueCapacity`等参数的设置策略,以确保线程池高效稳定运行。
488 10
spring多线程实现+合理设置最大线程数和核心线程数
|
3月前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
【10月更文挑战第1天】Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
163 9
|
4月前
|
Java 数据库连接 数据库
Spring基础3——AOP,事务管理
AOP简介、入门案例、工作流程、切入点表达式、环绕通知、通知获取参数或返回值或异常、事务管理
|
5月前
|
XML Java 数据库
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
这篇文章是Spring5框架的实战教程,详细介绍了事务的概念、ACID特性、事务操作的场景,并通过实际的银行转账示例,演示了Spring框架中声明式事务管理的实现,包括使用注解和XML配置两种方式,以及如何配置事务参数来控制事务的行为。
Spring5入门到实战------15、事务操作---概念--场景---声明式事务管理---事务参数--注解方式---xml方式
|
5月前
|
Java Spring 开发者
掌握Spring事务管理,打造无缝数据交互——实用技巧大公开!
【8月更文挑战第31天】在企业应用开发中,确保数据一致性和完整性至关重要。Spring框架提供了强大的事务管理机制,包括`@Transactional`注解和编程式事务管理,简化了事务处理。本文深入探讨Spring事务管理的基础知识与高级技巧,涵盖隔离级别、传播行为、超时时间等设置,并介绍如何使用`TransactionTemplate`和`PlatformTransactionManager`进行编程式事务管理。通过合理设计事务范围和选择合适的隔离级别,可以显著提高应用的稳定性和性能。掌握这些技巧,有助于开发者更好地应对复杂业务需求,提升应用质量和可靠性。
61 0
|
6月前
|
Java Spring
spring boot 中默认最大线程连接数,线程池数配置查看
spring boot 中默认最大线程连接数,线程池数配置查看
417 4
|
6月前
|
Java Spring 容器
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
Spring boot 自定义ThreadPoolTaskExecutor 线程池并进行异步操作
322 3
|
5月前
|
安全 Java C#
Spring创建的单例对象,存在线程安全问题吗?
Spring框架提供了多种Bean作用域,包括单例(Singleton)、原型(Prototype)、请求(Request)、会话(Session)、全局会话(GlobalSession)等。单例是默认作用域,保证每个Spring容器中只有一个Bean实例;原型作用域则每次请求都会创建一个新的Bean实例;请求和会话作用域分别与HTTP请求和会话绑定,在Web应用中有效。 单例Bean在多线程环境中可能面临线程安全问题,Spring容器虽然确保Bean的创建过程是线程安全的,但Bean的使用安全性需开发者自行保证。保持Bean无状态是最简单的线程安全策略;