Spring ---- 4
1. Spring中的JdbcTemplate
1.1. JdbcTemplate概述
是Spring框架提供的一个对象,是对原始JDBC API对象的封装,Spring提供了很多操作模板类:
- 操作关系型数据库的
JdbcTemplate HibernateTemplate
- 操作NoSQL数据库的
RedisTemplate
- 操作消息队列的
JmsTemplate
持久层的总图:
-
JdbcTemplate的作用:用于和数据库交互的,实现表的CRUD操作
-
如何创建对象
-
对象中的常用方法:
-
exqute
-
query
-
update
-
1.2. JdbcTemplate的简单使用
需求:在Account表中增加一条记录,Account表结构如下
- 在domain包下定义实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Double revenue;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getRevenue() {
return revenue;
}
public void setRevenue(Double revenue) {
this.revenue = revenue;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", revenue=" + revenue +
'}';
}
}
- 定义AccountDao接口
包括4个方法:根据id名查询,根据名称查询,查询所有以及更新账户
/**
* 持久层接口
*/
public interface AccountDao {
Account findById(Integer id);
Account findByName(String name);
List<Account> findAll();
void updateAccount(Account account);
}
- AccountDao接口的实现类AccountDaoImpl
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
// 通过set方法注入数据
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Account findById(Integer id) {
List<Account> res = jdbcTemplate.query("select * from account where id=?",
new BeanPropertyRowMapper<Account>(Account.class),
1);
if(res.isEmpty()) {
return null;
}else{
return res.get(0);
}
}
@Override
public Account findByName(String name) {
List<Account> res =jdbcTemplate.query("select * from account where name=?",
new BeanPropertyRowMapper<Account>(Account.class),
name);
if(res.isEmpty()){
return null;
}else if(res.size() > 1){
throw new RuntimeException("结果集不为1");
}else{
return res.get(0);
}
}
@Override
public List<Account> findAll() {
List<Account> res = jdbcTemplate.query("select * from account ",
new BeanPropertyRowMapper<Account>(Account.class));
return res;
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,revenue=? where id=?",
account.getName(), account.getRevenue(), account.getId());
}
}
-
jdbcTemplate的基本用法
-
准备数据源:使用Spring的内置数据源 DriverManagerDataSource,之前使用的是C3P0中的ComboPooledDataSource或者DBCP中的BasicDataSource来配置数据源
-
创建jdbcTemplate
-
给jdbcTemplate对象设置数据源
-
使用execute方法执行sql语句
-
public class demo1 {
public static void main(String[] args) {
DriverManagerDataSource dds = new DriverManagerDataSource();
dds.setDriverClassName("com.mysql.cj.jdbc.Driver");
dds.setUrl("jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false");
dds.setUsername("root");
dds.setPassword("123456");
JdbcTemplate jt = new JdbcTemplate();
jt.setDataSource(dds);
jt.execute("insert into account(name, revenue) values('周淑怡', 25248.32)");
}
}
1.3. 配置IoC容器获取对象
1.2中JdbcTemplate的使用存在的问题:对于JdbcTemplate和DriverrManagerDataSource对象的获取使用的是new的方式,类之间的耦合性较高,而通过配置jdbcTemplate对象和dataSource对象,让Spring来获取IoC容器帮助创建对象不需要再手动去new对象,降低类之间的耦合性。
- xml文件的配置
- 配置datasSource对象
- 配置jdbcTemplate
- 配置AccountDao对象,并且注入JdbcTemplate数据
<?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">
<!--配置jdbcTemplate对象 并且注入dataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置dataSource对象-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
- 测试类
public class demo2 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
jt.execute("insert into account(name, revenue) values('马飞飞', 27708.32)");
// 保存
jt.update("insert into account(money, revenue) values(?, ?)", "我兄弟", 4872);
// 更新
jt.update("update account set revenue=? where id=?", 47455, 5);
// 删除
jt.update("delete from account where id=?", 5);
// 查询所有
List<Account> res1 = jt.query("select * from account where revenue > ?",
new AccountRowMapper(),
27000d);
for(Account account : res1){
System.out.println(account);
}
System.out.println("=========================================");
List<Account> res2 = jt.query("select * from account where revenue > ?;",
new BeanPropertyRowMapper<Account>(Account.class),
30000d);
for(Account account : res2){
System.out.println(account);
}
// 查询一个
System.out.println("=============================================");
List<Account> res3 = jt.query("select * from account where id=?",
new BeanPropertyRowMapper<Account>(Account.class),
1);
System.out.println(res3.isEmpty() ? "没有内容" : res3.get(0));
// 查询返回一行一列(使用聚合函数但不使用group by子句)
System.out.println("==============================================");
Long res4 = jt.queryForObject("select count(*) from account where revenue > ?;",
Long.class, 30000d);
System.out.println("超过3W的人数:" + res4);
}
/**
* 定义account的封装策略
* 将结果集中的数据封装为account对象,由Spring把每个account加入到集合中
*/
public static class AccountRowMapper implements RowMapper<Account>{
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setRevenue(rs.getDouble("revenue"));
return account;
}
}
}
注意jdbcTempl中的query方法:
由于执行的是查询所有操作,满足条件的quey方法只有下面两个:
query(String sql, Object[] args, RowMappers<T> rowMapper)
第二个参数是输入参数数组,返回的结果是一个RowMapper类型的,适用于任何版本的JDK
query(String sql, RowMappers<T> rowMappers, Object.. args)
带有可变参数的query方法,针对于JDK1.5之后的,第二个参数为RowMapper接口,是一个泛型接口,需要重写其中的mapRow方法,也就是返回值Account的封装策略,因此只需要将结果集中的数据封装为account对象,由Spring把每个account加入到集合中,也就是测试类中的AccountRowMapper类
如果不想重写RowMapper的封装策略,可以使用类似于DBUtils中的QueryRunner对象中的query的第二个形参ResultSethandler<T>
, BeanPropertyRowMapper<>()来接受返回值并且进行封装,不同的是runner对象的query方法的返回值只有泛型,返回值取决于ResultSetHandler中的泛型T,而JdbcTemplate中的query方法的返回值可以是泛型或者是集合,返回值取决于方法的不同(方法的重载来决定)来返回集合或者某个泛型。
将查询方法的结果进行演示:
1.4. 配置AccountDao类使用jdbcTemplate
1.3.中存在的问题:操作表的sql语句是手动输入的,并且是在main方法中执行的,因此需要根据1.2中创建的AccountDao类进行IoC的配置
- XML中的配置
在1.3的基础上配置一个AccountDao对象,并且注入jdbcTemplate,将执行sql语句的操作放在持久层中进行执行
<?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">
<!--配置Account的持久层对象 并注入JdbcTemplate-->
<bean id="accountDao" class="com.hz.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置jdbcTemplate对象 并且注入dataSource-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置dataSource对象-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>
- 测试类
/**
* 加入持久层接口和实现类,用来执行操作表的操作
*/
public class demo3 {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
AccountDao accountDao = ac.getBean("accountDao", AccountDao.class);
List<Account> res = accountDao.findAll();
Account account1 = accountDao.findById(1);
Account account2 = accountDao.findByName("五五开");
System.out.println(res);
System.out.println(account1);
System.out.println(account2);
account2.setName("PDD");
account2.setRevenue(1234567d);
accountDao.updateAccount(account2);
}
}
- 打印输出结果
1.5. JdbcDaoSupport的使用以及两种dao的编写
1.4中存在的问题:如果存在多个dao接口的实现类,每一个类中都需要进行jdbcTemplate的定义和通过set方法注入
解决:== 定义一个jdbcDaoSupport类用于抽取dao类中的重复代码==
-
自定义jdbcDaoSupport类
-
该类定义了JdbcTemplate的获取:可以使用通过set方法注入
-
JdbcTemplate也可以通过传入一个DataSource对象来获取(通过JdbcTemplate源码的构造方法可知)
-
其中的createJdbcTemplate就是通过传入DataSource来获取jdbcTemplate对象
-
set方法的获取和通过传入ds获取jdbcTemplate的两种路径,有且只能执行一种
-
/**
* 此类用于抽取Dao实现类中的重复代码
* 如:AccountDaoImpl和AccountDaoImpl2中都出现的JdbcTemple对象的定义和set方法
*/
public class jdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void setDataSource(DataSource ds) {
if(jdbcTemplate == null){
this.jdbcTemplate = createJdbcTemplate(ds);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
- XML文件配置
只需要更改1.4中的accountDao对象的注入方式即可,其他的不需要动
<!--配置Account的持久层对象 并注入JdbcTemplate-->
<bean id="accountDao" class="com.hz.dao.impl.AccountDaoImpl">
<!--由于accountDao类继承了jdbcDaoSupport,可以通过直接创建ds来创建jdbcTemplate-->
<property name="dataSource" ref="dataSource"></property>
</bean>
测试类与1.4中的保持一致
当然可以使用Spring提供的JdbcDaoSupport,将dao层的实现类继承该类。
**作用:**去除注入和定义的重复代码,当存在多个dao层的impl,需要使用JdbcTemplate,不再需要在每一个impl中去定义和注入数据
继承类JdbcDaoSupport的dao类和不使用继承的dao类,两者的区别:
采用基于spring提供的获取jdbcTemplate对象的方法,就不能基于注解来注入jdbcTemplate而对于自定义的dao,可以通过@Autowired注解来注入
AccountDaoImpl实体类改写:
舍去JdbcTemplate的定义和set方法,调用父类的getTemplate方法来获取JdbcTemplate对象
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
// 不再需要使用set方法注入JdbcTemplate,使用父类方法中的getJdbcTemplate来获取对象即可
@Override
public Account findById(Integer id) {
List<Account> res = super.getJdbcTemplate().query("select * from account where id=?",
new BeanPropertyRowMapper<Account>(Account.class),
1);
if(res.isEmpty()) {
return null;
}else{
return res.get(0);
}
}
@Override
public Account findByName(String name) {
List<Account> res = super.getJdbcTemplate().uery("select * from account where name=?",
new BeanPropertyRowMapper<Account>(Account.class),
name);
if(res.isEmpty()){
return null;
}else if(res.size() > 1){
throw new RuntimeException("结果集不为1");
}else{
return res.get(0);
}
}
@Override
public List<Account> findAll() {
List<Account> res = super.getJdbcTemplate().query("select * from account ",
new BeanPropertyRowMapper<Account>(Account.class));
return res;
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,revenue=? where id=?",
account.getName(), account.getRevenue(), account.getId());
}
}
2. Spring中事务控制
2.1. Spring中事务控制需要明确的
- JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案
- Spring框架为我们提供了一组事务控制的接口,这组接口在spring-tx jar包中
- Spring的事务控制都是基于AOP的,既可以使用编程的方式实现,也可以使用配置的方式实现
2.2. Spring中事务控制的API
2.2.1. PlatformTransactionManager
此接口是spring的事务管理器,提供了常用的操作事务的方法,源码:
3个具体的操作:
- ==TransactionStatus getTransaction()==获取事务状态信息
- void commit() 提交事务
- ==void rollback()==回滚事务
但是PlatformTransactionManager是一个接口,需要使用其实现类来创建管理事务的对象:
- org.springframework.jdbc.datasource.DataSourceTransactionManager 使用>SpringJDBC 或 iBatis进行持久化数据时使用
- org.springframework.orm.hibernate5. HibernateTransactionManager 使用Hibernate版本进行持久化数据时使用
2.2.2. TransactionDefinition
是事务的定义属性接口,阅读源码可知常用方法有:
-
String getName() 获取事务对象名
-
int getIsolationLevel() 获取事务隔离级别(与数据库的隔离级别保持一致)
-
int getPropagationBehavior() 获取事务传播行为:什么时候需要开启事务(增删改操作需要开启事务),什么时候事务可有可无(查询操作可以不开启事务)
-
int getTimeout() 获取事务超时时间
-
boolean isReadOnly() 获取事务是否只读
2.2.2.1. 事务的隔离级别
事务的隔离级别反映事务提交并访问时的处理态度,以下四种选择一个(使用数据库的隔离级别):
- ISOLATION_DEFAULT 默认级别,属于下面4个中的哪一种
- ISOLATION_READ_UNOMMITED 可以读取未提交数据
- ISOLATION_READ_COMMITED 只能读取已经提交的数据,解决脏读问题(Oracle默认级别)
- ISOLATION_REPEATABLE_READ 是否读取其他事物提交修改后的数据,解决不可重复问题(MySQL默认级别)
- ISOLATION_SERIALIZABLE 是否读取其他事务提交添加后的数据,解决幻读问题
2.2.2.2. 事务的传播行为
- REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值,针对增删改操作)
- SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务,针对查询操作)
- MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常、
- REQUERS_NEW 新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER 以非事务方式运行,如果当前存在事务,抛出异常
- NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
2.2.2.3. 超时时间
默认值为-1,没有超时限制,如果有,则以秒为单位进行设置
2.2.2.4. 是否是只读事务
建议查询操作时设置为只读
2.2.2.5. TransactionStatus
该接口提供的是事务具体的运行状态,源码中的具体方法包括6个:
-
void flush() 涮新事务
-
boolean hasSavepoint() 是否存在存储点(类似于断点)
-
boolean isNewTransaction() 事务是否是新的事务
-
boolean isCompleted() 事务是否完成
-
boolean isRollbackOnly() 事务是否回滚
-
void setRollbackOnly() 设置事务回滚
2.3. 基于XML的声明式事务控制
需求: 使用XML配置的方式完成转账操作
2.3.1. 导入依赖
- spring-context
- spring-tx
- spring-jdbc
- spring-test
- mysql-connector-java
- junit
- aspectj
2.3.2. 持久层接口以及实现类
public interface AccountDao {
Account findById(Integer id);
Account findByName(String name);
List<Account> findAll();
void updateAccount(Account account);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public Account findById(Integer id) {
List<Account> res = super.getJdbcTemplate().query("select * from account where id=?",
new BeanPropertyRowMapper<Account>(Account.class),
1);
if(res.isEmpty()) {
return null;
}else{
return res.get(0);
}
}
@Override
public Account findByName(String name) {
List<Account> res = super.getJdbcTemplate().query("select * from account where name=?",
new BeanPropertyRowMapper<Account>(Account.class),
name);
if(res.isEmpty()){
return null;
}else if(res.size() > 1){
throw new RuntimeException("结果集不为1");
}else{
return res.get(0);
}
}
@Override
public List<Account> findAll() {
List<Account> res = super.getJdbcTemplate().query("select * from account ",
new BeanPropertyRowMapper<Account>(Account.class));
return res;
}
@Override
public void updateAccount(Account account) {
super.getJdbcTemplate().update("update account set name=?,revenue=? where id=?",
account.getName(), account.getRevenue(), account.getId());
}
}
2.3.3. 业务层接口以及实现类
/**
* 账户的业务层的接口
*/
public interface AccountService {
Account findAccountByID(Integer id);
void transfer(String sourceName, String targetName, Double money);
Account findAccountByName(String name);
}
public class ServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public Account findAccountByID(Integer id) {
return accountDao.findById(id);
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
System.out.println("transfer begin...");
Account account1 = accountDao.findByName(sourceName);
Account account2 = accountDao.findByName(targetName);
account1.setRevenue(account1.getRevenue() - money);
account2.setRevenue(account2.getRevenue() + money);
accountDao.updateAccount(account1);
// int i=1/0;
accountDao.updateAccount(account2);
System.out.println("transfer success...");
}
@Override
public Account findAccountByName(String name) {
return accountDao.findByName(name);
}
}
2.3.4. Account实体类
public class Account implements Serializable {
private Integer id;
private String name;
private Double revenue;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getRevenue() {
return revenue;
}
public void setRevenue(Double revenue) {
this.revenue = revenue;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", revenue=" + revenue +
'}';
}
}
2.3.5. XML文件配置
-
配置AccountDao对象, 通过继承JdbcDaoSupport类来注入JdbcTemplate
-
配置AccountService对象, 注入accountDao
-
配置DataSource对象
-
spring中基于XML的声明式事务控制配置
- 配置事务管理器,并且注入ds
- 配置事务的通知, 需要导入事务约束 xmlns:tx ,同时也需要AOP的约束
使用tx:advice标签配置事务通知
属性: id 给事务通知配置一个唯一表示
transaction-manager 给事务通知提供一个事务管理器
-
配置AOP中的通用切入点表达式
-
建立切入点表达式和事务通知的关系
-
配置事务的属性, 在事务通知aop:tx标签内部使用tx:attributes标签配置, 在tx:method标签内的属性有:
name: 针对于业务层中的哪个方法进行事务控制,可以使用通配符或者find(只针对于find开头的查询方法)
isolation:用于指定事务的隔离级别默认值为DEFAULT 表示使用数据库的默认隔离级别
propagation:用于指定事务的传播行为,默认值为REQUIRED 表示使用事务控制(增删改操作的选择,查询操作可以选择SUPPORTS)
read-only:用于指定事务是否是只读 只有查询方法才能设置为true 默认值为false表示读写
rollback-for:用于指定一个异常,当产生该异常时事务回滚,而产生其他异常时事务不回滚。没有默认值,表示任何异常都回滚
no-rollback-for:用于指定一个异常,当该异常发生时,事务不回滚。产生其他异常时事务回滚。没有默认值,表示任何异常都回滚
timeout:用于指定事务的超时时间默认值为-1 如果指定了数值以秒为单位<?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" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.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"> <!--配置Account的持久层对象 通过继承JdbcDaoSupport类来注入JdbcTemplate--> <bean id="accountDao" class="com.hz.dao.impl.AccountDaoImpl"> <property name="dataSource" ref="dataSource"></property> </bean> <!--配置业务层对象--> <bean id="accountService" class="com.hz.service.impl.ServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置dataSource对象--> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property> <property name="url" value="jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false"></property> <property name="username" value="root"></property> <property name="password" value="123456"></property> </bean> <!--spring中基于XML的声明式事务控制配置--> <!--1. 配置事务管理器 并且注入ds--> <bean id="transManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!--2. 配置事务的通知 需要导入事务约束 xmlns:tx 同时也需要aop的约束--> <tx:advice id="txAdvice" transaction-manager="transManager"> <!--5. 配置事务的属性 在事务通知aop:tx标签内部配置--> <tx:attributes> <tx:method name="*"></tx:method> <tx:method name="find*" propagation='REQUIRED' read-only="false"></tx:method> </tx:attributes> </tx:advice> <!-- 3. 配置AOP中的通用切入点表达式--> <aop:config> <aop:pointcut id="ptTransManager" expression="execution(* com.hz.service.impl.*.*(..))"></aop:pointcut> <!--4. 建立切入点表达式和事务通知的关系--> <aop:advisor advice-ref="txAdvice" pointcut-ref="ptTransManager"></aop:advisor> </aop:config> </beans>
2.3.6. JUnit测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class TransactionControlTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer(){
accountService.transfer("五五开", "大司马", 10000d);
}
}
转账之前:
转账之后:
转账成功,如果转账过程中发生异常,事务会进行回滚操作,并不会影响最后的结果
2.4. 基于注解的声明式事务控制
在实现XML配置的基础上,进行注解的声明式事务控制
2.4.1. 持久层的注解配置
由于XML配置持久层实现类实现的是通过继承JdbcDaoSupport类来获取JdbcTemplate对象,而使用注解进行配置时,无法通过注解进行jdbcTemplate的注入,因此需要自定义jdbcTemplate对象,并且使用==@Autowired==进行注入
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findById(Integer id) {
List<Account> res = jdbcTemplate.query("select * from account where id=?",
new BeanPropertyRowMapper<Account>(Account.class),
1);
if(res.isEmpty()) {
return null;
}else{
return res.get(0);
}
}
@Override
public Account findByName(String name) {
List<Account> res = jdbcTemplate.query("select * from account where name=?",
new BeanPropertyRowMapper<Account>(Account.class),
name);
if(res.isEmpty()){
return null;
}else if(res.size() > 1){
throw new RuntimeException("结果集不为1");
}else{
return res.get(0);
}
}
@Override
public List<Account> findAll() {
List<Account> res = jdbcTemplate.query("select * from account ",
new BeanPropertyRowMapper<Account>(Account.class));
return res;
}
@Override
public void updateAccount(Account account) {
jdbcTemplate.update("update account set name=?,revenue=? where id=?",
account.getName(), account.getRevenue(), account.getId());
}
2.4.2.配置类
2.4.2.1 SpringConfiguration 配置类
SpringConfiguration配置类相当于bean.xml中的ds, JdbcTemplate和事务管理器的配置与开启注解事务的支持
- @Configuration注解告诉该类是一个配置类
- @ComponentScan注解告诉Spring需需要扫描的包,相当于XML配置中的
<context:component-scan base-package="com.hz"></context:component-scan>
- @Import注解,用于导入其他的子配置类
- @PropertySource用于加载外部配置文件的配置
- EnableTransactionManagement注解,用于开启Spring对于注解事务的支持,相当于XML配置中的
<!--spring中基于注解的声明式事务控制配置-->
<bean id="transManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transManager"></tx:annotation-driven>
@Configuration
@ComponentScan("com.hz")
@Import({JdbcConfig.class, TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement // 开启Spring对于注解事务的支持
public class SpringConfiguration {
}
2.4.2.2. TransactionConfiguration配置类
和事务相关的配置类,其实就是创建事务管理器对象,并使用Bean注解注入到Spring的IoC容器
public class TransactionConfig {
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
2.4.2.3. JdbcConfig配置类
用于连接数据库,使用Value注解注入连接数据库需要而driver,url,username以及password
创建JdbcTemplate对象,使用Bean注解将该对象存入到Spring的IoC容器中
创建数据源DataSource对象,同理使用Bean注解将该对象存入到Spring的IoC容器中
public class JdbcConfig {
@Value("com.mysql.cj.jdbc.Driver")
private String driver;
@Value("jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false")
private String url;
@Value("root")
private String username;
@Value("123456")
private String password;
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean(name = "dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
2.4.3. 业务层的注解配置
-
Service注解指名是一个业务层
-
使用Transactional注解配置只读型事务和读写型事务,一般增删改操作需要配置成读写型事务,而简单的查询语句可以配置成只读型事务
- 属性propagation指名为SUPPORTS,readOnly指明为true表示是一个只读型事务
- 属性propagation指名为REQUIRED,readOnly指明为false表示是一个读写型事务
下面在类的外部,配置一个只读型事务,其中所有的事务都是只读型,而对于增删改操作需要再次配置成读写型
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class ServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public Account findAccountByID(Integer id) {
return accountDao.findById(id);
}
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
@Override
public void transfer(String sourceName, String targetName, Double money) {
System.out.println("transfer begin...");
Account account1 = accountDao.findByName(sourceName);
Account account2 = accountDao.findByName(targetName);
account1.setRevenue(account1.getRevenue() - money);
account2.setRevenue(account2.getRevenue() + money);
accountDao.updateAccount(account1);
// int i=1/0;
accountDao.updateAccount(account2);
System.out.println("transfer success...");
}
@Override
public Account findAccountByName(String name) {
return accountDao.findByName(name);
}
测试类同XML配置方式,转账的前后结果如下:
3.Spring中的编程式事务控制
在XML配置的基础上进行修改为编程式事务控制
3.1. XML文件配置
与纯XML配置的声明式事务控制的区别在于:对于事务配置,只需配置事务管理器和事务管理模板(注入事务管理器对象TransactionTemplate),并且在service对象中需要额外注入一个模板对象TransactionTemplate
<?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">
<bean id="accountService" class="com.huzhen.service.impl.ServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<property name="transactionTemplate" ref="transTemplate"></property>
</bean>
<bean id="accountDao" class="com.huzhen.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="ds"></property>
</bean>
<bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql:///spring_account?serverTimezone=UTC&useSSL=false"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
<!--配置事务管理器-->
<bean id="transManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="ds"></property>
</bean>
<!--配置事务管理模板 并且注入事务管理器对象-->
<bean id="transTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<property name="transactionManager" ref="transManager"></property>
</bean>
</beans>
3.2. 业务层实现类ServiceImpl
对于编程式事务控制,与基于XML配置的声明式事务控制的最大差别在于业务层的实现。通过观察Spring的TransactionTemplate模板类的源码:
其execute方法中的try中的方法执行即为所需要执行的方法(增删改查),catch为异常通知,如果不发生异常,则进行事务的commit也就是后置通知,而action是一个TransactionCallback对象,来执行该接口下的doInTransaction方法获取返回值。而TransactionCallback是一个接口,可以使用匿名内部类的方法来创建一个该类型的对象,并且覆盖重写抽象方法doInTransaction(也就是业务层所执行的增删改查操作)
在转账方法中使用transactionTemplate对象的execute方法进行编程式事务控制
public class ServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
public Account findById(Integer id) {
return transactionTemplate.execute(new TransactionCallback<Account>() {
@Override
public Account doInTransaction(TransactionStatus status) {
Account account = accountDao.findById(id);
return account;
}
});
}
public List<Account> findAll() {
return transactionTemplate.execute(new TransactionCallback<List<Account>>() {
@Override
public List<Account> doInTransaction(TransactionStatus status) {
List<Account> res = accountDao.findAll();
return res;
}
});
}
public Account findByName(String name) {
return transactionTemplate.execute(new TransactionCallback<Account>() {
@Override
public Account doInTransaction(TransactionStatus status) {
Account account = accountDao.findByName(name);
return account;
}
});
}
public void updateAccount(Account account) {
transactionTemplate.execute(new TransactionCallback<Account>() {
@Override
public Account doInTransaction(TransactionStatus status) {
accountDao.updateAccount(account);
return null;
}
});
}
@Override
public void transfer(String sourceName, String targetName, Double money) {
// 使用transactionTemplate对象的execute方法进行编程式事务控制
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus status) {
System.out.println("transfer begin...");
Account account1 = accountDao.findByName(sourceName);
Account account2 = accountDao.findByName(targetName);
account1.setRevenue(account1.getRevenue() - money);
account2.setRevenue(account2.getRevenue() + money);
accountDao.updateAccount(account1);
accountDao.updateAccount(account2);
System.out.println("transfer success...");
return null;
}
});
}
}
另外,对于业务层中的所有方法都需要使用该种方式来完成编程式事务的控制,当业务层方法数目较多时,编程式事务控制带来的耦合性增大并且代码冗余过大。