Spring事务管理
Spring框架所支持的事务管理,包括编程式事务管理和声明式事务管理。
一、Spring的数据库编程
数据库编程式互联网编程的基础,Spring框架为开发者提供了JDBC模板模式,即jdbcTemplate,它可以简化许多代码,但实际应用并不常用,在工作中更多的时候是使用hibernate框架和mybatis框架进行数据库编程。但是也可以了解一下。
1. Spring JDBC配置
主要使用Spring JDBC模块的core和dataSource包。core包是JDBC的核心功能包,包括常用的jdbcTemplate类;dataSource包是访问数据源的工具类包。
实例代码:
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--mysql数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的url-->
<property name="url" value="jdbc:mysql://localhost:3306/test0010?characterEncoding=UTF-8" />
<!--连接数据库的用户名-->
<property name="username" value="root"/>
<!--连接数据库的密码-->
<property name="password" value="root"/>
</bean>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
2.Spring JdbcTemplate的常用方法
jdbcTemplate类常用的方法是update 和 query
- public int update(String sql, Object args[])
更新方法,包括添加、修改、删除 - public List query(String sql, RowMapper rowMapper,Object args[])
查询方法,rowMapper将结果集映射到用户自定义的类中(自定义类中的属性要与数据表字段对应)
3.实例演示
1. 创建应用并导入jar包
出了spring的五大基础jar包,还需要spring的jdbc jar包和数据库连接jar包
spring-jdbc-5.2.3.RELEASE.jar
mysql-connector-java-5.1.47.jar
2. 创建并编辑配置文件
见:Spring JDBC配置
3. 创建实体类
在项目下添加jdbctemplate包,并创建entity存放实体类的包,创建dao存放数据传输层包。
数据库创建user表:
entity包下创建User实体类
public class User {
private int id;
private String name;
private String sex;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
4. 创建数据传输层
在dao包下创建UserDdao接口和实现类,并添加update 和 query方法用来演练jdbc的两个常用方法
接口:
public interface UserDao {
public int update(String sql, Object[] param);
public List<User> query(String sql, Object[] param);
}
实现类:
@Repository
public class UserDaoImpl implements UserDao {
//自动注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 更新方法,包括添加、修改、删除
* param为SQL中的参数,例如通配符
*/
@Override
public int update(String sql, Object[] param) {
return jdbcTemplate.update(sql,param);
}
/**
* 查询方法
* param为SQL中的参数,例如通配符
*/
@Override
public List<User> query(String sql, Object[] param) {
/*
* rowMapper将结果集映射到用户自定义的类中(自定义类中的属性要与数据表字段对应)
*/
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql,rowMapper,param);
}
}
5.创建测试类
public class SpringTest {
public static void main(String[] args) {
//初始化Spring容器的ApplicationContext,加载配置文件
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
//从容器中获取Bean
UserDao userDao = (UserDao) applicationContext.getBean("userDaoImpl");
//插入用户
String insertSql = "insert into user value(NULL,?,?)";
Object param[] = {"lisi", "男"};
if (userDao.update(insertSql, param) == 1) {
System.out.println("插入数据成功");
}else {
System.out.println("插入数据失败");
}
System.out.println("==================");
//查询用户
System.out.println("查询的用户");
String sql = " select * from user ";
List<User> users = userDao.query(sql, null);
for (User user : users) {
System.out.println(user);
}
}
}
6.测试结果
一个简单的jdbcTemplate使用成功。
二、编程式事务管理(了解)
在代码中显式调用beginTransaction、commit、rollback等与事务处理相关的方法,这就是编程式事务管理。只有少数事务操作时,编程式事务管理才比较合适。
2.1 基于底层API的编程式事务管理
基于底层API的编程式事务管理就是根据PlatformTransactionManager 、TransactionDefinition 和 TransactionStatus 几个核心接口,通过编程的方式来进行事务处理。
步骤 :
1. 给数据源配置事务管理器
使用PlatformTransactionManager接口的实现类org.springframework.jdbc.datasource.DataSourceTransactionManager为数据源添加事务管理器。
<!--为数据源添加事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--扫描注解包-->
<context:component-scan base-package="main.java.transaction"/>
2.创建数据访问类
在项目中创建transaction包并CodeTransaction,并注入spring容器。
@Repository
public class CodeTransaction {
//自动注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
// 自动注入dataSourceTransactionManager
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
public String test() {
//默认事务定义,例如隔离级别、传播行为等
TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
//开启事务transactionDefinition
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
String message = "执行成功,没有事务回滚";
try {
//删除表中数据
String sql1 = "delete from user";
//添加数据
String sql2 = "insert into user value(?,?,?)";
Object param[] = {1, "zhangsan", "女"};
//查询表中所有数据
String sql3 = "select * from user";
//表中初始数据
System.out.println("----------初始表中数据----------");
queryAll(sql3);
//删除数据
jdbcTemplate.update(sql1);
System.out.println("----------删除数据成功------------");
queryAll(sql3);
//添加一条数据
jdbcTemplate.update(sql2, param) ;
System.out.println("----------添加一条数据成功----------");
queryAll(sql3);
//添加相同一条数据,使主键重复
jdbcTemplate.update(sql2, param);
dataSourceTransactionManager.commit(transactionStatus);
} catch (Exception e) {
dataSourceTransactionManager.rollback(transactionStatus);
message = "主键重复,事务回滚";
//查询表中所有数据
String sql3 = "select * from user";
System.out.println("----------事务回滚后数据----------");
queryAll(sql3);
e.printStackTrace();
}
return message;
}
public void queryAll(String sql){
System.out.println("查询数据库数据");
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
if (users.size() != 0) {
for (User user : users) {
System.out.println(user);
}
}else{
System.out.println("表中没有数据");
}
}
}
3.测试类进行测试
public class SpringTest {
public static void main(String[] args) {
//初始化Spring容器的ApplicationContext,加载配置文件
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
CodeTransaction codeTransaction = (CodeTransaction) applicationContext.getBean("codeTransaction");
String result = codeTransaction.test();
System.out.println(result);
}
}
4 .测试结果
由结果可以看出数据库初始数据和主键重复后数据一致,取消了主键重复前执行的删除和插入操作。
2.2 基于TransactionTemplate的编程式事务管理
事务处理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务方法都包含了类似的启动事务、提交以及回滚事务的样板代码。
1. 为事务管理器添加事务模板
在2.1基于底层API编程模式的事务管理器的基础上添加模板
transactionTemplate的execute方法有一个TransactionCallback接口类型的参数,该接口中定义了一个doInTransaction方法,通常以匿名内部类的方式实现TransactionCallback接口,并在doInTransaction方法中书写业务逻辑代码。doInTransaction方法有一个TransactionStatus类型的参数,可以在方法的任何位置调用该参数的setRollbackOnly()方法将事务标识为回滚,以执行事务回滚。
<!--为事务管理器txManager创建transactionTemplate-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"></property>
</bean>
2.创建数据访问类
在transaction包下创建TransactionTemplateDao类,并注入spring容器中。
@Repository
public class TransactionTemplateDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
String message = "";
public String test() {
//以匿名内部类的方式实现TransactionCallback接口,使用默认的事务提交和回滚规则,在业务代码中不需要显示调用任何事务处理的API
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
//删除表中数据
String sql1 = "delete from user";
//添加数据
String sql2 = "insert into user value(?,?,?)";
Object param[] = {1, "zhangsan", "女"};
//查询表中所有数据
String sql3 = "select * from user";
try{
//表中初始数据
System.out.println("----------初始表中数据----------");
queryAll(sql3);
//删除数据
jdbcTemplate.update(sql1);
System.out.println("----------删除数据成功------------");
queryAll(sql3);
//添加一条数据
jdbcTemplate.update(sql2, param) ;
System.out.println("----------添加一条数据成功----------");
queryAll(sql3);
//添加相同一条数据,使主键重复
jdbcTemplate.update(sql2, param);
message = "执行成功,没有事务回滚";
} catch(Exception e){
message = "主键重复,事务回滚";
transactionStatus.setRollbackOnly();//异常后不能回滚,添加手动回滚,execute执行完进行回滚
e.printStackTrace();
}
return message;
}
});
//查询表中所有数据
String sql3 = "select * from user";
System.out.println("----------事务回滚后数据----------");
queryAll(sql3);
return message;
}
public void queryAll(String sql){
System.out.println("查询数据库数据");
List<User> users = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
if (users.size() != 0) {
for (User user : users) {
System.out.println(user);
}
}else{
System.out.println("null");
}
}
}
测试类进行测试
public class SpringTest {
public static void main(String[] args) {
//初始化Spring容器的ApplicationContext,加载配置文件
ApplicationContext applicationContext =new ClassPathXmlApplicationContext("applicationContext.xml");
//CodeTransaction codeTransaction = (CodeTransaction) applicationContext.getBean("codeTransaction");
TransactionTemplateDao transactionTemplateDao = (TransactionTemplateDao) applicationContext.getBean("transactionTemplateDao");
String result = transactionTemplateDao.test();
System.out.println(result);
}
}
测试结果与2.1的结果一样,这就是使用事务模板的方式。
三、声明式事务管理
Spring的声明式事务管理是通过AOP技术实现的事务管理,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务最大的优点是不需要通过编程的方式管理事务,因而不需要在业务逻辑代码中掺杂事务处理的代码,只需要相关的事务规则声明便可以将事务规则应用到业务逻辑中。
与编程式事务管理相比,声明式事务唯一不足的地方是最细粒度只能作用到方法级别,无法做到像编程式事务管理那样可以作用到代码块级别。
Spring的声明式事务管理可以通过两种方式来实现:
- 基于xml的方式
- 基于@Transaction注解的方式
3.1 基于XML方式的声明式事务管理
基于xml方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。Spring提供了tx命名空间来配置事务的,提供了<tx:advice>元素来配置事务的通知。
1.导入jar包
方便练习,导入所有jar包,tx命名空间需要txjar包
xml文件中加入命名空间规则等:
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
。。。。
2.创建Dao层
在transaction包下创建xmlstatement.dao包,并创建UserXmlDao接口和实现类
接口:
public interface UerXmlDao {
public int update(String sql, Object[] param);
public List<User> query(String sql, Object[] param);
}
实现类:
@Repository
public class UserXmlDaoImpl implements UerXmlDao {
//自动注入jdbcTemplate
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 更新方法,包括添加、修改、删除
* param为SQL中的参数,例如通配符
*/
@Override
public int update(String sql, Object[] param) {
return jdbcTemplate.update(sql,param);
}
/**
* 查询方法
* param为SQL中的参数,例如通配符
*/
@Override
public List<User> query(String sql, Object[] param) {
/*
* rowMapper将结果集映射到用户自定义的类中(自定义类中的属性要与数据表字段对应)
*/
RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
return jdbcTemplate.query(sql,rowMapper,param);
}
}
3.创建service层
在transaction包下创建xmlstatement.service包,并创建UserXmlService接口和实现类,在service层依赖注入数据访问层
接口:
public interface UserXmlService {
public void testXmlStatement();
}
``
实现类:
```java
@Service
public class UserXmlServiceImpl implements UserXmlService {
@Autowired
private UerXmlDao uerXmlDao;
@Override
public void testXmlStatement() {
String selectAllSql = "select * from user";
String deleteSql = "delete from user";
String saveSql = "insert into user values(?,?,?)";
Object []params = {1, "zhangsan", "男"};
try {
System.out.println("----------初始数据----------");
this.selectAllUsers(selectAllSql);
System.out.println("----------清空数据库数据----------");
uerXmlDao.update(deleteSql, null);
this.selectAllUsers(selectAllSql);
System.out.println("----------插入一条数据----------");
uerXmlDao.update(saveSql, params);
this.selectAllUsers(selectAllSql);
//重复主键
uerXmlDao.update(saveSql, params);
}catch (Exception e){
System.out.println("主键重复,事务回滚");
//spring 在抛出RuntimeException才自动回滚
throw new RuntimeException();
}
}
public void selectAllUsers(String selectAllSql) {
List<User> users = uerXmlDao.query(selectAllSql, null);
if (users.size() != 0) {
for (User user : users) {
System.out.println(user);
}
}else{
System.out.println("表中没有数据");
}
}
}
4.创建controller层
在在transaction包下创建xmlstatement.controller包,并创建控制器类UserXmlController ,并依赖注入service层
@Controller
public class UserXmlController {
@Autowired
private UserXmlService userXmlService;
public void testXmlStatement() {
userXmlService.testXmlStatement();
}
}
5.xml文件配置
在配置文件中使用<tx:advice>编写通知声明事务,使用<aop:config>编写AOP让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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-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/tx
http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="main.java.jdbctemplate.dao"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--mysql数据库驱动-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<!--连接数据库的url-->
<property name="url" value="jdbc:mysql://localhost:3306/test0010?characterEncoding=UTF-8" />
<!--连接数据库的用户名-->
<property name="username" value="root"/>
<!--连接数据库的密码-->
<property name="password" value="root"/>
</bean>
<!--配置jdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--为数据源添加事务管理器-->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--扫描注解包-->
<context:component-scan base-package="main.java.transaction"/>
<!--为事务管理器txManager创建transactionTemplate-->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="txManager"></property>
</bean>
<!--编写通知声明事务,关联txManager事务管理器-->
<tx:advice id="MyXmlStatement" transaction-manager="txManager">
<tx:attributes>
<!--*表示任意方法,del* 表示以*开头的任意方法,Spring默认Unchecked Exception时自动回滚(包括Error与RuntimeException),即未使用try catch语句,则可不配置rollback-for-->
<tx:method name="*" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!--编写AOP,让Spring自动对目标对象生成代理,需要使用AspectJ的表达式-->
<aop:config>
<!--定义切入点-->
<aop:pointcut id="txPointCut" expression="execution(* main.java.transaction.xmlstatement.service.*.*())"/>
<!--切面:将切入点与通知关联-->
<aop:advisor advice-ref="MyXmlStatement" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
6.测试类进行测试
public class SpringTest {
public static void main(String[] args) {
//初始化Spring容器的ApplicationContext,加载配置文件
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserXmlController userXmlController = (UserXmlController) applicationContext.getBean("userXmlController");
userXmlController.testXmlStatement();
}
}
7.测试结果
测试结果与前几个方式相同。
3.2 基于@Transactional注解的声明式事务管理
@Transactional注解可以作用于接口、接口方法、类以及类的方法上。当作用于类上时,所以的public方法都将具有该类型的事务属性,同时也可以在方法级别上使用该注解来覆盖类级别的定义。
1. xml文件配置
<!--为事务管理器注册侧注解驱动器-->
<tx:annotation-driven transaction-manager="txManager"/>
2.为service层添加@Transactional注解
在spring mvc中常通过service层进行事务管理,因此在service层添加@Transactional注解
在UserXmlServiceImpl类上添加注解
@Transactional(rollbackFor = {Exception.class})
//加上@Transactional,就可以制定这个类需要受spring的事务管理
//注意@Transactional只能针对public属性范围的方法添加
3.测试类中进行测试
测试类与xml方式相同,其他代码也相同,结果也相同
四、如何在事务中捕捉异常
4.1基于xml方式的声明式事务管理中捕获异常
Spring默认对Unchecked Exception时自动回滚(包括Error与RuntimeException),即未使用try catch语句,当我们不需要使用try cathch语句,则可不配置rollback,否则配置rollback-for=“java.lang.Exception” 并且在catch 块中添加throw new RuntimeException()
<tx:method name="*" rollback-for="java.lang.Exception" />
catch (Exception e){
System.out.println("主键重复,事务回滚");
//spring 在抛出RuntimeException才自动回滚
throw new RuntimeException();
// 或者使用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
4.2 基于注解方式的声明式事务管理中捕获异常
- 在Transactional中使用rollbackFor 属性
@Transactional(rollbackFor = {Exception.class})
- 在catch 块中throw new RuntimeException()
4.3 之间在catch代码块中添加
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();