目录

spring事务

事务管理介绍

  1. 事务添加到 JavaEE 三层结构里面 Service 层(业务逻辑层)

  2. 在 Spring 进行事务管理操作有两种方式:编程式事务管理和声明式事务管理。

    编程式事务需要你在代码中直接加入处理事务的逻辑,需要在代码中显式调用beginTransaction()、commit()、rollback()等事务管理相关的方法,如在执行a方法时候需要事务处理,你需要在a方法开始时候开启事务,处理完后。在方法结束时候,关闭事务。

    编程式事务侵入性比较强,但处理粒度更细.

  3. 声明式事务管理

    1. 基于注解
    2. 基于xml配置文件
  4. 声明式事务管理底层是基于aop实现的

  5. spring事务管理api 接口PlatformTransactionManager,代表事务管理器,这个接口针对不同的框架提供不同的实现类。

    比如针对Hibernate的HibernateTransactionManager,针对JdbcTemplate和Mybatis的DataSourceTransactionManager。

声明式事务管理注解方式演示

步骤:

  1. 引入事务名称空间

    tx

  2. 开启事务注解

    1
    2
    3
    4
    5
    
    <!--
        开启事务注解
        有属性transaction-manager,默认值就是transactionManager,所以可以不用填写
    -->
    <tx:annotation-driven/>
    
  3. 配置事务管理器

    1
    2
    3
    
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  4. 在service层添加事务注解

    可以在类上加也可以在方法上加,区别是在类上加,该类所有方法都添加事务。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    @Transactional
    @Override
    public void transfer(int srcId, int destId, int money) {
        // 付款人减
        accountDao.reduceMoney(srcId, money);
        // 模拟异常
        if (true) {
            throw new RuntimeException("模拟异常");
        }
        // 收款人增
        accountDao.addMoney(destId, money);
    }
    
  5. 测试

    1
    2
    3
    4
    5
    
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        AccountService accountService = context.getBean("accountServiceImpl", AccountServiceImpl.class);
        accountService.transfer(1, 2, 10);
    }
    

事务管理参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

  // 事务管理器
    @AliasFor("value")
    String transactionManager() default "";

  // 事务传播行为
    Propagation propagation() default Propagation.REQUIRED;

  // 事务隔离级别
    Isolation isolation() default Isolation.DEFAULT;

  // 事务超时时间
    int timeout() default -1;

  // 事务只读
    boolean readOnly() default false;

  // 回滚
    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

  // 不回滚
    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

事务传播行为propagation

事务方法:对数据库进行变化的方法

事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

比如事务方法a调事务方法b, 存在3种情景

  • a加事务注解b没加
  • a加b加
  • a没加b加

此时事务方法b该如何进行,是继续在调用者a的事务中运行呢,还是为自己开启一个新的事务,这就是由b的事务传播行为决定的。

spring定义了7种传播行为

事务传播行为类型 说明
PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择,也是默认的事务传播行为
PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW 新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,则抛出异常。
PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
注意

事务方法a和事务方法b不在同一个类中,如果在同一个类,注解是不生效的。像下面这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@Transactional
@Component
public class A{
    public void method1(){
        method2();
    }

    public void method2(){
       //...
    }
}

如果非要在同一个类进行也可以,需要从上下文容器中重新获取一次bean,再调b方法即可。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@Transactional
@Component
public class A{
        
    @Autowired
    private A a;

    public void method1(){
        a.method2();
    }

    public void method2(){
       //...
    }
}

jdk动态代理是动态生成一个类(根据原class生成新的class)的方式实现的代理,也就是说代理是不会修改原始类的字节码的,所以代理类的class可能是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public void method1(){
    //执行一段代码
    a.method1()
    //执行一段代码
}

public void method2(){
    //执行一段代码
    a.method2()
    //执行一段代码
}

回头看a.method1()的源码,也就明白了,为啥method2()没有被切到,因为a.method1()执行的方法,最后调用的不是 代理对象.method2(),而是它自己的method2()(this.method2()) 这个方法本身没有任何改动

反观aspectJ,aspectJ是在编译期修改了方法(类本身的字节码被改了),所以可以很轻松地实现调用自己的方法时候的增强。

所以要解决这个问题就出现了在类的内部引用自己这种很奇怪的做法。

事务隔离级别isolcation

数据库为了解决3个读问题(脏读、不可重复度、幻读)引入了4个隔离级别,spring支持对隔离级别进行配置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

注意:mysql默认的隔离级别是REPEATABLE-READ,如果spring没设置隔离级别则沿用数据库的隔离级别。

超时时间timeout

事务需要在一定时间内进行提交,如果不提交则回滚

默认值是 -1 ,表示不设置超时时间

设置时间以秒单位进行计算

是否只读readonly

readOnly 默认值 false,表示可以查询,可以添加修改删除操作

设置 readOnly 值是 true,只能查询

rollbackFor

设置出现哪些异常进行事务回滚

noRollbackFor

设置出现哪些异常不进行事务回滚

声明式事务管理配置文件方式演示

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?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/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">


    <context:property-placeholder location="classpath:db.properties"/>

    <!--配置成autowire时需要在AccountServiceImpl添加属性的set方法
		@Setter
    @Autowired
    private AccountDao accountDao;
		-->
    <bean id="accountService" class="com.eh.eden.spring.demo.service.AccountServiceImpl" autowire="byType"/>
    <bean id="accountDao" class="com.eh.eden.spring.demo.dao.AccountDaoImpl" autowire="byType"/>

    <!--step1: 配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driver}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
    </bean>

    <!--step2: 引入jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>


    <!--1. 配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>


    <!--配置通知-->
    <tx:advice id="txAdvice">
        <!--配置事务参数-->
        <tx:attributes>
            <!--指定哪种规则的方法上面添加事务-->
            <tx:method name="accountMoney" propagation="REQUIRED"/>
            <!--<tx:method name="account*"/>-->
        </tx:attributes>
    </tx:advice>

    <!--配置切入点和切面-->
    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="pt" expression="execution(* com.eh.eden.spring.demo.service.AccountServiceImpl.*(..))"/>
        <!--配置切面-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

</beans>

测试:

1
2
3
4
5
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("beans_tx.xml");
    AccountService accountService = context.getBean("accountService", AccountService.class);
    accountService.transfer(1, 2, 10);
}

纯注解声明式事务管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.eh.eden.spring.demo;

import com.alibaba.druid.pool.DruidDataSource;
import com.eh.eden.spring.demo.service.AccountService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.*;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;

@Configuration // 配置类
@ComponentScan("com.eh.eden.spring.demo") // 组件扫描
@EnableTransactionManagement // 开启事务
@PropertySource("classpath:db.properties")
public class TxSpringConfig {


    @Value("${driver}")
    private String driver;

    @Value("${url}")
    private String url;

    @Value("${username}")
    private String username;

    @Value("${password}")
    private String password;


    // 创建数据库连接池
    @Bean
    public DataSource getDruidDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    // 创建JdbcTemplate对象
    // 在方法中加入dataSource参数,表示到ioc 容器中根据类型找到 dataSource
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    // 创建事务管理器
    @Bean
    public TransactionManager getTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(TxSpringConfig.class);
        AccountService accountService = context.getBean("accountServiceImpl", AccountService.class);
        accountService.transfer(1, 2, 10);
    }

}