Spring第四天—Spring中事务控制
文章目录
- Spring第四天---Spring中事务控制
- 1.1 Spring中JdbcTemplate
- 1.2 Spring事务
- 1.2.1 数据库事务的基础知识【参考自[Spring学习04:事务控制[TransactionManager]](https://blog.csdn.net/ncepu_Chen/article/details/94857481)】
- 1.2.2 spring事务的隔离级别(还可参考[事务的四种隔离级别](https://www.cnblogs.com/ubuntu1/p/8999403.html),[数据库事务4种隔离级别及7种传播行为(三)](https://www.cnblogs.com/dwxt/p/8807899.html))
- TransactionStatus: 事务状态信息对象,提供操作事务状态的方法如下:
- 1.3 spring事务配置--基于xml(声明式事务)
- 1.4 spring事务配置--半注解(声明式事务)
- 1.4 spring事务配置--纯注解(声明式事务)
1、 Spring中JdbcTemplate
2、 Spring基于AOP的事务控制
3、 Spring中的事务控制
基于XML的
基于注解的
1.1 Spring中JdbcTemplate
JdbcTemplate是Spring框架中提供的一个对象,对原始的JDBC API进行简单封装,其用法与DBUtils类似.
对应jar包依赖:
spring-jdbc-5.0.2.RELEASE.jar,除了要导入这个 jar 包
外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
- 操作关系型数据的:
JdbcTemplate
HibernateTemplate - 操作 nosql 数据库的:
RedisTemplate - 操作消息队列的:
JmsTemplate
JdbcTemplate的增删改查操作
使用JdbcTemplate实现增删改
JdbcTemplate的增删改操作可以使用其update(“SQL语句”, 参数…)方法
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
//保存
jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
jt.update("update account set name=?,money=? where id=?","test",4567,7);
//删除
jt.update("delete from account where id=?", 8);
使用JdbcTemplate实现查询
//查询所有
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
for (Account account:accounts) {
System.out.println(account);
}
//查询一个
List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);
在DAO层使用JdbcTemplate
按原有方式
多个daoImpl 就都要 声明JdbcTemplate 也都要 set,会显得比较麻烦。
/**
* @author Ven
* @version 1.0 2020/12/14
* 账户的持久层实现类 //这种方式可以基于xml ioc,也可以基于注解ioc,两种都可以,但第一种extends JdbcDaoSupport 继承
* spring的JdbcDaoSupport不好用注解,因为jdbcTemplate写在class里。
* 但AccountDaoImpl2这种写法 多个daoImpl 就都要 声明JdbcTemplate 也都要 set,会显得比较麻烦。
*/
public class AccountDaoImpl2 implements IAccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public Account findAccountById(Integer accountId) {
List<Account> accounts = jdbcTemplate.query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
...
DAO层对象继承JdbcDaoSupport
避免一个DAO 声明一次JdbcTemplate,set一次。但这种方式因为不能修改JdbcDaoSupport类的源代码,DAO层的配置就只能基于xml配置,而不再可以基于注解配置了。
- 让DAO对象继承Spring内置的JdbcDaoSupport类.在JdbcDaoSupport类中定义了JdbcTemplate和DataSource成员属性,在实际编程中,只需要向其注入DataSource成员即可,DataSource的set方法中会注入JdbcTemplate对象.
- DAO的实现类中调用父类的getJdbcTemplate()方法获得JdbcTemplate()对象.
JdbcDaoSupport类的源代码如下:
public abstract class JdbcDaoSupport extends DaoSupport {
@Nullable
private JdbcTemplate jdbcTemplate; // 定义JdbcTemplate成员变量
public JdbcDaoSupport() {
}
// DataSource的set方法,注入DataSource时调用createJdbcTemplate方法注入JdbcTemplate
public final void setDataSource(DataSource dataSource) {
if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource()) {
this.jdbcTemplate = this.createJdbcTemplate(dataSource);
this.initTemplateConfig();
}
}
// 创建JdbcTemplate,用来被setDataSource()调用注入JdbcTemplate
protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
// JdbcTemplate的get方法,子类通过该方法获得JdbcTemplate对象
@Nullable
public final JdbcTemplate getJdbcTemplate() {
return this.jdbcTemplate;
}
@Nullable
public final DataSource getDataSource() {
return this.jdbcTemplate != null ? this.jdbcTemplate.getDataSource() : null;
}
public final void setJdbcTemplate(@Nullable JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.initTemplateConfig();
}
// ...
}
/**
* @author Ven
* @version 1.0 2020/12/14
* 账户的持久层实现类
*/
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
public Account findAccountById(Integer accountId) {
List<Account> accounts = super.getJdbcTemplate().query("select * from account where id=?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
return accounts.isEmpty()?null:accounts.get(0);
}
...
对应的bean.xml DAO只需要注入dataSource即可。
<?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="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- <property name="jdbcTemplate" ref="jdbcTemplate"></property>-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置JdbcTemplate
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource">
</property>
</bean>-->
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
</beans>
ps:很多参考了 Spring学习03:数据库操作模板JdbcTemplate
1.2 Spring事务
需导入的jar包:spring-tx-5.0.2.RELEASE.jar
PlatformTransactionManager 此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法。我们一般使用它的实现类,如下:
真正管理事务的对象
- org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时使用
- org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本进行持久化数据时使用
1.2.1 数据库事务的基础知识【参考自Spring学习04:事务控制[TransactionManager]】
事务的四大特性:ACID
- 1.
原子性(Atomicity)
: 事务包含的所有操作要么全部成功,要么全部失败回滚;成功必须要完全应用到数据库,失败则不能对数据库产生影响. - 2.
一致性(Consistency)
:事务执行前和执行后必须处于一致性状态.例如:转账事务执行前后,两账户余额的总和不变. - 3.
隔离性(Isolation)
: 多个并发的事务之间要相互隔离. - 4.
持久性(Durability)
: 事务一旦提交,对数据库的改变是永久性的.
事务的隔离级别
- 1.
ISOLATION_READ_UNCOMMITTED
: 读未提交.事务中的修改,即使没有提交,其他事务也可以看得到.会导致脏读,不可重复读,幻读. - 2.
ISOLATION_READ_COMMITTED
: 读已提交(Oracle数据库默认隔离级别).一个事务不会读到其它并行事务已修改但未提交的数据.避免了脏读,但会导致不可重复读,幻读. - 3.
ISOLATION_REPEATABLE_READ
: 可重复读(Mysql数据库默认的隔离级别).一个事务不会读到其它并行事务已修改且已提交的数据,(只有当该事务提交之后才会看到其他事务提交的修改).避免了脏读,不可重复读,但会导致幻读. - 4.
ISOLATION_SERIALIZABLE
: 串行化.事务串行执行,一个时刻只能有一个事务被执行.避免了脏读,不可重复读,幻读.
可以通过下面的例子理解事务的隔离级别:
事务A | 事务B |
---|---|
启动事务查询得到原始值origin =1 | |
启动事务 | |
查询得到值1 | |
将1改成2 | |
查询得到值value1 | |
提交事务B | |
查询得到值value2 | |
提交事务A | |
查询得到值value3 |
对不同的事务隔离级别,事务A三次查询结果分别如下:
事务隔离级别 | 原始值origin | value1 | value2 | value3 |
---|---|---|---|---|
ISOLATION_READ_UNCOMMITTED | 1 | 2(脏读) | 2 | 2 |
ISOLATION_READ_COMMITTED | 1 | 1 | 2(不可重复读) | 2 |
ISOLATION_REPEATABLE_READ | 1 | 1 | 1 | 2 |
ISOLATION_SERIALIZABLE | 1 | 1 | 1 | 1 |
事务的安全隐患有如下三种,他们可以通过设置合理的隔离级别来避免:
- 脏读: 一个事务读到另外一个事务还未提交(可能被回滚)的脏数据.
- 不可重复读: 一个事务执行期间另一事务提交修改,导致第一个事务前后两次查询结果不一致.
- 幻读: 一个事务执行期间另一事务提交添加数据,导致第一个事务前后两次查询结果到的数据条数不同.
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
ISOLATION_READ_UNCOMMITTED | ✓ | ✓ | ✓ |
ISOLATION_READ_COMMITTED | ✕ | ✓ | ✓ |
ISOLATION_REPEATABLE_READ | ✕ | ✕ | ✓ |
ISOLATION_SERIALIZABLE | ✕ | ✕ | ✕ |
1.2.2 spring事务的隔离级别(还可参考事务的四种隔离级别,数据库事务4种隔离级别及7种传播行为(三))
- 1.ISOLATION_DEFAULT: Spring事务管理的的默认级别,使用数据库默认的事务隔离级别.
- 2.ISOLATION_READ_UNCOMMITTED: 可以读取未提交数据
- 3.ISOLATION_READ_COMMITTED: 只能读取已提交数据,解决脏读问题(Oracle默认级别)。即事务B提交了修改记录后,事务A在B提交前和提交后查询得到的结果是不一样的,不可重复读。
- 4.ISOLATION_REPEATABLE_READ: 可重复读.即两次读取内容一样不会变。(Mysql默认级别) 事务B提交了修改记录后,事务A在B提交前和提交后查询得到的结果是一样的,可重复读。只有A也提交后,值才会改变。
- 5.ISOLATION_SERIALIZABLE: 串行化. 解决幻影读的问题。事务A未提交前,B不能更改。
事务的传播行为
REQUIRED
:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式运行,如果当前存在事务,抛出异常
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
超时时间
Spring默认设置事务的超时时间为-1,表示永不超时。如果有,以秒为单位进行设置。
是否已读
Spring默认设置为false,建议查询
操作中设置为true
.
TransactionStatus: 事务状态信息对象,提供操作事务状态的方法如下:
- void flush(): 刷新事务
- boolean hasSavepoint(): 查询是否存在存储点
- boolean isCompleted(): 查询事务是否完成
- boolean isNewTransaction(): 查询是否是新事务
- boolean isRollbackOnly(): 查询事务是否回滚
- void setRollbackOnly(): 设置事务回滚
1.3 spring事务配置–基于xml(声明式事务)
1、配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2、配置事务的通知
- 此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
<?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">
</beans>
- 使用tx:advice标签配置事务通知
- 属性:
- id:给事务通知起一个唯一标识
- transaction-manager:给事务通知提供一个事务管理器引用
- 属性:
<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS.
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚;产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
-->
<tx:attributes>
<!--当既有*又有find*,两个都能匹配时,find*优先级更高。 这种写法要求查询方法名 必须都是find开头-->
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
<!--配置aop-->
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
<!--建立切入点表达式和事务通知的对应关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
5、配置事务的属性
是在事务的通知tx:advice标签的内部
其中事务的属性(对应标签为<tx:method>)
- 1.
name
:拦截到的方法,可以使用通配符* - 2.
isolation
:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 - 3.
propagation
:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS. - 4.
read-only
:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 - 5.
timeout
:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 - 6.
rollback-for
:用于指定一个异常,当产生该异常时,事务回滚;产生其他异常时,事务不回滚。没有默认值,表示任何异常都回滚。 - 7.
no-rollback-for
:用于指定一个异常,当产生该异常时,事务不回滚;产生其他异常时事务回滚。没有默认值,表示任何异常都回滚。
1.4 spring事务配置–半注解(声明式事务)
导入相关约束,不再赘述。
1、配置事务管理器
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
2、开启spring对注解事务的支持
<!--开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在需要事务支持的地方使用@Transacitonal注解
@Transactional注解,其参数与<tx:method>的属性一致.
该注解可以加在接口,类或方法上
- 对接口加上@Transactional注解,表示对该接口的所有实现类进行事务控制
- 对类加上@Transactional注解,表示对类中的所有方法进行事务控制
- 对具体某一方法加以@Transactional注解,表示对具体方法进行事务控制
三个位置上的注解优先级依次升高。
/**
* 账户的业务层实现类
*
* 事务
*
*/
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true) //只读型事务的配置
public class AccountServiceImpl implements IAccountService {
@Autowired
private IAccountDao accountDao;
public Account findAccountById(Integer accountId) {
return accountDao.findAccountById(accountId);
}
@Transactional(propagation = Propagation.REQUIRED,readOnly = false)
public void transfer(String sourceName, String targetName, Float money) {
...
}
1.4 spring事务配置–纯注解(声明式事务)
其实就是把xml相关的改为配置类来实现创建,且创建完通过注解放到spring容器中。同时告诉spring这是配置类。
1.创建主配置类SpringConfiguration.java
@EnableTransactionManagement
事务的注解,表明开启事务控制。
/**
* @author Ven
* @version 1.0 2020/12/16
* spring的配置类,相当于bean.xml
*/
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
2.创建和数据库连接相关的配置类
- @Bean(name = “jdbcTemplate”)告诉spring创建完放到容器中
@Value("${jdbc.driver}")
和主配置类@PropertySource("jdbcConfig.properties")
关联使用
/**
* @author Ven
* @version 1.0 2020/12/16
* 和连接数据库相关的配置类
*/
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate对象
* @param dataSource
* @return
*/
// @Bean这样创建完才会进容器
@Bean(name = "jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
// 同理,创建完把它放容器中
@Bean(name = "dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
3.创建和事务相关配置类
/**
* @author Ven
* @version 1.0 2020/12/16
* 和事务相关的配置类
*/
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name = "transactionManager")
public PlatformTransactionManager createTransacitonManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
4.测试类导入方式变为加载主配置类
/**
* 使用Junit单元测试:测试我们的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}