Spring学习之事务Transaction的使用

零、事务管理器运行过程

通过两个事务方法来说明一下:

service1方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
  public void m1(){
    this.jdbcTemplate.update("insert into user1(username) VALUES (?)","aa");
    service2.m2();
    }
service2方法:
@Transactional(transactionManager="transactionManager1",propagation = Propagation.REQUIRED)
  public void m2(){
    this.jdbcTemplate.update("insert into user1(username) VALUES (?)","ss");}

spring事务中有个resources的ThreadLocal,static修饰的,用来存放共享的资源

private static final ThreadLocal<Map<Object,Object>> resources = new NamedThreadLocal<>("Transactional resources");

下面具体看一下简化版的事务过程:

1.TransactionInterceptor拦截m1方法
2.获取m1方法的事务配置信息:事务管理器名称和事务传播行为
3.从spring容器中找到事务管理器,然后判断当前上下文有没有事务,这时候显然没有
4.创建一个事务
  //获取当前事务管理器的数据源
  DataSource datasource1 = transactionManager1.getDataSource();
  //获取当前数据源的连接
  Connection conn = datasource1.getConnection();
  //设置事务手动提交,开启事务
  conn.setAutoCommit(false)
  //将datasource1和conn存入map中
  map.put(datasource1,conn)
  //将map存入static的ThrealLocal中
  resources.set(map);
5.执行this.jdbcTemplate.update();
6.jdbcTemplate内部需要获取连接,过程如下:
  //先从上面的resources中获取map
  Map map = resources.get();
  //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(jdbaTemplate.getDatasources());
  if(conn == null){
   //如果没有找到连接,就重新获取一个;
  	conn = jdbcTemplate.datasource.getConnection();
  }
7.执行db操作,进行插入
8.执行service2.m2()
9.m2方法上也有@Transactional, TransactionInterceptor拦截m2方法
10.同样获m2上的事务配置信息:事务管理器和传播行为
11.从spring中获取到事务管理器,transactionManager1和required,
   然后判断当前上下文有没有事务,发现当前是有事务的,m1的事务正在进行,所以m2就加入了
12.执行this.jdbcTemplate.update();
13.jdbcTemplate内部需要获取连接,过程如下:
  //先从上面的resources中获取map	
  Map map = resources.get();
  //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(jdbaTemplate.getDatasources());
  if(conn == null){
   //如果没有找到连接,就重新获取一个;
  	conn = jdbcTemplate.datasource.getConnection();
  }
14.执行db操作,进行插入
15.最终TransactionInterceptor发现两个方法都执行完毕,没有异常,执行事务提交
//获取当前事务管理器的数据源
  DataSource datasource1 = transactionManager1.getDataSource();
  //先从上面的resources中获取map	
  Map map = resources.get();
   //从map中获取连接,看有没有可用的连接
  Connection conn = map.get(datasource1);
  //提交事务
  conn.commit();
  //管理链接
  conn.close();
16.清理ThreadLocal中的连接,通过resources.remove(datasource1)将连接移除
17.清理事务

0.0、事务管理器如何判断当前有事务

在这里插入图片描述

一、Spring中事务的使用方式

1. 编程式事务

通过硬编码的方式使用spring中提供的事务相关的类来控制事务

方式1:通过PlatformTransactionManager控制事务

步骤1:定义事务管理器PlatformTransactionManager
步骤2:定义事务属性TransactionDefinition
步骤3:开启事务
步骤4:执行业务操作
步骤5:提交 or 回滚
开启事务后,spring内部会执行一些操作

//有一个全局共享的threadLocal对象 resources
static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
//获取一个db的连接
DataSource datasource = platformTransactionManager.getDataSource();
Connection connection = datasource.getConnection();
//设置手动提交事务
connection.setAutoCommit(false);
Map<Object, Object> map = new HashMap<>();
map.put(datasource,connection);
resources.set(map);

将数据源datasource和connection映射起来放在了ThreadLocal中,ThreadLocal大家应该比较熟悉,用于在同一个线程中共享数据;后面我们可以通过resources这个ThreadLocal获取datasource其对应的connection对象。

为什么要扔进ThreadLocal ,原因是数据库实现事务是基于同一个数据库链接的,spring把他扔进ThreadLocal 就是为了保证同一个用事务注解的方法里面的所有sql执行时候拿来的数据库链接是同一个

  1. 准备maven依赖
<!-- JdbcTemplate需要的 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
<!-- spring 事务支持 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.3.RELEASE</version>
</dependency>
  1. 编写配置类,配置数据源和JdbcTemplate
