typora-copy-images-to: images
Spring<04>JdbcTemplate&声明式事务
1、Spring JdbcTemplate
1.1 JdbcTemplate概述
以往使用jdbc时,每次都需要自己获取PreparedStatement,执行sql语句,关闭连接等操作。操作麻烦冗余,影响编码的效率。Spring把对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate(jdbc模板)之中,这样我们只需要做一些简单的操作(eg:编写SQL语句、传递参数)就可以了。
spring框架根据不同持久层方案为我们提供了不同的JdbcTemplate(jdbc模板类)。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等
1.2 QuickStart
1)JdbcTemplate基础开发步骤
① 导入spring-jdbc和spring-tx坐标
② 创建数据库表
③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用
JdbcTemplate位于
spring-jdbc-x.x.x.RELEASE.jar
中。其全限定命名为org.springframework.jdbc.core.JdbcTemplate
。要使用JdbcTemlate还需要开启事务,所以需要Spring-tx-x.x.x.RELEASE.jar
,该包包含了一下事务和异常管理。所以需要导入spring-jdbc和spring-tx的坐标。
2)JdbcTemplate基础开发代码实现
① 导入spring-jdbc和spring-tx坐标
<dependencies>
<!-- Spring上下文坐标,如果使用到了Spring的上下文容器,需要导
本次测试只是演示JdbcTemplate对象的创建和使用,不使用Spring容器进行Ioc,所以可以不导
-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- 使用Spring JdbcTemplate需要导入下面两个坐标 -->
<!-- 导入spring-jdbc坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- 导入spring-tx事务管理坐标,如果不使用事务管理,可以不导。本测试未开启事务管理,可以不导 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- 因为操作Mysql数据库,需要导入数据库驱动坐标 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- 数据源选择使用c3p0连接池,所以导入其坐标,测试中用到了c3p0的连接池,必导 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- 使用单元测试,所以导入JUnit坐标 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
② 创建数据库表(和实体类)
CREATE TABLE account( username varchar(255),money double );
package com.itheima.jdbctemplate.domain;
/**
* 本案例中未使用到该实体类,故在本案例中可以不创建该类
*/
public class Account {
private String username;
private double money;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"username='" + username + '\'' +
", money=" + money +
'}';
}
}
③ 在测试类中创建JdbcTemplate对象并使用
package com.itheima.jdbctemplate.test;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;
import java.beans.PropertyVetoException;
public class AccountTest {
@Test
public void test1() throws PropertyVetoException {
//1. 创建JdbcTemplate模板对象
JdbcTemplate jdbcTemplate = new JdbcTemplate();
//2. 设置数据源ComboPooledDataSource对象
//创建DataSource对象,并设置连接四要素
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("135246");
//JdbcTemplate依赖于DataSource
jdbcTemplate.setDataSource(dataSource);
//3. 执行增删改查操作
int row = jdbcTemplate.update("insert into account values (?,?)", "hzheima", 50000);
System.out.println(row);
}
}
执行数据库操作并查看执行结果
Disconnected from the target VM, address: '127.0.0.1:58966', transport: 'socket'
1
Process finished with exit code 0
3)基础开发步骤总结
① 导入spring-jdbc[ 和spring-tx坐标 ](同时导入依赖mysql-connector-java、c3p0坐标和测试junit坐标)
② 创建数据库表
③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用
1. 创建JdbcTemplate模板对象
2. 设置数据源ComboPooledDataSource对象
3. 执行增删改查操作
1.3 Spring容器创建并维护JdbcTemplate及相关对象
1)思路分析
Spring的核心之一就是Ioc,控制反转,也就是由Spring容器负责对象的创建和对象间依赖关系的管理。上述代码中DataSource、JdbcTemplate对象以及他们之间的关系都是由我们手动创建,并且手动设置依赖关系的。
经过观察,JdbcTemplate类可以通过无参构造创建该类对象,同时又setDataSource()方法用于设置数据源依赖,所以JdbcTemplate类的对象完全可以交给Spring容器管理,再加上我们之前学习过的数据源DataSource可以交给Spring容器管理,那我们就可以使用Spring容器管理并维护上述两个对象。
2)代码实现
导入坐标:因为要使用到Spring容器创建对象并维护对象依赖关系,所以要在pom.xml中确定导入了spring-context坐标。
<!-- Spring上下文坐标,使用到了Spring的上下文容器,需要 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
Xml配置:在applicationContext.xml文件中先配置数据源,然后配置JdbcTemplate,最后将数据源注入到JdbcTemplate中。
<!-- DataSource对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql:///test"></property>
<property name="user" value="root"></property>
<property name="password" value="135246"></property>
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
Java代码:通过Spring容器获得JdbcTemplate对象,调用JdbcTemplate对象的update方法,传入sql语句及参数完成插入数据的操作
@Test
public void test2() throws PropertyVetoException {
//1. 获取JdbcTemplate模板对象(Spring已经将DataSource注入其中)
ApplicationContext ac= new ClassPathXmlApplicationContext(
"applicationContext.xml");
JdbcTemplate jdbcTemplate = ac.getBean(JdbcTemplate.class);
//2. 不需要设置数据源ComboPooledDataSource对象
//3. 执行增删改查操作
int row = jdbcTemplate.update("insert into account values (?,?)", "shheima", 60000);
System.out.println(row);
}
1.4 抽取jdbc配置
目前的jdbc四要素已经脱离了代码,已经实现了解耦,之后如果数据库信息修改后直接修改配置文件即可。
但是目前数据库四要素的配置耦合进了Spring的配置文件,为了更方便的维护,建议将该信息抽取到一个独立properties文件中。
提取数据源的基本连接信息到配置文件jdbc.properties(名字任意,格式为properties)
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
username=root
password=135246
引入properties文件,并修改DataSource注入基本参数的配置,具体如下:
<!-- 导入jdbc.properties文件。classpath表示文件在类路径下 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- DataSource对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--
property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置
通过${key}的方式可以从properties文件中读取key对应的值。
-->
<property name="driverClass" value="${jdbc.driver}"></property>
<property name="jdbcUrl" value="${jdbc.url}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
</bean>
<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
其他代码保持不变。
1.5 JdbcTemplate常用操作
jdbcTemplate更新数据库常用方法
- update (更新数据,也就是增删改查)
batchUpdate (批量更新数据库,也就是增删改查)- queryForObject (单行查询)
- query (多行查询)
- queryForObject (单值查询)
执行DDL语句
1.6 JdbcTemplate常用操作-增删改操作
JdbcTemplate对象的update方法可以完成增删改的操作,只是传入的sql语句及对应参数不同。
jdbcTemplate.update(sql语句, sql语句参数列表) 对数据库进行增删改的操作,返回影响的行数
代码如下:
因为要使用Spring整合JUnit测试,所以需要导入spring-test和junit(已经导入了)坐标
<!-- 导使用Spring整合JUnit测试,所以需要导入Spring-test及其依赖junit坐标 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- 使Spring-test依赖JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
applicationContext.xml文件
<!-- 内容无需修改 -->
使用Spring整合JUnit进行测试
@RunWith(SpringJUnit4ClassRunner.class) //指定测试器
@ContextConfiguration("classpath:applicationContext.xml") //指定Spring配置文件并加载接卸
public class JdbcTemplatetTest {
@Autowired //自动注入JdbcTemplate对象
private JdbcTemplate jdbcTemplate;
/**
* testUpdate:测试删除
*/
@Test
public void testDelete(){
int row = jdbcTemplate.update("delete from account where username=?","nanjingheima");
System.out.println(row);
}
/**
* testUpdate:测试修改
*/
@Test
public void testUpdate(){
int row = jdbcTemplate.update("update account set money=? where username=?", 500000,"hzheima");
System.out.println(row);
}
}
1.7 JdbcTemplate常用操作-查询操作
JdbcTemplate对象的query**()
方法可以完成查询的操作。
多行查询
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class))
查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
单行查询
T queryForObject(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
单行查询
查询并将结果通过BeanPropertyRowMapper将查询结果封装到javaBean对象中
单值查询
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class)
查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class, sql语句参数列表)
查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
代码如下:
导入依赖坐标
<!-- 内容无需修改 -->
applicationContext.xml文件
<!-- 内容无需修改 -->
编写JavaBean类
//已完成Account类编写
使用Spring整合JUnit进行测试
@RunWith(SpringJUnit4ClassRunner.class) //指定测试器
@ContextConfiguration("classpath:applicationContext.xml") //指定Spring配置文件并加载接卸
public class JdbcTemplatetTest {
@Autowired //自动注入JdbcTemplate对象
private JdbcTemplate jdbcTemplate;
/**
* testQueryCount:聚合查询
*/
@Test
public void testQueryCount(){
long count = jdbcTemplate.queryForObject("select count(*) from " +
"account",long.class);
System.out.println(count);
count = jdbcTemplate.queryForObject("select count(*) from " +
"account where money = ?",long.class,60000);
System.out.println(count);
}
/**
* testQueryOne:查询单个
*/
@Test
public void testQueryOne(){
Account account = jdbcTemplate.queryForObject("select * from account " +
"where username = ?",
new BeanPropertyRowMapper<Account>(Account.class) ,
"hzheima");
System.out.println(account);
}
/**
* testQuerySome:按条件查询部分
*/
@Test
public void testQuerySome(){
List<Account> accounts = jdbcTemplate.query("select * from account where money = ?",
new BeanPropertyRowMapper<Account>(Account.class), 60000);
System.out.println(accounts);
}
/**
* testQueryAll:查询所有
*/
@Test
public void testQueryAll(){
List<Account> accounts = jdbcTemplate.query("select * from account",
new BeanPropertyRowMapper<Account>(Account.class));
System.out.println(accounts);
}
}
补充讲解:
批量更新(增删改)
jdbcTemplate.batchUpdate(String sql, List batchArgs); //批量增删改,batchArgs为批量更新的参数列表,类型为Object[]数组类型的List集合
/**
*testBatechUpdate:批量更新,批量 insert,update,delete
*/
@Test
public void testBatechUpdate() {
String sql = "insert into account(username, money) value(?,?)";
List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[] {"zhangsan",2000});
batchArgs.add(new Object[] {"lisi",2000});
batchArgs.add(new Object[] {"wangwu",2000});
jdbcTemplate.batchUpdate(sql, batchArgs);
}
自定义绑定(使用场景较少)
除了使用系统提供的RowMapper的实现类之外,我们也可以自己手动编写RowMapper实现类,以实现特殊的结果集到javaBean的封装转化,实例代码如下:
Account account = jdbcTemplate.queryForObject(
"select * from account where money = ?",
new Object[]{60000L},
new RowMapper<Account>() {
//重写该方法时只需要将获取的结果集中单个结果封装到某个JavaBean中,query方法会根据实际情况选择选择返回一个javaBan对象还是一个List
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setUsername(rs.getString("username"));
account.setMoney(rs.getString("money"));
return account;
}
});
执行DDL语句(使用场景很少)
Spring的JdbcTemplate也可以使用execute(…)方法运行任意SQL语句。所以,该方法通常用于DDL语句。下面代码创建一个表:
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
详见Spring官方文档:data-access --> 3.3.1. Using JdbcTemplate
1.8 JdbcTemplate使用总结
使用步骤
① 导入spring-jdbc[ 和spring-tx ]坐标(同时导入依赖mysql-connector-java、c3p0和测试junit坐标;spring-context<SpringIoc容器>、spring-test<Spring整合JUnit>按需导入)
② 编写JavaBean类、创建数据库表
③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用
-
创建JdbcTemplate模板对象
-
设置数据源ComboPooledDataSource对象
(如果使用了SpringIoc容器,上述1、 2步骤可以使用自动注入完成。)
-
使用JdbcTemplate对象执行增删改查操作
增删改查的常用方法
增删改
jdbcTemplate.update(sql语句, sql语句参数列表) 对数据库进行增删改的操作,返回影响的行数
查询
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class))
查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
T queryForObject(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
查询并将结果通过BeanPropertyRowMapper将查询结果封装到javaBean对象中
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class)
查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class, sql语句参数列表)
查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
2、Spring事务管理
Spring事务管理主要应用在软件分层之后的业务层,也就是Service层。一个service层的操作依赖的多个dao层操作要被事务管理起来,保证数据安全准确。eg:转账。
2.1 Spring事务管理核心API(了解)☆
Spring事务管理的三个高层接口
- PlatformTransactionManager:Spring提供的事务管理器接口,内部规定了常用的事务管理的规则。是整个Spring进行事务管理的核心接口。
- TransactionDefinition:Spring提供的封装事务属性的接口,用于封装和操作事务的属性
- TransactionStatus:Spring提供的封装事务状态的接口,用于封装和操作事务的状态
三者的关系:平台事务管理器(PlatformTransactionManager)根据 事务属性管理器(TransactionDefinition)中定义的事务属性对事务进行管理;事务运行过程中,每一个时间点都有对应的事务状态信息(TransactionStatus)。
也就是说,干活的是Manager(PlatformTransactionManager),根据事务属性管理器(TransactionDefinition)的配置信息干活,在干活的过程中会有一些状态信息,通过TransactionStatus体现。
2.2 PlatformTransactionManager(了解)☆
Spring提供的事务管理器接口,整个Spring进行事务管理的核心接口,内部规定了常用的操作事务的规则。
a) 接口中规定的操作事务的方法:
操作事务的方法 | 说明 |
---|---|
TransactionStatus getTransaction (TransactionDefination defination) | 根据事务的属性信息对象 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚事务 |
根据上述方法,
TransactionStatus getTransaction(TransactionDefination defination)
我们可以推断:
PlatformTransactionManager根据TransactionDefinition对象获得 TransactionStatus 对象
b) 不同的Dao层技术则有不同的实现类
PlatformTransactionManager是一个接口,只负责制定规范,那具体的实现是哪些类呢?Spring根据不同的数据持久层技术提供了不同实现类:
-
Dao 层技术是jdbc、 mybatis、Apache DBUtils时:org.springframework.jdbc.datasource.DataSourceTransactionManager
-
Dao层技术是hibernate时:
org.springframework.orm.hibernate5.HibernateTransactionManager
-
我们需要在开发中根据实际情况选择并配置对应的平台事务管理器。
2.3 TransactionDefinition(了解)
Spring提供的封装事务属性的接口,用于规定封装事务的属性,通过该接口实现类对象可以获取(子类对象可以设置)事务属性。
a) 接口中规定的获取事务属性的方法:
方法名 | 说明 |
---|---|
int getIsolationLevel() | 获取事务的隔离级别,解决事务并发访问隔离问题 |
int getPropagationBehavior() | 获取事务的传播行为,解决的就是两个事务方法事务统一性的问题 |
int getTimeout() | 获取超时时间 |
boolean isReadOnly() | 判断是否只读 |
*接口中只规定了获取的方法,在其实现类中有设置的方法,eg:DefaultTransactionDefinition
事务属性需要在进行声明式事务配置时,需要在配置文件中对于事务属性进行配置,以便Spring框架进行解析。
b) 事务的四个特性:
ACID:原子性、一致性、隔离性、持久性。
① Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成 功,要么全部失败。
② Consistency(一致性):事务完成时,数据必须处于一致状态,数据的完整性约束没有被破坏,事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
③ Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
④ Durability(持久性):事务结束后,事务处理的结果必须能够得到持久化。
c) 事务属性:隔离级别(理解)
在事务的隔离性在并发访问的时候,会出现一些读问题。
读问题
- 脏读:一个事务读取了另外一个事务改写但还未提交的更新数据,如果这些数据被回滚,则读到的数据是无效的。
- 不可重复读:在同一个事务中,多次读取同一个数据返回的结果有所不同。换句话说就是,后续读取可以读取到另一个事务已提交的更新数据。相反,“可重复读”在同一个事务中多次读取数据时,能保证数据时一样,也就是,后续读取不能读取到另一个事务已经提交的更新数据。
- 幻读:一个事务读取了(按照某个规则的)全部数据后,另一个事务插入了一些记录,当后续再次查询的时候,幻读就发生了。在后续查询中,第一个事务就会发现有一些原来没有的记录。
设置隔离级别,可以解决事务并发产生的读问题,如脏读、不可重复读和虚读(幻读)。
-
ISOLATION_DEFAULT Spring事务管理提供的默认级别,使用数据库内部默认级别(mysql - Repeatable read,其他大多数是 - Read committed)
-
ISOLATION_READ_UNCOMMITTED 读未提交:一个事务可以读取另一个未提交事务的数据,该级别下容易上述所有问题。
-
ISOLATION_READ_COMMITTED 读提交:一个事务要等另一个事务提交后才能读取数据。能解决脏读问题,但是可能会有不可重复读、幻读的问题。
-
ISOLATION**_REPEATABLE_READ** 重复读,在开始读取数据(事务开启)时,不再允许修改操作。能解决脏读、不可重复读的问题,但是可能会有幻读的问题。
-
ISOLATION_SERIALIZABLE 序列化,解决所有三个问题,相当于锁表安全性高,串行化处理事务,但是非常耗数据库性能,效率低下,一般不用。
列表如下(√表示可能出现,×表示不会出现):
隔离级别\读问题 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read uncommitted读未提交 | √ | √ | √ |
Read commited读提交 | × | √ | √ |
Repeatable read重复读 | × | × | √ |
Serializable | × | × | × |
上述事务的四大特性、四个隔离级别是原来是在数据库内部规定的,Spring从数据库引入了 过来。但是接下来的七种传播行为和数据库没有直接关系,是Spring为了解决实际开发中的问题而引入的。
d) 事务属性:传播行为(理解)
即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。
事务传播行为(propagation behavior)指的就是当一个事务方法A调用另一个事务方法B时,事务方法B应该以何种事务特性进行。是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。
事务传播行为是为了解决业务方法相互调用时,事务如何管理的问题。
实际开发中,程序要进行事务管理,在程序分层之后,程序的事务控制通常在哪一层?
通常情况下,一个业务是一个事务。但是多个业务方法相互调用的时候可能会出现问题。
eg:取钱打印小票
七种事务传播行为(当前方法:被调用的方法):
- PROPAGATION_REQUIRED:表示当前方法必须运行在事务中。如果调用者事务存在,当前方法就会在该事务中运行;否则,会启动一个新的事务。一般的选择(默认值)。
- PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果当前事务存在的话,那么该方法会在这个事务中运行。
- PROPAGATION_MANDATORY:表示当前方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
- PROPAGATION_REQUERS_NEW:表示当前方法必须运行在它自己的事务中,一个新的事务将被启动。如果存在当前事务,该方法执行期间,当前事务会被挂起。
- PROPAGATION_NOT_SUPPORTED:表示当前方法不应该运行在事务中。如果存在当前事务,该方法运行期间,当前事务会被挂起。
- PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果存在当前事务,则会抛出异常。
- PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行独立的提交和回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持不同,可以参考资源管理器的文档来确认他们是否支持嵌套事务。
e) 事务其他属性
超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。一般使用默认。
是否只读:建议查询操作时设置为只读,效率更高。
事务中的readOnly属性表示对应的事务会最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化属性。
2.4 TransactionStatus
Spring提供的封装事务状态的接口,用于封装事务的状态。
事务的状态是事务自身的属性,在事务管理不同的时间段,事务会表现出不同的状态,所以不需要配置。
a) 接口中规定的判断事务状态的方法:
方法名 | 说明 |
---|---|
boolean isNewTransaction() | 是否是新事务。如果返回false,说明当前事务已存在,或者该操作没在事务环境中。 |
boolean hasSavepoint() | 是否存在回滚点 |
boolean isRollbackOnly() | 判断当前事务是否设置了rollback-only |
boolean isCompleted() | 事务是否已结束 |
2.5 要点总结
Spring事务管理的三个高层接口
-
PlatformTransactionManager:Spring提供的事务管理器接口,内部规定了常用的操作事务的规则。我们需要在开发中根据实际情况选择并配置对应的平台事务管理器。
-
TransactionDefinition:Spring提供的定义事务参数的接口,用于封装事务的属性。
-
TransactionStatus:Spring提供的封装事务状态的接口,用于封装事务的状态。
三者关系:平台事务管理器(PlatformTransactionManager)根据 事务属性管理器(TransactionDefinition)中定义的事务属性对事务进行管理;事务运行过程中,每一个时间点都有一个对象的事务状态信息(TransactionStatus)。
需要配置的内容:PlatformTransactionManager、TransactionDefinition
不需要配置的内容:TransactionStatus
2.6 Spring事务管理分类 ☆
- 编程式事务管理
- 声明式事务管理
- 基于XML的声明式事务管理
- 基于注解的声明式事务管理
特点分析:
- 编程式事务管理(了解)
- 在实际开发中使用很少使用
- 手动使用TransitionTemplate对象,编写代码手动管理事务
- 声明式事务管理(基于AOP,掌握)
- 无需修改业务代码,通过配置的方式,就可以在业务代码上添加事务管理相关的功能
2.7 准备案例环境
事务管理非常常见的场景就是转账业务,那我们就搭建一个转账业务的环境。
- 使用sql创建表,录入测试数据
- 使用SpringIoc的规范编写service、dao层相关代码
- 在转账业务添加异常代码,观看演示效果
详细代码见模块itheima_spring_tx_oraginal
2.8 编程式事务 ☆
使用java代码进行事务管理就是编程式事务管理。
在之前学习jdbc的时候:
connection.setAutoCommit(false) 开启事务
connection.commit() 提交事务
connection.rollback() 回滚事务
在Spring中使用,使用事务管理工具类TransactionTemplate进行编程式事务管理。
-
导入itheima_spring_tx_oraginal模块(已经导入spring-tx坐标)
-
因为一般在Service中进行事务管理,故将TransactionTemplate注入到service中.
–
public class ServiceImpl{ //其他原有代码 //注入务管理工具类TransactionTemplate private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate){ this.transactionTemplate = transactionTemplate; } // 其他原有代码 }
<!--装配TransactionTemplate--> <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <!-- 真正进行事务管理的不是transactionTemplate,而是我们之前讲的PlatformTransactionManager 因为我们使用的是jdbc技术,所有选用DataSourceTransactionManager --> <property name="transactionManager" ref="dataSourceTransactionManager"></property> </bean> <!-- 装配DataSourceTransactionManager到Spring容器 DataSourceTransactionManager需要注入DataSource --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 将transactionTemplate注入到service中 --> <!--目标对象 内部的方法就是切点--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="transactionTemplate" ref="transactionTemplate"></property> </bean>
-
在业务代码中添加事务管理代码,使用transactionTemplate进行事务管理。
package com.itheima.service.impl; import com.itheima.dao.AccountDao; import com.itheima.service.AccountService; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate; public class AccountServiceImpl implements AccountService { // 编程式事务管理,要用到事务模板工具类 private TransactionTemplate transactionTemplate; public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; } private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } /* 编程式事务管理,需要将下面的需要被事务管理的代码写到指定方法中 transactionTemplate对象的execute() 方法的参数对象transactionCallbackWithoutResult的doInTransactionWithoutResult方法中 内部类中的变量需要用final修饰 */ public void transfer(final String outMan, final String inMan, final double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) { //业务代码 accountDao.out(outMan, money); int i = 1 / 0; accountDao.in(inMan, money); } }); } }
上面代码就是通过编码完成事务管理,就是编程式事务管理。我们的编码使用的都是默认的事务参数(管理级别、传播行为;可以使用transactionTemplate设置不同的参数)。
编程式事务管理缺点:
1.(编码时)事务管理严重侵入业务组件。造成两者强耦合,但是事务管理是属于系统层面的,业务组件是业务逻辑的一部分,所以应该解耦。
2.代码不易维护。事务管理策略代码书写复杂,两者耦合在一起不易维护。
2.9 声明式事务概述
1. 概念:
Spring 的声明式事务就是采用声明的方式来设置事务的属性参数,事务会按照指定的规则自动管理运行。
这里所说的声明,指在配置文件中配置(或添加注解)<事务的属性参数>,用在Spring 配置文件(注解)中声明的事务来代替用代码编写的事务。
2. 原理:
编写业务代码的时候并没有体现事务管理的代码
在业务代码运行过程中,Spring容器(中的事务管理器对象)跟据配置文件中的配置参数,动态的把事务加入到业务代码中。(AOP思想)
3. 特点:
**(编码时)事务管理不侵入业务组件。**因为事务管理是属于系统层面的,业务组件是业务逻辑的一部分,所以应该解耦。
**事务管理策略维护方便。**如果需要修改事务管理策略,只要在配置文件上修改,无需改变代码重新编译。
2.10基于XML的声明式事务管理快速入门
a) 编码前明确事项
切点 – 目标方法 - 业务层中的方法
通知 – 增强方法 – 事务管理器的事务管理方法
切面 – 目标方法 + 增强方法
织入 – 让目标方法和增强方法结合的过程
b) 编码步骤:
1. 引入tx命名空间
2. 配置切点
3. 配置通知(事务管理器)
4. 配置事务织入
5. 测试转账业务是否被正确被事务控制
c) 实现代码:
-
引入tx命名空间
<?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/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd ">
-
配置切点
<!--pointcut 目标对象 内部的方法就是切点--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean>
-
配置通知(事务管理器)
<!-- advice 配置通知-事务管理器就是通知(增强)所在的类 tx:attribute 内部参数稍后再讲 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice> <!-- jdbc 使用的(平台)事务管理器是 DataSourceTransactionManager 事务管理管理的是Service层,service层需要调用dao层操作数据库,dao层依赖DataSource管理连接池并连接数据库 所以事务管理器中需要注入DataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean>
-
配置织入
<!-- weaving 配置事务的织入。 增强方法 + 目标方法 --> <aop:config> <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service.impl.*.*(..))"/> </aop:config>
-
测试转账业务是否被正确被事务控制
//转账过程中如果发生异常,所有账户中金额不发生变动,就表示转账方法已经被事务管理起来了。
2.10基于XML的声明式事务管理参数详解
重点分析如下配置:
<!-- advice 配置通知-事务管理器就是通知(增强)所在的类 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
其中:
tx:advice
advice,增强方法所在的类(transactionManager的class)
因为要增强的内容是事务(为业务逻辑添加的事务),所以里面可以配置多个事务,
以及这些事务都分别匹配到哪些业务逻辑(不同的事务分别增强哪些方法)
tx:attributes
具体的事务需要配到这个标签里面
tx:method
每一个tx:method代表一个事务,在这个标签里面的属性上会配置某些业务逻辑的方法(目标方法),并同时配置对应的事务管理逻辑。
tx:nethod ~ name
name属性,用于配置哪些业务逻辑方法被当前事务增强。
该属性并非Spring事务本身的属性,而是为了让该事务匹配关联到某些业务逻辑方法。
符合切点表达式的所有方法会被拿过来进行同一个事务管理。
如果name值为*,则表示所有符合切点表达式的方法都被当前事务管理
如果是其他的值,则会根据规则匹配,比方说name="saveUser",就是匹配saveUser方法
name="save*",就是匹配所有save开头的方法
tx:nethod ~ isolation
isolation属性,配置当前事务的隔离级别;如不设置或设置DEFAULT,使用默认的隔离级别。
mysql默认的Repeatable read,其他大多数是Read committed。
tx:method ~ propagation
propagation属性,配置当前事务的传播行为。不设置则使用默认值REQUIRED。
tx:method ~ timeout
timeout属性,配置当前事务的超时时间,不设置则使用默认值-1,表示不限制。一般不设置。
tx:method ~ readonly
readonly属性,值为boolean类型。默认值为false。
如果是查询操作,建议设为true,优化查询性能;其他设为false或者不设置使用默认值。