AOP的应用(事务管理)
说说Spring事务管理
什么是事务?
事务(Transaction),一般是指要做的或所做的事情。指一个工作单元,它包含了一组数据操作命令,并且所有的命令作为一个整体一起向系统提交或撤消请求操作,即这组命令要么都执行,要么都不执行。 在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。
spring事务管理
Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring并不直接管理事务,而是提供了多种事务管理器。
Spring 支持两种类型的事务管理:
- 编程式事务管理 :这意味着你在编程的帮助下有管理事务。这给了你极大的灵活性,但却很难维护。
- 声明式事务管理 :这意味着你从业务代码中分离事务管理。你仅仅使用注释或 XML 配置来管理事务。
本文就Spring声明式事务为主要研究对象,但是很多事务概念、接口抽象和实现方式同时适用于其他情况。
声明式事务
什么是声明式事务?
声明式事务(declarative transaction management)是Spring提供的对程序事务管理的方式之一。
Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的声明,就是指在配置文件中声明。用在Spring配置文件中声明式的处理事务来代替代码式的处理事务。
使用声明式事务的好处
好处是,事务管理不侵入开发的代码,具体来说,事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可;在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便。
Spring使用AOP来完成声明式的事务管理,因而声明式事务是以方法为单位,Spring的事务属性自然就在于描述事务应用至方法上的策略。
两种方式实现使用声明式事务:
1.xml配置方式
2.注解式
事务管理器
Spring框架支持事务管理的核心是事务管理器抽象,Spring并不直接管理事务,而是提供了多种事务管理器,将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。对于不同的数据访问框架(如Hibernate)通过实现策略接口PlatformTransactionManager,从而能支持各种数据访问框架的事务管理。
PlatformTransactionManager
当我们用到事务管理器时,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager 。
Spring的事务处理中,通用的事务处理流程框架是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如 DataSourceTransactionManager 、JtaTransactionManager和 HibernateTransactionManager等。
DataSourceTransactionManager
我们以往使用JDBC进行数据操作,通常使用DataSource,从数据源中得到Connection,我们知道数据源是线程安全的,而连接不是线程安全的,所以对每个请求都是从数据源中重新取出一个连接。一般的数据源由容器进行管理,包括连接池。
DriverManagerDataSource实现了javax.sql.DataSource接口,(Spring框架自带的数据源,不常用):每个连接请求时都新建一个连接,DriverManagerDataSource提供的连接没有进行池管理,当连接数到达一定的大小会出现异常。 DriverManagerDataSource建立连接是只要有连接就新建一个connection,根本没有连接池的作用。
用DataSourceTransactionManager实现类获取数据源,代码示例:
package com.lanou3g.spring.transaction.annotation.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
import java.util.Properties;
/**
* 配置数据源
*/
@PropertySource("classpath:jdbc.properties")
@Component
public class MyDataSource extends DriverManagerDataSource{
public MyDataSource(@Value("${jdbc.driver}") String driver, @Value("${jdbc.url}") String url, @Value("${jdbc.user}") String userName, @Value("${jdbc.password}") String password, @Value("${jdbc.characterEncoding}") String characterEncoding) {
super.setDriverClassName(driver);
super.setUrl(url);
super.setUsername(userName);
super.setPassword(password);
Properties conProperties = new Properties();
conProperties.setProperty("characterEncoding", characterEncoding);
super.setConnectionProperties(conProperties);
}
}
XML配置方式
使用XML配置方式,主要实在spring配置文件中配置spring的事务管理器,下面就主要说明如何在teacher_conf.xml中配置事务管理器,主要是连接数据库teacher进行操作。
先将java实体类Teacher和实现TeacherDaoImpl类展示:
package com.lanou3g.spring.transaction.bean;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Teacher {
private Integer id;
private String tname;
public Teacher() {
}
public Teacher(Integer id, String tname) {
this.id = id;
this.tname = tname;
}
public Teacher(String tname) {
this.tname = tname;
}
}
@Setter
@Getter
public class TeacherDaoImpl {
private JdbcTemplate jdbcTemplate;
/**
* 查询teacher表所有数据
* @return
*/
public List<Teacher> queryAll() {
List<Teacher> teachers = jdbcTemplate.query("select * from teacher", new BeanPropertyRowMapper<Teacher>(Teacher.class));
return teachers;
}
/**
* 插入teacher
* @param teacher
* @return 返回影响行数
*/
public int insertTeacher(Teacher teacher) {
int result = jdbcTemplate.update("insert into teacher (tname) values (?)", teacher.getTname());
return result;
}
}
配置tx命名空间
先通过xml配置声明式事务,我们需要添加tx命名空间
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
配置准备
通过xml方式配置Spring声明式事务,开始先做些必要的准备配置:
- 扫描配置类
- 创建JdbcTemplate的bean(JdbcTemplate是Spring框架自带的对JDBC操作的封装,目的是提供统一的模板方法使对数据库的操作更加方便、友好,效率也不错。但是功能还是不够强大(比如不支持级联属性))
- 创建TeacherDaoImpl类bean
<!--扫描该类所在的包下所有的配置类-->
<context:component-scan base-package="com.lanou3g.spring.transaction" />
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
<bean id="teacherDao" class="com.lanou3g.spring.transaction.dao.TeacherDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
配置数据源,初始化事务管理器
代码示例:
创建jdbc.properties文件
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.driver=com.mysql.jdbc.Driver
jdbc.user=root
jdbc.password=root
jdbc.characterEncoding=utf8
先配置数据源,
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="${jdbc.url}" />
<property name="driverClassName" value="${jdbc.driver}" />
<property name="username" value="${jdbc.user}" />
<property name="password" value="${jdbc.password}" />
<property name="connectionProperties">
<props>
<prop key="characterEncoding">${jdbc.characterEncoding}</prop>
</props>
</property>
</bean>
配置好了数据源,那么我们就可以进行初始化事务管理器了
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置事务AOP通知
上面已初始化事务管理器,接下来就是配置事务AOP通知
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" rollback-for="ArithmeticException" />
<tx:method name="query*" isolation="READ_UNCOMMITTED" read-only="true"/>
</tx:attributes>
</tx:advice>
定义AOP配置
定义AOP配置(将上面的通知和表达式组装到一起)
<aop:config>
<aop:pointcut id="all_dao_method" expression="execution(* com.lanou3g.spring.transaction.dao.*.*(..))" />
<aop:advisor advice-ref="txAdvice" pointcut-ref="all_dao_method" />
</aop:config>
到此,通过xml方式配置Spring声明式事务结束。
注解配置方式
说完xml配置,接下来来讲一下注解式
注解式配置数据源(DriverManagerDataSource)
package com.lanou3g.spring.transactionAop.Annotation.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.stereotype.Component;
import java.util.Properties;
@PropertySource("classpath:jdbc.properties")
@Component
public class MyDataSource extends DriverManagerDataSource {
public MyDataSource(@Value("${jdbc.driver}") String diver, @Value("${jdbc.url}") String url,
@Value("${jdbc.user}") String user,
@Value("${jdbc.password}") String password,
@Value("${jdbc.characterEncoding}") String characterEncoding){
super.setDriverClassName(diver);
super.setUrl(url);
super.setPassword(password);
super.setUsername(user);
Properties properties=new Properties();
properties.setProperty("characterEncoding",characterEncoding);
super.setConnectionProperties(properties);
}
}
开启事务注解支持
注解方式开启:
@Configuration
@EnableTransactionManagement
public class MyTransactionConf {
}
配置事务管理器
1.定义JdbcTemplate对象,Spring给我们封装了所有的JDBC操作
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
- 定义事务管理器bean
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
在需要事务的方法上添加事务注解
在TeacherDaoImpl类中写了两个方法:一个查询一个插入。
并在这两个方法上添加事务注解@Transactional,通过@Transactional 开启事务、设置事务属性
package com.lanou3g.spring.transaction.annotation.dao;
import com.lanou3g.spring.transaction.annotation.bean.Teacher;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Setter
@Getter
@Repository// 此注解和@Component作用一样, 只是含有特定的语义(一般用来标注dao层的类)
public class TeacherDaoImpl {
@Autowired
private JdbcTemplate jdbcTemplate;
// 通过@Transactional 开启事务、设置事务属性
@Transactional(readOnly = true)
public List<Teacher> queryAll() {
List<Teacher> teachers = jdbcTemplate.query("select * from teacher", new BeanPropertyRowMapper<Teacher>(Teacher.class));
return teachers;
}
// 通过@Transactional 开启事务、设置事务属性
@Transactional(rollbackFor = {ArithmeticException.class})
public int insertTeacher(Teacher teacher) {
int result = jdbcTemplate.update("insert into teacher (tname) values (?)", teacher.getTname());
return result;
}
}
@Transactional属性说明
- value: 指定特定的事务管理器,默认是transactionManager
- 其他属性和xml中的的属性类似
Spring事务的隔离级别和传播行为
七种传播行为
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法的时事务如何传播。在代码层指定Spring事务传播行为,就指明了事务的控制范围。Spring定义了七种传播行为,下面简单介绍下七种传播行为:
1. PROPAGATION_REQUIRED – 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
2. PROPAGATION_SUPPORTS – 支持当前事务,如果当前没有事务,就以非事务方式执行。
3. PROPAGATION_MANDATORY – 支持当前事务,如果当前没有事务,就抛出异常。
4. PROPAGATION_REQUIRES_NEW – 新建事务,如果当前存在事务,把当前事务挂起。
5. PROPAGATION_NOT_SUPPORTED – 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
6. PROPAGATION_NEVER – 以非事务方式执行,如果当前存在事务,则抛出异常。
7. PROPAGATION_NESTED – 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
注:常用的两个事务传播属性是1和4,即PROPAGATIONREQUIRED PROPAGATIONREQUIRES_NEW
五种隔离级别
在Spring中定义了5中不同的事务隔离级别。
隔离级别 | 说明 |
ISOLATION_DEFAULT | 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别. |
ISOLATIONREADUNCOMMITTED | 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。 这种隔离级别会产生脏读,不可重复读和幻像读。 |
ISOLATIONREADCOMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。 |
ISOLATIONREPEATABLEREAD | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。 |
ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 除了防止脏读,不可重复读外,还避免了幻像读。 |
如果不考虑隔离性引发安全性问题:
幻读(虚读)
事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;
通俗的说,幻读就是指在一个事务内读取了别的事务插入的数据,导致前后读取不一致(insert)
不可重复读取
事务1读取记录时,事务2更新了记录并提交,事务1再次读取时可以看到事务2修改后的记录;
在一个事务内读取表中的某一行数据,多次读取结果不同.一个事务读取到了另一个事务提交后的数据.
脏读
事务1更新了记录,但没有提交,事务2读取了更新后的行,然后事务T1回滚,现在T2读取无效。
通俗的说,脏读就是指一个事务读取了一个未提交事务的数据
回滚事务
回滚特定异常(rollback-for)
rollback-for 在该方法中有异常则进行回滚 。
代码示例
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" read-only="true" rollback-for="NoProductInStockException"/>
<tx:method name="query*"/>
</tx:attributes>
</tx:advice>
不回滚特定异常(no-rollback-for)
在你遇到异常不想回滚事务的时候,同样的你也可使用no-rollback-for来指定不回滚的规则。
代码示例
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="updat" no-rollback-for="InstrumentNotFoundException"/>
<tx:method name="delete"/>
</tx:attributes>
</tx:advice>