@Configuration
@ComponentScans(
        {@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
public class MyConfig {
    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }
}
  1. 编写测试类
@Repository
public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DruidDataSource dataSource;

    public void update(User user) {
        //1.获取事务管理器,指定数据源
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        //2.定义事务属性(传播特性,隔离级别,超时时间,是否制度,回滚)
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        //3.开启事务,返回事务状态
        TransactionStatus transactionStatus = transactionManager.getTransaction(transactionDefinition);
        try {
            System.out.println("before:" + jdbcTemplate.queryForList("SELECT * from user"));
            int resilt = jdbcTemplate.update("insert into user(userId, username, password)value(?,?,?)",
                    user.getUserId(), user.getUsername(), user.getPassword());
            System.out.println("新增行数"+resilt);
            //4.提交事务
            transactionManager.commit(transactionStatus);
        } catch (DataAccessException e) {
            //5.回滚事务
            transactionManager.rollback(transactionStatus);
        }
        System.out.println("after:" + jdbcTemplate.queryForList("SELECT * from user"));
    }
}
  1. 运行输出
 @Test
    public void test(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        User user = new User(6,"ddd","1233121");
        userService.doUpdate(user);
    }

方式2:通过TransactionTemplate来控制事务

一、通过TransactionTemplate提供的方法执行业务操作主要有2个方法:(1).executeWithoutResult(Consumer action):没有返回值的,需传递一个Consumer对象,在accept方法中做业务操作

transactionTemplate.executeWithoutResult(new Consumer<TransactionStatus>() {
    @Override
    public void accept(TransactionStatus transactionStatus) {
        //执行业务操作
    }
});

(2). T execute(TransactionCallback action):有返回值的,需要传递一个TransactionCallback对象,在doInTransaction方法中做业务操作

Integer result = transactionTemplate.execute(new TransactionCallback<Integer>() {
    @Nullable
    @Override
    public Integer doInTransaction(TransactionStatus status) {
        return jdbcTemplate.update("insert into t_user (name) values (?)", "executeWithoutResult-3");
    }
});

二、调用execute方法或者executeWithoutResult方法执行完毕之后,事务管理器会自动提交事务或者回滚事务。那么什么时候事务会回滚,有2种方式:
(1)在execute或者executeWithoutResult内部执行transactionStatus.setRollbackOnly();将事务状态标注为回滚状态,spring会自动让事务回滚
(2)execute方法或者executeWithoutResult方法内部抛出任意异常即可回滚
三、什么时候事务会提交?
方法没有异常 && 未调用过transactionStatus.setRollbackOnly();

案例:

  1. 配置DataSource和JdbcTemplate,TransactionTemplate,DataSourceTransactionManager
@Configuration
@ComponentScans(
        {@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
public class MyConfig {

    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }

    @Bean
    public TransactionTemplate transactionTemplate(DataSourceTransactionManager transactionManager){
        TransactionTemplate transactionTemplate = new TransactionTemplate();
        transactionTemplate.setTransactionManager(transactionManager);
        return transactionTemplate;
    }
}
  1. 测试
@Service("userService")
public class UserService {
    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void bus1(){
        this.transactionTemplate.executeWithoutResult(transactionStatus -> {
            //先删除数据
            this.jdbcTemplate.update("delete from user");
            this.bus2();
        });
    }
    private void bus2() {
        this.transactionTemplate.executeWithoutResult(transactionStatus -> {
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",7,"zzz","123");
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",8,"pppp","123");
            this.jdbcTemplate.update("insert into user(userId, username, password) value (?,?,?)",9,"kkkk","123");
        });
    }
}

bus1中会先删除数据,然后调用bus2,此时bus1中的所有操作和bus2中的所有操作会被放在一个事务中执行,这是spring内部默认实现的,bus1中调用executeWithoutResult的时候,会开启一个事务,而内部又会调用bus2,而bus2内部也调用了executeWithoutResult,bus内部会先判断一下上线文环境中有没有事务,如果有就直接参与到已存在的事务中,刚好发现有bus1已开启的事务,所以就直接参与到bus1的事务中了,最终bus1和bus2会在一个事务中运行

2. 声明式事务

2.1 配置xml文件的方式

 <!--datasource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="ronny@123456"/>
    </bean>
<!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>
    <!--配置事务传播特性-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="txPointcut" expression="execution(* com.zjhc.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>

2.2 注解的方式

1、启用Spring的注释驱动事务管理功能

@EnableTransactionManagement
public class MainConfig4 {}

当spring容器启动的时候,发现有@EnableTransactionManagement注解,此时会拦截所有bean的创建,扫描看一下bean上是否有@Transaction注解(类、或者父类、或者接口、或者方法中有这个注解都可以),如果有这个注解,spring会通过aop的方式给bean生成代理对象,代理对象中会增加一个拦截器,拦截器会拦截bean中public方法执行,会在方法执行之前启动事务,方法执行完毕之后提交或者回滚事务。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(TransactionManagementConfigurationSelector.class)
public @interface EnableTransactionManagement {

 /**
  * spring是通过aop的方式对bean创建代理对象来实现事务管理的
  * 创建代理对象有2种方式,jdk动态代理和cglib代理
  * proxyTargetClass:为true的时候,就是强制使用cglib来创建代理
  */
 boolean proxyTargetClass() default false;

 /**
  * 用来指定事务拦截器的顺序
  * 我们知道一个方法上可以添加很多拦截器,拦截器是可以指定顺序的
  * 比如你可以自定义一些拦截器,放在事务拦截器之前或者之后执行,就可以通过order来控制
  */
 int order() default Ordered.LOWEST_PRECEDENCE;
}

2、定义事务管理器

@Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }

