第5章Spring的事务管理
学习目的与要求
本章主要介绍了Spring框架所支持的事务管理,包括编程式事务管理和声明式事务
管理。通过本章的学习,要求读者掌握声明式事务管理,了解编程式事务管理。
本章主要内容
.Spring的数据库编程
.编程式事务管理
.声明式事务管理
在数据库操作中,事务管理是一个重要的概念。例如,银行转账,当从A账户向B
账户转1000元后,银行的系统将从A账户上扣除1000元,而在B账户上加1000元,这
是正常处理的结果。
一旦银行系统出错了怎么办?这里假设发生两种情况:
(1)A账户少了1000元,但B账户却没有多1000元。
(2)B账户多了1000元钱,但A账户却没有被扣钱。
客户和银行都不愿意发生上面两种情况。那么有没有措施保证转账顺利进行?这种措
施就是数据库事务管理机制。
Spring的事务管理简化了传统的数据库事务管理流程,提高了开发效率。在学习事务
管理之前,需要了解Spring的数据库编程。
5.1 Spring的数据库编程
数据库编程是互联网编程的基础,Spring框架为开发者提供了JDBC模板模式,即
jdbcTemplate,它可以简化许多代码,但在实际应用中jdbcTemplate并不常用,在工作中
更多的是使用Hibernate框架和MyBatis框架进行数据库编程。
本节简要介绍Spring jdbcTemplate的使用方法,对于MyBatis框架的相关内容将在第
14章详细介绍,对于Hibernate框架本书不再涉及,需要的读者可以查阅Hibernate框架的
相关知识。
XX5.1.1 Spring JDBC的配置
本节Spring的数据库编程主要使用Spring JDBC模块的core包和dataSource包。core
包是JDBC的核心功能包,包括常用的JdbcTemplate类;dataSource包是访问数据源的工
具类包。使用Spring JDBC操作数据库,需要对其进行配置。配置文件的示例代码如下:
扫一扫
视频讲解
在上述示例代码中,配置JDBC模板时,需要将dataSource注入jdbcTemplate,而在
数据访问层(Dao层)需要使用jdbcTemplate时,也需要将jdbcTemplate注入对应的Bean
中。示例代码如下:
…
@Repository("testDao")
public class TestDaoImpl implements TestDao{
@Autowired
//使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
…
}
XX5.1.2 Spring jdbcTemplate的使用方法
在获取JDBC模板后如何使用它是本节将要讲述的内容。首先需要了解jdbcTemplate
类的常用方法,该类的常用方法是update()和query()方法。
(1)public int update(String sql,Object args[]):该方法可以对数据表进行增加、修改、
删除等操作。使用args[]设置SQL语句中的参数,并返回更新的行数。示例代码如下:
String insertSql = "insert into user values(null,?,?)";
Object param1[] = {"chenheng1", "男"};
jdbcTemplate.update(sql, param1);
(2)public List query (String sql, RowMapper rowMapper, Object args[]):该方法
可以对数据表进行查询操作。rowMapper将结果集映射到用户自定义的类中(前提是自定
义类中的属性与数据表的字段对应)。示例代码如下:
String selectSql ="select * from user";
RowMapper rowMapper = new BeanPropertyRowMapper(MyUser.
class);
List list = jdbcTemplate.query(sql, rowMapper, null);
下面通过一个实例演示Spring JDBC的使用过程。
【例5-1】 使用Spring JDBC访问数据库。
A. 创建模块并导入JAR包
创建名为ch5的项目,然后在ch5项目中创建一个名为ch5_1的模块,同时给ch5_1
模块添加Web Application,并将Spring的4个基础包、Spring Commons Logging Bridge
对应的JAR包spring-jcl-6.0.0.jar和spring-aop-6.0.0.jar、MySQL数据库的驱动程序JAR
包(mysql-connector-java-8.0.29.jar)、Spring JDBC的JAR包(spring-jdbc-6.0.0.jar)、Java
增强库(lombok-1.18.24.jar)以及Spring事务管理的JAR包(spring-tx-6.0.0.jar)复制到
ch5_1的WEB-INF/lib目录中,添加为模块依赖,如图5.1所示。
图5.1 ch5_1模块所依赖的JAR包
B. 创建并编辑配置文件
在ch5_1模块的src目录中创建配置文件applicationContext.xml,并在该文件中配置
数据源和JDBC模板,具体代码如下:
C. 创建实体类
在ch5_1模块的src目录中创建名为entity的包,并在该包中创建实体类MyUser。该
类的属性与数据表user中的字段一致。数据表user的结构如图5.2所示。
图5.2 数据表user的结构
实体类MyUser的代码如下:
package entity;
import lombok.Data;
/**
* 使用@Data注解实体属性无须Setter和Getter方法,但需要给IDEA安装Lombok 插件(安
* 装后重构模块)
*/
@Data
public class MyUser {
private Integer uid;
private String uname;
private String usex;
public String toString() {
retu rn "myUser [uid=" + uid +", uname=" + uname + ", usex=" +
usex + "]";
}
}
D. 创建数据访问层Dao
在ch5_1模块的src目录中创建名为dao的包,并在dao包中创建TestDao接口和
TestDaoImpl实现类。在实现类TestDaoImpl中使用JDBC模板jdbcTemplate访问数据库,
并将该类注解为@Repository("testDao")。
TestDao接口的代码如下:
package dao;
import java.util.List;
import entity.MyUser;
public interface TestDao {
public int update(String sql, Object[] param);
public List query(String sql, Object[] param);
}
TestDaoImpl实现类的代码如下:
package dao;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
import entity.MyUser;
@Repository("testDao")
public class TestDaoImpl implements TestDao{
@Autowired
//使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
/**
* 更新方法,包括添加、修改、删除
* param为sql中的参数,例如通配符?
*/
@Override
public int update(String sql, Object[] param) {
return jdbcTemplate.update(sql, param);
}
/**
* 查询方法
* param为sql中的参数,例如通配符?
*/
@Override
public List query(String sql, Object[] param) {
RowM apper rowMapper = new BeanPropertyRowMapper
(MyUser.class);
return jdbcTemplate.query(sql, rowMapper, param);
}
}
E. 创建测试类
在ch5_1模块的src目录中创建名为test的包,并在该包中创建测试类TestSpringJDBC。
在主方法中调用数据访问层Dao中的方法,对数据表user进行操作。具体代码如下:
package test;
import java.util.List;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import dao.TestDao;
import entity.MyUser;
public class TestSpringJDBC {
private static ApplicationContext appCon;
public static void main(String[] args) {
appC on = new ClassPathXmlApplicationContext
("applicationContext.xml");
TestDao td = (TestDao)appCon.getBean("testDao");
String insertSql = "insert into user values(null,?,?)";
//数组param的值与insertSql语句中的?一一对应
Object param1[] = {"chenheng1", "男"};
Object param2[] = {"chenheng2", "女"};
Object param3[] = {"chenheng3", "男"};
Object param4[] = {"chenheng4", "女"};
//添加用户
td.update(insertSql, param1);
td.update(insertSql, param2);
td.update(insertSql, param3);
td.update(insertSql, param4);
//查询用户
String selectSql ="select * from user";
List list = td.query(selectSql, null);
for(MyUser mu: list) {
System.out.println(mu);
}
}
}
运行上述测试类,运行结果如图5.3所示。
图5.3 Spring数据库编程的运行结果
5.2 编程式事务管理
在代码中显式调用beginTransaction()、commit()、rollback()等与事务管理相关的方法,
这就是编程式事务管理。当只有少数事务操作时,采用编程式事务管理比较合适。
XX5.2.1 基于底层API的编程式事务管理
基于底层API的编程式事务管理就是根据PlatformTransactionManager、TransactionDefinition
和TransactionStatus 3个核心接口通过编程的方式来进行事务管理。
下面通过一个实例讲解基于底层API的编程式事务管理。
【例5-2】 基于底层API的编程式事务管理。
A. 创建模块并导入JAR包
在ch5项目中创建一个名为ch5_2的模块,同时给ch5_2模块添加Web Application,
并将Spring的4个基础包、Spring Commons Logging Bridge对应的JAR包spring-jcl-
6.0.0.jar和spring-aop-6.0.0.jar、MySQL数据库的驱动程序JAR包(mysql-connector-java-
8.0.29.jar)、Spring JDBC的JAR包(spring-jdbc-6.0.0.jar)以及Spring事务管理的JAR
包(spring-tx-6.0.0.jar)复制到ch5_2的WEB-INF/lib目录中,添加为模块依赖。
B. 给数据源配置事务管理器
在ch5_2模块的src目录中创建配置文件applicationContext.xml,并在配置文件中使
用PlatformTransactionManager接口的一个间接实现类DataSourceTransactionManager为数
据源添加事务管理器,具体配置代码如下:
扫一扫
视频讲解
C. 创建数据访问类
在ch5_2模块的src目录中创建名为dao的包,在该包中创建数据访问类CodeTransaction,
并使用@Repository("codeTransaction")注解为数据访问层。在CodeTransaction类中使用编
程的方式进行数据库的事务管理。
CodeTransaction类的代码如下:
package dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
@Repository("codeTransaction")
public class CodeTransaction {
@Autowired
//使用配置文件中的JDBC模板
private JdbcTemplate jdbcTemplate;
//依赖注入事务管理器txManager(配置文件中的Bean)
@Autowired
private DataSourceTransactionManager txManager;
public void test() {
//默认事务定义,例如隔离级别、传播行为等
TransactionDefinition tf = new DefaultTransactionDefinition();
//开启事务ts
TransactionStatus ts = txManager.getTransaction(tf);
//删除表中的数据
String sql = " delete from user ";
//添加数据
String sql1 = " insert into user values(?,?,?) ";
Object param[] = { 1, "陈恒", "男" };
try {
//删除数据
jdbcTemplate.update(sql);
//添加一条数据
jdbcTemplate.update(sql1, param);
//添加相同的一条数据,使主键重复
jdbcTemplate.update(sql1, param);
//提交事务
txManager.commit(ts);
System.out.println("执行成功,没有事务回滚!");
} catch (Exception e) {
System.out.println("主键重复,事务回滚!");
}
}
}
D. 创建测试类
在ch5_2模块的src目录中创建名为test的包,并在该包中创建测试类TestCodeTransaction,
具体代码如下:
package test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import dao.CodeTransaction;
public class TestCodeTransaction {
private static ApplicationContext appCon;
public static void main(String[] args) {
appC on = new ClassPathXmlApplicationContext
("applicationContext.xml");
Code Transaction ct = (CodeTransaction)appCon.getBean
("codeTransaction");
ct.test();
}
}
上述测试类的运行结果如图5.4所示。
图5.4 基于底层API的编程式事务管理的测试结果
从程序运行前后数据表中的数据可以看出,取消了主键重复前执行的删除和插入操
作,即事务回滚。
XX5.2.2 基于TransactionTemplate的编程式事务管理
事务管理的代码散落在业务逻辑代码中,破坏了原有代码的条理性,并且每一个业务
方法都包含了类似的启动事务、提交以及回滚事务的样板代码。
TransactionTemplate的execute()方法有一个TransactionCallback接口类型的参数,该接
口中定义了一个doInTransaction()方法,通常以匿名内部类的方式实现TransactionCallback
接口,并在其doInTransaction()方法中书写业务逻辑代码。这里可以使用默认的事务提交
和回滚规则,在业务代码中不需要显式调用任何事务管理的API。doInTransaction()方法有
一个TransactionStatus类型的参数,可以在方法的任何位置调用该参数的setRollbackOnly()
方法将事务标识为回滚,以执行事务回滚。
根据默认规则,如果在执行回调方法的过程中抛出了未检查异常,或者显式调用了
setRollbackOnly()方法,则回滚事务;如果事务执行完成或者抛出了checked类型的异常,
则提交事务。
【例5-3】 基于TransactionTemplate的编程式事务管理。
A. 创建模块并导入JAR包
在ch5项目中创建一个名为ch5_3的模块,同时给ch5_3模块添加Web Application,
并将ch5_2模块的lib复制到ch5_3模块的WEB-INF目录中,添加为模块依赖。
B. 为事务管理器添加事务模板
在ch5_3模块的src目录中创建配置文件applicationContext.xml,在配置文件中使用
org.springframework.transaction.support.TransactionTemplate类为事务管理器添加事务模板,
具体配置代码如下:
C. 创建数据访问类
在ch5_3模块的src目录中创建名为dao的包,并在该包中创建数据访问类
TransactionTemplateDao,同时注解为@Repository("transactionTemplateDao")。在
TransactionTemplateDao类中使用编程的方式进行数据库的事务管理。
数据访问类TransactionTemplateDao的代码具体如下:
package dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
@Repository("transactionTemplateDao")
public class TransactionTemplateDao {
//依赖注入JDBC模板
@Autowired
private JdbcTemplate jdbcTemplate;
//依赖注入transactionTemplate
@Autowired
private TransactionTemplate transactionTemplate;
public void test() {
//以匿名内部类的方式实现TransactionCallback接口,使用默认的事务提交和回滚
//规则,在业务代码中不需要显式调用任何事务处理的API
transactionTemplate.execute(new TransactionCallback