声明式事务控制

  1. 1. 一、什么是声明式事务控制
    1. 1.1. 1. 搭建声明式事务的环境
  2. 2. 二、声明式事务处理的作用
  3. 3. 三、事务传播行为
  4. 4. 四、声明式事务控制的实现
  5. 5. 五、添加事务管理

一、什么是声明式事务控制

​ Spring 的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明,用在 Spring 配置文件中声明式的处理事务来代替代码式的处理事务。

1. 搭建声明式事务的环境

​ 对数据库进行增删改操作时,必然是要使用到事务的。因此,接下来,我们就来搭建好声明式事务的基本环境。

导入相关依赖:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 导入C3P0连接池坐标 -->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>

<!-- 导入数据库驱动坐标 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>

<!-- 导入spring的jdbc坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
JdbcConfig.java配置类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.xxxx.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.annotation.Import;

/**
* 指定该类是一个spring配置类,相当于一个配置文件
* ComponentScan:组件扫描
* Import:引入JdbcConfig.class文件
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@ComponentScan(basePackages = "com.xxxx")
@Import(JdbcConfig.class)
public class SpringConfig {

}

配置数据源以及JdbcTemplate:

​ 1. 首先,我们得向IOC容器中注册一个c3p0数据源,那么如何做到这一点呢?很简单,先新建一个配置类,例如TxConfig,再使用@Bean注解向IOC容器中注册一个c3p0数据源,如下所示。

JdbcConfig.java配置类
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
package com.xxxx.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
* jdbc配置类
* PropertySource:引入jdbc属性资源文件
* classpath: 在resource资源包里面
*/

@EnableTransactionManagement // 它是来开启基于注解的事务管理功能的
// @ComponentScan("com.xxxx.tx")
@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;

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

@Value("${jdbc.user}")
private String user;

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

/**
* Bean注解:该注解只能写在方法上,表面该方法创建了一个对象,并放入spring容器
*/
@Bean("dataSource")
public DataSource createDataSource() throws PropertyVetoException {
// 创建数据源
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(user);
dataSource.setPassword(password);
return dataSource;
}

....

}
  1. 然后,再向IOC容器中注册一个JdbcTemplate组件,它是Spring提供的一个简化数据库操作的工具,它能简化对数据库的增删改查操作。
注册一个JdbcTemplate组件
1
2
3
4
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

​ 注意,在创建JdbcTemplate对象的时候,得把数据源传入JdbcTemplate类的有参构造器中,因为需要从数据源里面获取数据库连接。

​ 为什么可以这样做呢?因为@Bean注解标注的方法在创建对象的时候,方法参数的值是从IOC容器中获取的,并且标注在这个方法的参数上的@Autowired注解可以省略。

​ 这种方式就不用那么麻烦了,在JdbcTemplate类的有参构造器中调用一次dataSource()方法即可。可以看到,向IOC容器中注册一个JdbcTemplate组件时,使用的就是这种方式。

​ 有些同学可能会有一些疑问,TxConfig配置类的dataSource()方法是向IOC容器中注册一个c3p0数据源的,该方法的逻辑也很简单,就是创建一个c3p0数据源并将其返回出去,而在向IOC容器中注册一个JdbcTemplate组件时,会在其有参构造器中调用一次dataSource()方法,那岂不是又会创建一个c3p0数据源呢?不知你会不会有这样一个疑问,反正我是有的。

​ 其实,并不会再创建一个c3p0数据源,因为对于Spring的配置类而言,只要某个方法是给IOC容器中注册组件的,那么我们第二次调用该方法,就相当于是从IOC容器中找组件,而不是说把该方法再运行一遍。

​ 总结一下,Spring对@Configuration注解标注的类会做特殊处理,多次调用给IOC容器中添加组件的方法,都只是从IOC容器中找组件而已。

​ 在Service组件中,使用@Transactional注解,就可以给业务方法添加事务管理。

1
2
3
4
@Transactional
public int transferAccounts(Emp emp1,Emp emp2) {
//需要事务管理的业务
}

注意:

  1. 需要事务管理的service,在方法上加上@Transactional 注解即可。
  2. 必须为public方法才行,不要捕捉异常,要让异常自动抛出,否则不能进行事务回滚。

二、声明式事务处理的作用

  1. 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。

  2. 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。

编程式事务控制三大对象:

  1. PlatformTransactionManager
  2. TransactionDefinition
  3. TransactionStatus

三、事务传播行为

@Transactional 注解中的 propagation 属性,可以设置事务传播行为。属性值为:

  1. REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,就加入到这个事务中。这是最常见的选择。
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果存在事务,就把当前事务挂起。
  6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. 超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置
  8. 是否只读:建议查询时设置为只读

​ 接下来,我们就为Service类中的getDeptById()方法添加上事务,添加上事务以后,只要这个方法里面有任何一句代码出现了问题,那么该行代码之前执行的所有操作就都应该回滚。

1
2
3
4
5
@Override
@Transactional(propagation=Propagation.SUPPORTS)
public Dept getDeptById(Integer deptno) {
return deptMapper.getDeptById(deptno);
}

​ 如果要想为该方法添加上事务,那么就得使用@Transactional注解了。我们在该方法上标注这么一个注解,就是为了告诉Spring这个方法它是一个事务方法,这样,Spring在执行这个方法的时候,就会自动地进行事务控制。如果该方法正常执行,没出现任何问题,那么该方法中的所有操作都会生效,最终就会提交;如果该方法运行期间出现异常,那么该方法中的所有操作都会回滚。

​ 光为getDeptById()方法加一个@Transactional注解是不行的,那我们还得做什么呢?还得在配置类上标注一个@EnableTransactionManagement注解,来开启基于注解的事务管理功能。

​ 如果是像以前一样基于配置文件来开发,那么就得在配置文件中添加如下这样一行配置,来开启基于注解的事务管理功能。

1
<tx:annotation-driven/>

四、声明式事务控制的实现

声明式事务控制明确事项:

  1. 谁是切点?
  2. 谁是通知?
  3. 配置切面?

五、添加事务管理

​ Spring为了支持事务管理,专门封装了事务管理对象。我们只要在Spring容器中配置这个对象,即可使用。

​ 在Spring容器中添加事务管理的配置:

  1. xml配置文件
1
2
3
4
5
6
7
8
<!-- 配置Spring提供的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启注解事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
  1. 注解方式的配置

    ​ 在主配置类上标注一个@EnableTransactionManagement注解,来开启基于注解的事务管理功能,为需要实现的方法添加上事务,那么就得使用@Transactional注解。如:

    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
    package com.xxxx.tx;

    import javax.sql.DataSource;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    import com.mchange.v2.c3p0.ComboPooledDataSource;

    @EnableTransactionManagement // 它是来开启基于注解的事务管理功能的
    @ComponentScan("com.xxxx.tx")
    @Configuration
    public class TxConfig {

    // 注册c3p0数据源
    @Bean
    public DataSource dataSource() throws Exception {
    ComboPooledDataSource dataSource = new ComboPooledDataSource();
    dataSource.setUser("root");
    dataSource.setPassword("liayun");
    dataSource.setDriverClass("com.mysql.jdbc.Driver");
    dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
    return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate() throws Exception {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
    return jdbcTemplate;
    }

    }