3、需使用事务的目标上加**@Transaction**注解

  • @Transaction放在接口上,那么接口的实现类中所有public都被spring自动加上事务、
  • @Transaction放在类上,那么当前类以及其下无限级子类中所有pubilc方法将被spring自动加上事务
  • @Transaction放在public方法上,那么该方法将被spring自动加上事务

注意:@Transaction只对public方法有效
4、执行db业务操作

如何确定方法有没有用到spring事务?
方式1:断点调试
TransactionAspectSupport中invokeWithinTransaction
在这里插入图片描述
方式2:看日志
spring处理事务的过程,有详细的日志输出,开启日志,控制台就可以看到事务的详细过程了
添加maven配置

<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

src\main\resources新建logback.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{MM-dd HH:mm:ss.SSS}][%thread{20}:${PID:- }][%X{trace_id}][%level][%logger{56}:%line:%method\(\)]:%msg%n##########**********##########%n</pattern>
        </encoder>
    </appender>

    <logger name="org.springframework" level="debug">
        <appender-ref ref="STDOUT" />
    </logger>
</configuration>

控制台中日志:

[09-27 18:24:36.391][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:225:getSingleton()]:Creating shared instance of singleton bean 'druidDataSource'
##########**********##########
[09-27 18:24:36.449][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:808:createArgumentArray()]:Autowiring by type from bean name 'jdbcTemplate' via factory method to bean named 'druidDataSource'
##########**********##########
[09-27 18:24:36.499][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:225:getSingleton()]:Creating shared instance of singleton bean 'transactionManager'
##########**********##########
[09-27 18:24:36.500][main: ][][DEBUG][o.s.beans.factory.support.DefaultListableBeanFactory:808:createArgumentArray()]:Autowiring by type from bean name 'transactionManager' via factory method to bean named 'druidDataSource'
##########**********##########
[09-27 18:24:36.537][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:370:getTransaction()]:Creating new transaction with name [com.zjhc.service.UserService.doDelete]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT; 'transactionManager'
##########**********##########
[09-27 18:24:37.811][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:267:doBegin()]:Acquired Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] for JDBC transaction
##########**********##########
[09-27 18:24:37.813][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:285:doBegin()]:Switching JDBC Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] to manual commit
##########**********##########
[09-27 18:24:37.850][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:958:update()]:Executing prepared SQL update
##########**********##########
[09-27 18:24:37.851][main: ][][DEBUG][org.springframework.jdbc.core.JdbcTemplate:643:execute()]:Executing prepared SQL statement [delete from user where userId = ?]
##########**********##########
[09-27 18:24:37.929][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:740:processCommit()]:Initiating transaction commit
##########**********##########
[09-27 18:24:37.929][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:330:doCommit()]:Committing JDBC transaction on Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a]
##########**********##########
[09-27 18:24:37.962][main: ][][DEBUG][o.s.jdbc.datasource.DataSourceTransactionManager:389:doCleanupAfterCompletion()]:Releasing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4dd6fd0a] after transaction
##########**********##########

简要分析:
在这里插入图片描述
@Transaction注解参数都是默认值,@Transaction注解中可以通过value或者transactionManager来指定事务管理器,但是没有指定,此时spring会在容器中按照事务管理器类型找一个默认的,刚好我们在spring容器中定义了一个,所以直接拿来用了。事务管理器我们用的是new DataSourceTransactionManager(dataSource),从事务管理器的datasource中获取一个数据库连接,然后通过连接设置事务为手动提交,然后将(datasource->这个连接)丢到ThreadLocal中了
在这里插入图片描述
从事务管理器的datasource中获取一个链接
在这里插入图片描述
开启connection手动提交事务
在这里插入图片描述
通过jdbcTemplate执行数据库操作
在这里插入图片描述
最后delete方法执行完毕之后,没有任何异常,那么spring就开始通过数据库连接提交事务了

二、Spring中事务的7种传播行为案例

只有同一个事务管理器的时候,才有者7种表现行为
1、事务管理器中的connection和jdbcTemplate操作db的connection如何使用同一个?
在这里插入图片描述
案例分析:
1、配置类

