好记忆力不如烂笔头97-spring3学习(18)-多线程模式下的spring事务

好记性不如烂笔头97-spring3学习(18)-多线程模式下的spring事务

Spring的事务控制,是比较麻烦的一个地方,也是很容易搞错的一个地方。
如果启动了多线程,事务+ 多线程,问题就更加复杂了
还好spring是一个很强大的框架,它本身对线程安全有一些很好的机制,确保线程安全。但是这种特性,要和spring的事务传播机制区分开
Spring的多线程事务控制,和spring的事务嵌套看起来非常类似,但是他们处理的完全不一样的。

简单的场景和结论:
有两个service。UserService 本身有数据库访问,另外也调用了ChargeServer的数据库操作方法。
都使用了AOP进行事务增强。
一种是在 UserService 直接调用了 ChargeServer 里面的方法,那么spring最终使用的是事务传播模式,两个数据库操作的动作,在一个事务中完成
一种是在UserService 里,启动一个 多线程来调用 ChargeServer 的数据库操作;最终spring使用的是一个线程,使用一个单独的事务。 我这里 UserService 里面有1个数据库操作;启动了两个ChargeServer线程,执行了2个数据库操作;最终,spring一共启动了3个独立的事务

1) 前提和准备
我们需要利用log4j的debug模式来观察,因此我们需要系统能支持log4j运行;
我们需要观察事务,因此我们要访问一个数据库。

create table FFM_USER
(
  USERNAME        VARCHAR2(64),
  PASSWORD        VARCHAR2(64),
  LAST_LOGON_TIME VARCHAR2(64),
  CHARGE          INTEGER
)

里面有一条数据: 在username字段对应“ffm”
2) 业务场景
一个嵌套事务的服务方法。
有两个service。UserService 本身有数据库访问,另外也调用了ChargeServer的数据库操作方法。
都使用了AOP进行事务增强。

在UserService 里,启动一个 多线程来调用 ChargeServer 的数据库操作;最终spring使用的是一个线程,使用一个单独的事务。 我这里 UserService 里面有1个数据库操作;启动了两个ChargeServer线程,执行了2个数据库操作;最终,spring一共启动了3个独立的事务。

3) 实现简单事务传播的源代码
基础父类

package com.spring.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**  
 * 模拟spring事务嵌套调用的父类
 * @author 范芳铭
 */ 
public class BaseService {
    protected static final Logger log = LoggerFactory.getLogger(BaseService.class);
}

多线程之间事务调用

package com.spring.thread;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;

/**  
 * 模拟spring,多线程之间事务调用
 *  
 * @author 范芳铭
 */ 
@Service("userService")
public class UserService extends BaseService {
    @Autowired private JdbcTemplate jdbcTemplate;
    @Autowired private ChargeService chargeService;

    public void logon(String userName){
        System.out.println("--before,本地方法 userService运行");
        updateLastLogonTime(userName);
        System.out.println("--after,本地方法 userService 运行");

        //启用一个多线程执行事务
        Thread chargeThread1 = new ChargeThread(this.chargeService,userName,20,"No1");
        chargeThread1.start();

        Thread chargeThread2 = new ChargeThread(this.chargeService,userName,20,"No2");
        chargeThread2.start();

    }

    public void updateLastLogonTime(String userName){
        String sql = "update ffm_user u set u.last_logon_time = ? where username =? ";
        jdbcTemplate.update(sql, System.currentTimeMillis(),userName);
    }

    private class ChargeThread extends Thread{
        private ChargeService chargeService;
        private String username;
        private int charge;
        private String name;

        private ChargeThread(ChargeService chargeService,String username,int charge,String name){
            this.chargeService = chargeService;
            this.username = username;
            this.charge = charge;
            this.name = name;
        }

        public void run(){
            try{
                Thread.sleep(1000);
            }catch(Exception e ){
                e.printStackTrace();
            }
            System.out.println("-- before, " + name +  ",  chargeService 的方法");
            chargeService.addCharge(username, charge);

            System.out.println("-- after, " + name +  ",  chargeService 的方法");

        }
    }

    //模拟容器运行
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("b_thread.xml");

        UserService service = (UserService)ctx.getBean("userService");
        service.logon("ffm");

    }

}

模拟spring事务嵌套调用的配套方法

package com.spring.thread;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
/**  
 * 模拟spring事务多线程调用的配套方法
 *  
 * @author 范芳铭
 */ 
@Service("chargeService")
public class ChargeService extends BaseService{
    @Autowired private JdbcTemplate jdbcTemplate;

    public void addCharge(String userName,int toAdd){
        String sql = " update ffm_user u set u.charge =nvl(u.charge,0) + ? where username = ? ";
        jdbcTemplate.update(sql,toAdd,userName);
    }

}

4)配置文件b_thread.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"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:task="http://www.springframework.org/schema/task"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/task
        http://www.springframework.org/schema/task/spring-task.xsd">

   <context:component-scan base-package="com.spring.thread"/>    

   <context:property-placeholder location="classpath:dbconfig.properties" />
   <bean id = "dataSource"  class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method = "close"
        p:driverClassName="${jdbc.o2o.driverClassName}"
        p:url="${jdbc.o2o.url}"
        p:username="${jdbc.o2o.username}"
        p:password="${jdbc.o2o.password}"
   />

   <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"    />

   <tx:advice id="jdbcAdvice" transaction-manager="jdbcManager">
    <tx:attributes>
        <tx:method name = "*" />
    </tx:attributes>    
   </tx:advice> 

   <aop:config proxy-target-class="true">
    <aop:pointcut id="serviceJdbcMethod" expression="within(com.spring.thread.BaseService+)" />
    <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="jdbcAdvice" order="0" />
   </aop:config>


</beans>

5)观察和结论
开启DEBUG模式,启动“模拟容器运行”应用。查看日志。
[DEBUG] Creating new transaction with name [com.spring.thread.UserService.logon]

[DEBUG] Creating new transaction with name [com.spring.thread.ChargeService.addCharge]

[DEBUG] Creating new transaction with name [com.spring.thread.ChargeService.addCharge]

三个数据库操作,启动了3个事务。
在这里,每一个单独的线程,启动了一个独立的事务。