0%

【Spring】声明式事务

通过Spring配置事物管理

一、引出事务错误

1.1 运行环境

  • 此例子是以Spring 和 Mybatis整合的方式进行举例
  • Maven依赖导入
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>transaction</artifactId>
<version>1.0-SNAPSHOT</version>

<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>

<!--spring-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

<!--spring操作数据库-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>

<!--spring AOP-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>

<!--注解实现JavaBean-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>

<!--Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>

<!--Mybatis 与 Spring 整合适配包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.2</version>
</dependency>

</dependencies>

<!--运行环境-->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<!--Maven打包Java项目下的xml文件-->
<build>
<resources>
<resource>
<!--资源所在目录-->
<directory>src/main/java</directory>
<!--扫描的内容:xx.properties和xx.xml文件-->
<includes>
<include>**/*.properties</include><!--扫描properties文件-->
<include>**/*.xml</include><!--扫描xml文件-->
</includes>
<!--是否启动过滤器:选定扫描的内容已经达到了过滤效果-->
<filtering>false</filtering>
</resource>
</resources>
</build>

</project>

1.2 各包内容

  • com.letere.bean包
1
2
3
4
5
6
7
8
9
10
JavaBean:通过引入Lombok包进行快速创建
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
private Integer id;
private String lastName;
private char gender;
private String email;
}
  • com.letere.dao包