@Configuration
@ComponentScans(
        {@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
@EnableTransactionManagement
public class MyConfig {

    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }
}

2、创建三个Service类:

package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class Service1 {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}
package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class Service2 {
    @Autowired
    private JdbcTemplate jdbcTemplate;
}
package com.zjhc.service;
import org.springframework.stereotype.Service;
@Service
public class TxService {
    @Autowired
    private Service1 service1;
    @Autowired
    private Service2 service2;
}

3、测试类

public class TransactionTest {
    private TxService txService;
    private JdbcTemplate jdbcTemplate;

    //每次测试前先启动spring容器,并清理表数据
    @Before
    public void before(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        txService = context.getBean(TxService.class);
        jdbcTemplate = context.getBean(JdbcTemplate.class);
        jdbcTemplate.update("truncate table user");
        jdbcTemplate.update("truncate table user1");
    }

    @After
    public void after(){
        System.out.println("user表中数据:"+jdbcTemplate.queryForList("select * from user"));
        System.out.println("user1表中数据:"+jdbcTemplate.queryForList("select * from user1"));
    }
}

2.1 REQUIRED

Service1:添加一个方法,事务传播特性为:required

 @Transactional(propagation = Propagation.REQUIRED)
    public void required(int id,String username,String password){
        this.jdbcTemplate.update("insert into user(userId, username, password) VALUES (?,?,?)",id,username,password);
    }

Service2:添加两个方法,事务传播特性为:required

    @Transactional(propagation = Propagation.REQUIRED)
    public void required(int id,String username,String password){
        this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void required_exception(int id,String username,String password){
        this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
        throw new RuntimeException();
    }

场景一:外围没有事务

验证方法一:

 //required
    //场景一:外围没有事务,外围方法调用两个Required级别的方法
    public void notransaction_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required(1,"zs","111");
        throw new RuntimeException();
    }

测试:

 @Test
    public void test(){
        txService.notransaction_exception_required_required();
    }

结果:
在这里插入图片描述
验证方法二:

//required
    //场景一:外围没有事务,外围方法调用两个Required级别的方法
    public void notransaction_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required_exception(1,"zs","111");
        throw new RuntimeException();
    }

测试:

 @Test
    public void test(){
        txService.notransaction_exception_required_required();
    }

结果:
在这里插入图片描述

场景一:外围有事务

TxService种添加方法:
测试方法一:

  //场景二:外围开启事务,外围方法调用两个Required级别的方法
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required(1,"zs","111");
        throw new RuntimeException();
    }

测试:

 @Test
    public void test(){
        txService.transaction_exception_required_required();
    }

结果:
在这里插入图片描述
测试方法二:

  //场景二:外围开启事务,外围方法调用两个Required级别的方法
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required_exception(1,"zs","111");
        throw new RuntimeException();
    }

测试:

 @Test
    public void test(){
        txService.transaction_exception_required_required();
    }

结果:
在这里插入图片描述

2.1 REQUIRED_NEW

Service1:添加一个方法,事务传播特性为REQUIRED_NEW

 @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void required_new(int id,String username,String password){
        this.jdbcTemplate.update("insert into user(userId, username, password) VALUES (?,?,?)",id,username,password);
    }

Service2:添加一个方法,事务传播特性为REQUIRED_NEW

 @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void required_new(int id,String username,String password){
        this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void required_new_exception(int id,String username,String password){
        this.jdbcTemplate.update("insert into user1(userId, username, password) VALUES (?,?,?)",id,username,password);
        throw new RuntimeException();
    }

场景一:外围没有事务

验证方法一:

 //required_new
    //场景一:外围没有事务,外围方法调用两个Required级别的方法
    public void notransaction_exception_required_requirednew(){
        this.service1.required_new(1,"zs","111");
        this.service2.required_new(1,"zs","111");
        throw new RuntimeException();
    }

测试:

@Test
    public void test(){
        txService.notransaction_exception_required_requirednew();
    }

结果:
在这里插入图片描述
验证方法二:

 //required_new
    //场景一:外围没有事务,外围方法调用两个Required级别的方法
    public void notransaction_exception_required_requirednew(){
        this.service1.required_new(1,"zs","111");
        this.service2.required_new_exception(1,"zs","111");
        throw new RuntimeException();
    }

测试:

@Test
    public void test(){
        txService.notransaction_exception_required_requirednew();
    }

结果:
在这里插入图片描述

场景二:外围有事务

验证方法一:

   @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_new_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required_new(1,"zs","111");
        this.service2.required_new(2,"zs","222");
        throw new RuntimeException();
    }

测试:

@Test
    public void test(){
      txService.transaction_new_exception_required_required();
    }

结果:
在这里插入图片描述
验证方法二:

 //场景二:外围开启事务,外围方法调用两个Required_new级别的方法
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_new_exception_required_required(){
        this.service1.required(1,"zs","111");
        this.service2.required_new(1,"zs","111");
        this.service2.required_new_exception(2,"zs","222");
        throw new RuntimeException();
    }

测试:

