事务管理
在 Spring 框架中事务管理有两种方式:
一种是传统的编程式事务管理,即通过编写代 码实现的事务管理;
另一种是基于 AOP 技术实现的声明式事务管理。
由于在 Spring 框架中, 编程式事务管理很少使用,所以我们只对 Spring 的声明式事务管理进行详细讲解
事务
在说spring实现事务管理之前,先来重温一下事务的一些概念,有助于后面的学习
事务的概念
事务是作为一个逻辑单元执行的一系列操作。一个事务工作单元必须有四个特性:原子性、一致性、隔离性、持久性。只有这样才能成为一个事务
事务的作用
事物对于数据库的作用是对数据的一系列操作,要么全部成功,要么全部失败,防止中间状态的出现,以确保数据库中的数据始终处于正确的状态
事务的ACID特性
- 原子性:事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功, 要么全部失败
- 一致性:事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,也就是不一致的状态
- 隔离性:并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时 一样。比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结 果一样
- 持久性:事务一旦提交,其对数据库的更新就是持久的。任何事务或系统故障 都不会导致数据丢失
一致性是事务的最终目的,原子性、隔离性、持久性都是为了实现一致性
事务的隔离性
不考虑隔离性将会出现的问题
- 脏读:脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据
- 不可重复读:是指 A 事务读取到了 B 事务提交更改前后的数据,在同个时间段内, 两次查询的结果不一致
- A 事务读取到 B 事务提交的新增数据,幻象读一般发生在数据统计 事务中(换句话说,就是B事务未提交时已经读了一次,然后B事务提交后又读了一次,读了两次)
解决方案——设置隔离等级
- Read Uncommited(读取未提交内容)
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。但是,读未提交产生了脏读,采用 Read Commited 可以解决脏读问题 - Read Commited(提取提交内容)
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。读提交,若有事务对数据进行更新操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但是,读提交两次查询会产生不同的查询结果,就会造成不可重复读问题,采用 Repeatable Read 可以解决此问题 - Repeated Read(重复读)
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作。重复读可以解决不可重复读问题。应该明白的一点就是,不可重复读对应的是修改,即 update操作。但是可能还会有幻读问题。因为幻读问题对应的是insert操作,而不是update操作。采用 Serializable 可以解决幻读问题 - Serializable(可串行化)
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用
注意:
- 大多数数据库默认的事务隔离级别是 Read committed,比如 Sql Server , Oracle
- Mysql 的默认隔离级别是 Repeatable read
- 隔离级别的设置只对当前链接有效。对于使用 MySQL 命令窗口而言,一个窗口就 相当于一个链接,当前窗口设置的隔离级别只对当前窗口中的事务有效;对于JDBC 操作数据库来说,一个 Connection 对象相当于一个链接,而对于 Connection 对象 设置的隔离级别只对该 Connection 对象有效,与其他链接 Connection 对象无关
- 隔离等级越高,效率越低
事务的传播行为
事务传播行为是指:多个含有事务的方法相互调用时,事务如何在这些方法间传播
共有如下七种表现形式
- REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行
- MANDATORY:支持当前事务,如果当前没有事务,就抛出异常
- REQUIRES_NEW:新建事务,如果当前存在事务,就把当前事务挂起
- NOT_SUPPORTS:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
- NESTED:必须在事务状态下执行,如果没有事务,新建事务,如果当前有事务,创建一个嵌套事务
声明式事务管理
Spring 实现声明式事务管理主要有两种方式:
- 基于 XML 文件方式的声明式事务管理。
- 通过 Annotation 注解方式的事务管理。
使用XML文件方式实现声明式事务管理
- 创建实体类Users
public class Users {
private int userid;
private String username;
private String usersex;
public int getUserid() {
return userid;
}
public void setUserid(int userid) {
this.userid = userid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsersex() {
return usersex;
}
public void setUsersex(String usersex) {
this.usersex = usersex;
}
}
- 创建实体类Orders
public class Orders {
private int orderid;
private double orderprice;
public int getOrderid() {
return orderid;
}
public void setOrderid(int orderid) {
this.orderid = orderid;
}
public double getOrderprice() {
return orderprice;
}
public void setOrderprice(double orderprice) {
this.orderprice = orderprice;
}
}
- 创建持久层
public interface UsersDao {
void insertUsers(Users users);
void insertOrders(Orders orders);
}
public class UsersDaoImpl implements UsersDao {
private JdbcTemplate jdbcTemplate;
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
// 添加用户
@Override
public void insertUsers(Users users) {
String sql = "insert into users values(defaulta,?,?)";
Object[] args = new Object[]{users.getUsername(),users.getUsersex()};
this.jdbcTemplate.update(sql,args);
}
//添加订单
@Override
public void insertOrders(Orders orders) {
String sql = "insert into orders values(default,?,null)";
Object[] args = new Object[]{orders.getOrderprice()};
this.jdbcTemplate.update(sql,args);
}
}
- 创建业务层
public interface UsersService {
void addUsersAndOrders(Users users, Orders orders);
}
public class UsersServiceImpl implements UsersService {
private UsersDao usersDao;
public UsersDao getUsersDao() {
return usersDao;
}
public void setUsersDao(UsersDao usersDao) {
this.usersDao = usersDao;
}
// 添加用户与订单
@Override
public void addUsersAndOrders(Users users, Orders orders) {
this.usersDao.insertOrders(orders);
this.usersDao.insertUsers(users);
}
}
- 修改xml文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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" >
<!--配置解析 Properties 文件的工具类-->
<context:property-placeholder location="db.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSourc e">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置 JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置持久层-->
<bean id="usersDao" class="com.lanh.dao.impl.UsersDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--配置业务层-->
<bean id="usersService" class="com.lanh.service.impl.UsersServiceImpl">
<property name="usersDao" ref="usersDao"/>
</bean>
<!--配置事务管理器切面对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--需要注入数据源对象-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置事务管理器属性-->
<tx:advice id="txAdvice">
<tx:attributes>
<!-- name:执行受事务控制的方法。配置方式 1,给定完全方法名,通过* 统配符指定方法名-->
<!--propagation:配置事务的传播行为-->
<tx:method name="add*" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!--配置切面-->
<aop:config>
<!--配置切点-->
<aop:pointcut id="txPointcut" expression="execution(* com.lanh.service.*.*(..))"/>
<!--配置切面-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>
</beans>
- 实现事务管理
public class Test {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UsersService usersService = (UsersService)applicationContext.getBean("usersService");
Users users = new Users();
users.setUsername("lanh");
users.setUsersex("male");
Orders orders = new Orders();
orders.setOrderprice(666);
usersService.addUsersAndOrders(users,orders);
}
}
使用注解方式实现声明式事务管理
- @Transactional 注解可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该 注解来覆盖类级别的定义
- 虽然@Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建 议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效
- @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质 决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解, 这将被忽略,也不会抛出任何异常
以下代码皆是在上面的xml方式实现事务管理的基础上进行部分修改
- 修改业务层
public interface UsersService {
void addUsersAndOrders(Users users, Orders orders);
}
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)
public class UsersServiceImpl implements UsersService {
private UsersDao usersDao;
public UsersDao getUsersDao() {
return usersDao;
}
public void setUsersDao(UsersDao usersDao) {
this.usersDao = usersDao;
}
// 添加用户与订单
@Override
public void addUsersAndOrders(Users users, Orders orders) {
this.usersDao.insertOrders(orders);
this.usersDao.insertUsers(users);
}
}
- xml文件
<?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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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" >
<!--配置解析 Properties 文件的工具类-->
<context:property-placeholder location="db.properties"/>
<!--配置数据源-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSourc e">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--配置 JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置持久层-->
<bean id="usersDao" class="com.lanh.dao.impl.UsersDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--配置业务层-->
<bean id="usersService" class="com.lanh.service.impl.UsersServiceImpl">
<property name="usersDao" ref="usersDao"/>
</bean>
<!--配置事务管理器切面对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--需要注入数据源对象-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--注册事务管理驱动-->
<tx:annotation-driven transaction-manager="txTransactionManager"/>
</beans>
总结
其实两种方式的相差并不大,但是xml文件方式需要配置的属性较为繁杂,而注解方式则是利用一个注解@Transactional,同时在xml文件中注册事务管理驱动即可。相比之下,显然时注解方式更胜一筹。二者也就是人们所说的约定大于配置吧