1
2
3
4
5
6
7
8
9
10
接口
public interface EmployeeMapper {
//增
int addEmp(Employee employee);
//删
int deleteEmpById(Integer id);

//方法整合
int transationTest();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
映射文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.letere.dao.EmployeeMapper">
<insert id="addEmp">
insert into tbl_employee(id, last_name, gender, email)
values (#{id}, #{lastName}, #{gender}, #{email})
</insert>

<delete id="deleteEmpById">
delete from tbl_employee
where id=#{id}
</delete>
</mapper>
  • con.letere.service包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Service层实现类
public class EmployeeService extends SqlSessionDaoSupport implements EmployeeMapper {

@Override
public int addEmp(Employee employee) {
return getSqlSession().getMapper(EmployeeMapper.class).addEmp(employee);
}

@Override
public int deleteEmpById(Integer id) {
return getSqlSession().getMapper(EmployeeMapper.class).deleteEmpById(id);
}

@Override
public int transationTest() {
addEmp(new Employee(null, "虾米", '1', "routui.com"));//增加员工

int a = 10/0; //故意填写错误,使其抛出异常

deleteEmpById(8);//删除员工
return 0;
}
}

1.3 配置文件

1
2
3
4
5
JDBC连接配置文件
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=123
1
2
3
4
5
6
7
8
9
10
11
12
13
Mybatis全局配置文件(可选)
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

<settings>
<!--启用驼峰命名-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

</configuration>
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
Spring-dao配置文件
<?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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

<!--引入数据库连接配置文件-->
<context:property-placeholder location="classpath:jdbc-config.properties" />

<!--数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>

<!--sqlSessionFactory
省去了Mybatis中需要去new sqlSessionFactory().build(inpuyStream)步骤,通过Spring进行属性注入
-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="dataSource"/> <!--关联数据源-->
<property name="configLocation" value="classpath:mybatis-config.xml"/> <!--绑定配置文件-->
<property name="mapperLocations" value="classpath*:com/letere/dao/*.xml"/> <!--指定sql映射文件的位置,默认为借口所在位置-->
</bean>
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Spring整合配置文件
<?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">

<import resource="classpath:spring-dao.xml"/>

<!--属性注入Service层实现类-->
<bean class="com.letere.service.EmployeeService" id="employeeService">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

</beans>

1.4 测试方法

1
2
3
4
5
6
7
8
9
10
11
12
测试方法
//故意制造事务错误
@Test
public void transationTest(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

EmployeeMapper mapper = context.getBean("employeeService", EmployeeMapper.class);

mapper.transationTest();

//在执行完插入操作后出现异常,但是插入仍然成功,这是事务所不想看到的
}

二、xml配置文件实现

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
Spring整合配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://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 https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--导入spring-dao配置文件,进行进行管理-->
<import resource="classpath:spring-dao.xml"/>

<bean class="com.letere.service.EmployeeService" id="employeeService">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<!--1、创建事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>

<!--2、配置通知:切入的方法-->
<tx:advice transaction-manager="transactionManager" id="txAdvice">
<!--给方法配置事务
name:方法名字,可以直接用*表示所有方法
propagation:传播特性,默认:REQUIRED
REQUIRED : 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
SUPPORTS : 支持当前事务,如果当前没有事务,就以非事务方式执行。
MANDATORY : 支持当前事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW : 新建事务,如果当前存在事务,把当前事务挂起。
SUPPORTED : 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
NEVER : 以非事务方式执行,如果当前存在事务,则抛出异常。
NESTED : 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
-->
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/><!--所有方法-->
</tx:attributes>
</tx:advice>

<!--3、事务切入-->
<aop:config>
<!--配置切入点-->
<aop:pointcut id="txPointCut" expression="execution(* com.letere.service.*.*(..))"/><!--Service层下所有类所有方法-->
<!--切入-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>

</beans>
  • Spring配置事物利用了AOP层的原理,所有配置切入点以及切面
  • 配置完后再执行测试方法,会发现插入不成功了,实现了声明式事务

三、注解实现

  • 由于xml配置过于繁琐,开发中一般使用注解实现声明式事务
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
Spring整合配置文件
<?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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context https://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 https://www.springframework.org/schema/aop/spring-aop.xsd">

<!--导入spring-dao配置文件,进行进行管理-->
<import resource="classpath:spring-dao.xml"/>

<bean class="com.letere.service.EmployeeService" id="employeeService">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

<!--1、创建事务管理器-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>

<!--2、开启事务注解:让Spring能够识别事务注解-->
<tx:annotation-driven transaction-manager="transactionManager"/>

<!--3、在Service类上面 或 在Service类中方法上添加事务注解
@Transactionnal
注解添加到类中:类中所有方法具有事务
注解添加到方法上:只有该方法上具有事务
-->

<!--4、可选:在@Transactionnal中添加参数
propagation:传播行为
参数为Propagation.xxx,是一个枚举类
isolation:事务隔离级别
参数为Isolation.xxx,是一个枚举类
timeout:超时时间
默认值为-1,不超时,设置超时时间以秒为单位
readOnly:是否只读
默认false,true/fasle
rollbackFor:回滚
设置出现哪些异常会进行事务回滚
noRollbackFor:不回滚
设置出现哪些异常不会进行事务回滚
-->
</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
将事务注解声明在类上
@Transactionnal
public class EmployeeService extends SqlSessionDaoSupport implements EmployeeMapper {

@Override
public int addEmp(Employee employee) {
return getSqlSession().getMapper(EmployeeMapper.class).addEmp(employee);
}

@Override
public int deleteEmpById(Integer id) {
return getSqlSession().getMapper(EmployeeMapper.class).deleteEmpById(id);
}

@Override
public int transationTest() {
addEmp(new Employee(null, "虾米", '1', "routui.com"));//增加员工

int a = 10/0; //故意填写错误,使其抛出异常

deleteEmpById(8);//删除员工
return 0;
}
}

三、纯注解开发

  • 纯注解是在注解开发上的拓展
  • 演示只配置与声明式事务有关的配置,不是全部xml文件都转为配置类
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
配置类
@Configuration//配置类
@ComponentScan(basePackages = "com.letere.service")//组建扫描
@EnableTransactionManagement//开启事务
public class TxConfig {

//创建数据源
@Bean
public DriverManagerDataSource getDataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8");
dataSource.setUsername("root");
dataSource.setPassword("123");
return dataSource;
}

//创建事务管理器
@Bean
public DataSourceTransactionManager getTransactionManager(DataSource datasource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(datasource);
return transactionManager;
}
}