@Test
    public void test(){
        txService.notransaction_exception_required_requirednew();
    }

结果:
在这里插入图片描述

三、Spring多数据源的问题

案例一:
1、准备DB:2个库_ds1,ds2),每个库有2个表(user1,user2)
2、Spring配置类
在这里插入图片描述
源码:

@Configuration
@ComponentScans(
        {@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
@EnableTransactionManagement
public class MyConfig {

    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }

    @Bean
    public DruidDataSource druidDataSource1(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/test?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate1(DruidDataSource druidDataSource1){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource1);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager1(DruidDataSource druidDataSource1){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource1);
        return transactionManager;
    }
}

3、创建6个service类

@Service
public class Ds1User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void required(String name){
        this.jdbcTemplate.update("insert into user(username) values (?)",name);
    }
}

@Service
public class Ds1User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void required(String name){
        this.jdbcTemplate.update("insert into user1(username) values (?)",name);
    }
}

@Service
public class Ds2User1Service {
    @Autowired
    private JdbcTemplate jdbcTemplate1;

    @Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
    public void required(String name){
        this.jdbcTemplate1.update("insert into test.user(username) values (?)",name);
    }
}

@Service
public class Ds2User2Service {
    @Autowired
    private JdbcTemplate jdbcTemplate1;

    @Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
    public void required(String name){
        this.jdbcTemplate1.update("insert into test.user1(username) values (?)",name);
    }
}

@Service
public class Tx1Service {
    @Autowired
    private Ds1User1Service ds1User1Service;
    @Autowired
    private Ds1User2Service ds1User2Service;
}

@Service
public class Tx2Service {
    @Autowired
    private Ds2User1Service ds2User1Service;
    @Autowired
    private Ds2User2Service ds2User2Service;
}

4、创建测试方法

public class ManyDataSourceTest {

    private Tx1Service tx1Service;
    private JdbcTemplate jdbcTemplate;
    private JdbcTemplate jdbcTemplate1;

    @Before
    public void before(){
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        this.tx1Service = context.getBean(Tx1Service.class);
        this.jdbcTemplate = context.getBean("jdbcTemplate",JdbcTemplate.class);
        this.jdbcTemplate1 = context.getBean("jdbcTemplate1",JdbcTemplate.class);
        jdbcTemplate.update("truncate table user");
        jdbcTemplate.update("truncate table user1");
        jdbcTemplate1.update("truncate table test.user");
        jdbcTemplate1.update("truncate table test.user1");
    }
    @After
    public void after(){
        System.out.println("ds1.user表数据:"+this.jdbcTemplate.update("select * from user"));
        System.out.println("ds1.user1表数据:"+this.jdbcTemplate.update("select * from user1"));
        System.out.println("ds2.user表数据:"+this.jdbcTemplate.update("select * from test.user"));
        System.out.println("ds2.user1表数据:"+this.jdbcTemplate.update("select * from test.user1"));
    }
}

5、代码验证
(1)场景一:

外围方法和内部方法使用相同的事务管理器,传播行为都是required
Tx1Service中:

 @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void test1(){
        this.ds1User1Service.required(1,"张三");
        this.ds1User2Service.required(1,"李四");
        throw new RuntimeException();
    }
方法事务管理器事务管理器对应数据源JdbcTemplate对应数据源
test1transactionManagerdataSource-
ds1User1Service.requiredtransactionManagerdataSourcedataSource
ds1User2Service.requiredtransactionManagerdataSourcedataSource

测试代码:

 @Test
    public void test(){
        tx1Service.test1();
    }

运行输出:
在这里插入图片描述

数据库结果分析
张三李四均未插入外围方法和内部方法均使用同一个事务管理器,且事务管理器和jdbcTemplate使用的datasource是同一个,所以外围开启事务后,内部方法加入外围事务,外围方法报错导致事务回滚,所以内部方法也跟着回滚了

(2)场景二:

外围方法和内部方法使用不同的事务管理器,传播行为都是required
Tx1Service中:

 @Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
    public void test2(){
        this.ds1User1Service.required(1,"张三");
        this.ds1User2Service.required(1,"李四");
        throw new RuntimeException();
    }
方法事务管理器事务管理器对应数据源JdbcTemplate对应数据源
test2transactionManager1dataSource1-
ds1User1Service.requiredtransactionManagerdataSourcedataSource
ds1User2Service.requiredtransactionManagerdataSourcedataSource

测试代码:

 @Test
    public void test(){
        tx1Service.test2();
    }

结果
在这里插入图片描述

数据库结果分析
张三李四均插入外围方法和内部方法使用不同的事务管理器,内部方法在各自的事务中进行,不受外围事务方法的控制

场景三:

Tx1Service中加入:

@Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void test3(){
        this.ds1User1Service.required(1,"张三");
        this.ds1User2Service.required(1,"李四");
        this.ds2User1Service.required(1,"王五");
        this.ds2User2Service.required(1,"赵六");
        throw new RuntimeException();
    }
方法事务管理器事务管理器对应数据源JdbcTemplate对应数据源
test3transactionManagerdataSource-
ds1User1Service.requiredtransactionManagerdataSourcedataSource
ds1User2Service.requiredtransactionManagerdataSourcedataSource
ds2User1Service.requiredtransactionManager1dataSource1dataSource1
ds2User2Service.requiredtransactionManager1dataSource1dataSource1

测试结果:
在这里插入图片描述

数据库结果分析
张三李四均未插入外围方法和内部方法使用同一个事务管理器,所以三个方法在同一个事务中进行,外围方法抛出回滚,内部方法也跟着回滚
王五赵六均插入外围方法和内部方法使用不同的事务管理器,内部方法在各自的事务中进行,不受外围事务方法的控制

场景四:
Tx2Service中加入:

 @Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
    public void test1(){
        this.ds2User1Service.required(1,"张三");
        this.ds2User2Service.required(2,"李四");

    }

Tx1Service中加入:

 @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void test4(){
        this.ds1User1Service.required(1,"王五");
        this.ds1User2Service.required(1,"赵六");
        this.tx2Service.test1();
        throw new RuntimeException();
    }
方法事务管理器事务管理器对应数据源JdbcTemplate对应数据源
test4transactionManagerdataSource-
ds1User1Service.requiredtransactionManagerdataSourcedataSource
ds1User2Service.requiredtransactionManagerdataSourcedataSource
this.tx2Service.test1transactionManager1dataSource1
ds2User1Service.requiredtransactionManager1dataSource1dataSource1
ds2User2Service.requiredtransactionManager1dataSource1dataSource1

运行结果:
在这里插入图片描述
场景五:
Tx2Service中加入:

  @Transactional(transactionManager = "transactionManager1",propagation = Propagation.REQUIRED)
    public void test2(){
        this.ds2User1Service.required(1,"张三");
        this.ds2User2Service.required(2,"李四");
        throw new RuntimeException();
    }

Tx1Service中加入:

  @Transactional(transactionManager = "transactionManager",propagation = Propagation.REQUIRED)
    public void test5(){
        this.ds1User1Service.required(1,"王五");
        this.ds1User2Service.required(1,"赵六");
        this.tx2Service.test1();
    }
方法事务管理器事务管理器对应数据源JdbcTemplate对应数据源
test5transactionManagerdataSource-
ds1User1Service.requiredtransactionManagerdataSourcedataSource
ds1User2Service.requiredtransactionManagerdataSourcedataSource
this.tx2Service.test2transactionManager1dataSource1
ds2User1Service.requiredtransactionManager1dataSource1dataSource1
ds2User2Service.requiredtransactionManager1dataSource1dataSource1

运行结果:
在这里插入图片描述
结果分析:
四条数据均未插入,test5通过事务管理器transactionManager 开启了事务tm1,然后张三和李四加入了tm1。test2通过事务管理器transactionManager1 开启了事务tm2,王五和赵六加入了tm2;test2方法内部抛错tm2回滚,方法里面的王五赵六跟test2属于同一个事务管理器,是同一个事务,所以王五赵六插入失败。tm1也感知到了这个异常,两个事务都回滚。

案例二:
同一个数据源,多个事务管理器,多个JdbcTemplate的情形。

@Configuration
@ComponentScans(
        {@ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Service.class)}),
                @ComponentScan(basePackages = "com.zjhc",useDefaultFilters = false,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Repository.class)})
        }
)
@EnableTransactionManagement
public class MyConfig {

    @Bean
    public DruidDataSource druidDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://121.41.106.140:43306/eblog?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("ronny@123456");
        dataSource.setInitialSize(5);
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }

    @Bean
    public JdbcTemplate jdbcTemplate1(DruidDataSource druidDataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(druidDataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager transactionManager1(DruidDataSource druidDataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(druidDataSource);
        return transactionManager;
    }
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、手动创建事务管理器的两种方法

在这里插入图片描述

4.1 使用QueryRunner+自定义事务管理器

使用queryRunner操作数据库时,一定要在参数里指定连接为当先线程里的连接,来保证一个事务里的连接只有一个,否则抛错后可能不会回滚。

 <dependency>
  <groupId>commons-dbutils</groupId>
  <artifactId>commons-dbutils</artifactId>
  <version>1.7</version>
</dependency>

ConnectionUtil

public class ConnectionUtil {
    //存放连接的ThreadLocal
    private ThreadLocal<Connection> connectionThreadLocal = new ThreadLocal<>();
    //注入数据源
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    //获取当前连接
    public Connection getConnection(){
        try {
            //先从线程中取连接
            Connection connection = connectionThreadLocal.get();
            if(connection == null){
                connection = dataSource.getConnection();
                connectionThreadLocal.set(connection);
            }
            return connection;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void remove(){
        connectionThreadLocal.remove();
    }
}

TransactionManager

public class TransactionManager {

    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public void beginTransaction(){
        try {
            this.connectionUtil.getConnection().setAutoCommit(false);
            System.out.println("事务已开启");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void commit(){
        try {
            this.connectionUtil.getConnection().commit();
            System.out.println("事务已提交");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void rollback(){
        try {
            this.connectionUtil.getConnection().rollback();
            System.out.println("事务已回滚");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void release(){
        try {
            this.connectionUtil.getConnection().close();
            this.connectionUtil.remove();
            System.out.println("连接已释放");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

QueryRunnerDao

public interface QueryRunnerDao {

    //下面2个方法测试手动创建txManager
    Account queryOneByName(String name);
    void update(Account account);
}

QueryRunnerDaoImpl
注意
在xml中一定不要给QueryRunner注入datasource,让从当前线程ThreadLocal中获取连接,保证一个事务上的连接是一样的,从而保证是同一个事务。不然事务可能不会回滚

public class QueryRunnerDaoImpl implements QueryRunnerDao {

    //测试手动创建TXmanager
    private QueryRunner queryRunner;
    private ConnectionUtil connectionUtil;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    /**
     * 测试手动创建事务管理器
     */
    public Account queryOneByName(String name){
        try {
            return queryRunner.query(connectionUtil.getConnection(),"select * from test.account where name =?",new BeanHandler<Account>(Account.class),name);
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 测试手动创建事务管理器
     */
    public void update(Account account){
        try {
            queryRunner.update(connectionUtil.getConnection(),"update test.account set name =?,money=? where id =?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
}

IQueryRunnerSerive :

public interface IQueryRunnerSerive {

    void transfer(String sourceName, String targetName, Float money);
}

QueryRunnerService :

public class QueryRunnerService implements IQueryRunnerSerive{

    private QueryRunnerDao queryRunnerDao;
    private TransactionManager transactionManager;

    public void setQueryRunnerDao(QueryRunnerDao queryRunnerDao) {
        this.queryRunnerDao = queryRunnerDao;
    }

   public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    /**
     *  测试手动创建事务管理器
     */
    public void transfer(String sourceName, String targetName, Float money){
        try {
            transactionManager.beginTransaction();
            Account sourceAccount = queryRunnerDao.queryOneByName(sourceName);
            Account targetAccount = queryRunnerDao.queryOneByName(targetName);
            sourceAccount.setMoney(sourceAccount.getMoney()-money);
            targetAccount.setMoney(targetAccount.getMoney()+money);
            queryRunnerDao.update(sourceAccount);
            int i = 1/0;
            queryRunnerDao.update(targetAccount);
            transactionManager.commit();
        } catch (Exception e) {
            transactionManager.rollback();
            e.printStackTrace();
        }finally {
            transactionManager.release();
        }

    }
}

beans.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://121.41.106.140:43306/test?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="ronny@123456"/>
        <property name="defaultAutoCommit" value="true"/>
    </bean>

    <bean id="queryRunnerDao" class="com.zjhc.dao.impl.QueryRunnerDaoImpl">
        <!--以下只为测试手动创建txManager-->
        <property name="queryRunner" ref="querRun"/>
        <property name="connectionUtil" ref="dbUt"/>
    </bean>

    <bean id="trans" class="com.zjhc.dbUtils.TransactionManager">
        <property name="connectionUtil" ref="dbUt"/>
    </bean>
    <bean id="dbUt" class="com.zjhc.dbUtils.ConnectionUtil">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="queryRunnerService" class="com.zjhc.service.QueryRunnerService">
        <property name="queryRunnerDao" ref="queryRunnerDao"/>
        <property name="transactionManager" ref="trans"/>
    </bean>
    <!--在这里不要注入数据源-->
    <bean id="querRun" class="org.apache.commons.dbutils.QueryRunner"/>
</beans>

测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans1.xml")
public class queryRunnerTest {
    @Autowired
    @Qualifier("queryRunnerService")
    private IQueryRunnerSerive service;
    @Test
    public void service(){
        service.transfer("aaa","bbb",100f);
    }
}

4.2 使用DataSourceTransactionManager手动控制

用JdbcTemplate操作数据库是,由于不能在参数里指定连接,所以每次操作数据库时都会从datasource获取一个新的连接,此时用自定义的事务管理器和connectionUtil会导致事务不会回滚,因为每次的连接都不是同一个。
AccountDao :

public interface AccountDao {

    List<Map<String, Object>> queryAllAccount();

    Account queryAccountByName(String name);

    void updateAccount(Account account);

    void insertAccount(Account account);

    void deleteAccount(Integer id);

}

AccountDaoImpl :

public class AccountDaoImpl implements AccountDao {
    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public List<Map<String, Object>> queryAllAccount() {
        return jdbcTemplate.queryForList("select * from test.account");
    }

    public Account queryAccountByName(String name) {
        return jdbcTemplate.queryForObject("select * from test.account where name =?", (resultSet, i) -> {
            String username = resultSet.getString("name");
            float money = resultSet.getFloat("money");
            Integer id = resultSet.getInt("id");
            Account account = new Account();
            account.setId(id);
            account.setName(username);
            account.setMoney(money);
            return account;
        }, name);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update test.account set name=? , money=? where id =?",account.getName(),account.getMoney(),account.getId());
    }

    public void insertAccount(Account account) {
        jdbcTemplate.update("insert into test.account(name, money) VALUES (?,?)",account.getName(),account.getMoney());
    }

    public void deleteAccount(Integer id) {
        jdbcTemplate.update("delete from test.account where id =?",id);
    }
}

IAccountSerive :

public interface IAccountSerive {
    void transfer(String sourceName, String targetName, Float money);
}

AccountService :

public class AccountService implements IAccountSerive{

    private AccountDao accountDao;

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    /**
     *转账接口
     * @param sourceName
     * @param targetName
     * @param money
     */
    public void transfer(String sourceName, String targetName, Float money){
        //获取事务管理器
        PlatformTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
        //配置事务特性,隔离级别,是否只读。。。。
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        //开启事务
        TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
        try {
            Account sourceAccount = accountDao.queryAccountByName(sourceName);
            Account targetAccount = accountDao.queryAccountByName(targetName);
            sourceAccount.setMoney(sourceAccount.getMoney()-money);
            targetAccount.setMoney(targetAccount.getMoney()+money);
            accountDao.updateAccount(sourceAccount);
            int i = 1/0;
            accountDao.updateAccount(targetAccount);
            //提交事务
            transactionManager.commit(transaction);
        } catch (Exception e) {
            //回滚事务
            transactionManager.rollback(transaction);
            e.printStackTrace();
        }
    }
}

beans.xml

<bean id="accountDao" class="com.zjhc.dao.impl.AccountDaoImpl">
       <property name="jdbcTemplate" ref="jdbaTemplate"/>
   </bean>

    <bean id="accountService" class="com.zjhc.service.AccountService">
        <property name="accountDao" ref="accountDao"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="jdbaTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://121.41.106.140:43306/test?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="ronny@123456"/>
        <property name="defaultAutoCommit" value="true"/>
    </bean>

测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans.xml")
public class txTest {

    @Autowired
    private IAccountSerive serive;

    @Test
    public void serive(){
        serive.transfer("aaa","bbb",100f);
    }
}

4.3 用手写jdk动态代理配置事务增强

部分类跟上一案例一样,这里只写不同的
创建动态代理类

public class BeanFactory implements InvocationHandler{
    private static final Logger log = LoggerFactory.getLogger(BeanFactory.class);

    private Object target;

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public BeanFactory(Object target){
        this.target = target;
    }

    public Object getProxy(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object rtValue = null;
        PlatformTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(dataSource);
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        //开启事务
        TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
        log.info("事务已开启~~~~~~");
        try {
            rtValue = method.invoke(target,args);
            //提交事务
            dataSourceTransactionManager.commit(transactionStatus);
            log.info("事务已提交~~~~~~~");
            return rtValue;
        } catch (Exception e) {
            dataSourceTransactionManager.rollback(transactionStatus);
            log.info("事务已回滚~~~~~~~~");
            e.printStackTrace();
        }
        return null;
    }
}

beans.xml:
配置动态代理类的bean,配置代理的目标类(相当于用工厂模式创建被代理的目标类对象),用代理类对象操作方法。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--动态代理类-->
    <bean id="beanFactory" class="com.zjhc.aop.BeanFactory">
        <!--注入代理对象-->
        <constructor-arg index="0" ref="accountService"/>
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 创建动态代理后的bean对象,用动态代理类调用方法-->
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getProxy"/>

   <bean id="accountDao" class="com.zjhc.dao.impl.AccountDaoImpl">
       <property name="jdbcTemplate" ref="jdbaTemplate"/>
   </bean>

    <bean id="accountService" class="com.zjhc.service.AccountService">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <bean id="jdbaTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://121.41.106.140:43306/test?useUnicode=true&amp;useSSL=false&amp;userCharacterEncoding=utf8"/>
        <property name="username" value="root"/>
        <property name="password" value="ronny@123456"/>
        <property name="defaultAutoCommit" value="true"/>
    </bean>
</beans>

测试:
@Qualifier(“proxyService”)指定生成的代理类对象,因为容器有两个同类型的bean。不指定会报错bean不唯一。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:beans.xml")
public class txTest {

    @Autowired
    @Qualifier("proxyService")//
    private IAccountSerive serive;

    @Test
    public void serive(){
        serive.transfer("aaa","bbb",100f);
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一位不知名民工

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值