目录
5.1 Spring事务管理概述
Spring事务管理简化了传统的事务管理流程,减少了开发量
5.1.1 事务管理的核心接口
在Spring的所有JAR包中包含一个名为Spring-tx-4.3.6.RELEASE的JAR包,该包就是Spring提供的用于事务管理的依赖包。在该JAR包的org.Springframework.transaction包中有3个接口文件:
1. PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的平台事务管理器,主要用于管理事务。该接口中提供了3个事务操作的方法
- TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息。该方法会根据TransactionDefinition参数返回一个TransactionStatus对象。TransactionStatus对象表示一个事务,被关联在当前执行的线程上。
- void commit(TransactionStatus status):用于提交事务。
- void rollback(TransactionStatus status):用于回滚事务。
TransactionDefinition和TransactionStatus,PlatformTransactionManager接口只是代表事务管理的接口,并不知道底层是如何管理事务的,它只需要事务管理提供上面的3个方法,但具体如何管理事务则由它的实现类来完成。
PlatformTransactionManager,PlatformTransactionManager接口有许多不同的实现类,常见的几个实现类如下。
- org.springframework.jdbc.datasource.DataSourceTransactionManager:用于配置JDBC数据源的事务管理器。
- org.springframework.orm.Hibernate4.HibernateTransactionManager:用于配置Hibernate的事务管理器。
- org.springframework.transaction.jta.JtaTransactionManager:用于配置全局事务管理器。
当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类即可。
这里使用的是MyBatis
2. TransactionDefinition
TransactionDefinition接口是事务定义(描述)的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法
- string getName():获取事务对象名称。
- int getlsolationLeve():获取事务的隔离级别
- int getPropagationBehavior():获取事务的传播行为。
- int setTimeout():获取事务的超时时间。
- boolean isReadOnly():获取事务是否只读。
上述方法中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种
3. TransactionStatus
TransactionStatus接口是事务的状态,描述了某一时间点上事务的状态信息。该接口中包含6个方法
- void flush():刷新事务。
- boolean hasSavepoint():获取是否存在保存点。
- boolean isCompleted():获取事务是否完成。
- boolean isNewTransaction():获取是否是新事务。
- boolean isRollbackOnly():获取是否回滚。
- void setRollbackOnly():设置事务回滚。
5.1.2 事务管理的方式
Spring中的事务管理分为两种方式
- 传统的编程序事务管理
编程序事务管理:通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。 - 声明式事务管理
声明式事务管理:通过AOP技术实现的事务管理,其主要思想是将事务管理作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”代码植入业务目标类中。
声明式事务管理最大的优点在于开发者无须通过编程的方式来管理事务,只需在配置文件中进行相关的事务规则声明,就可以将事务规则应用到业务逻辑中。
5.2 声明式事务管理
Spring的声明式事务管理可以通过两种方式来实现:
- 基于XML的方式
- 基于Annotation的方式
5.2.1 基于XML方式的声明式事务
基于XML方式的声明式事务管理是通过在配置文件中配置事务规则的相关声明来实现的。
- Spring 2.0以后,提供了tx命名空间来配置事务,tx命名空间下提供了< tx:advice>元素来配置事务的通知(增强处理)。当使用< tx:advice>元素配置了事务的增强处理后,就可以通过编写的AOP配置让Spring自动对目标生成代理。
配置< tx:advice>元素时,通常需要指定id和transaction-manager属性,其中id属性是配置文件中的唯一标识,transaction-manager属性用于指定事务管理器。除此之外,还需要配置一个< tx:attributes>子元素,该子元素可通过配置多个< tx:method>子元素来配置执行事务的细节。
关于< tx:method>元素的属性描述如图
实例一,基于Xml方式的声明式事务
-
创建项目(模拟一个会员赠送积分的功能,要求在赠送积分时通过Spring对事务进行控制,即是进行异常发现并处理),引入jar包。
-
修改数据库中的数据表user,增加字段jf(积分),设置初始积分全部为1000,
-
在User类中增加jf成员变量和对应的构造方法
package com.ssm.jdbc;
public class User {
private Integer id;
private String username;
private String password;
private Integer jf;
public Integer getJf() {
return jf;
}
public void setJf(Integer jf) {
this.jf = jf;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", jf=" + jf + "]";
}
}
- 在UserDao中增加一个对积分的操作的方法
package com.ssm.jdbc;
import java.util.List;
public interface UserDao {
public int addUser(User user);
public int updateUser(User user);
public int deleteUser(int id);
//通过id查询用户
public User findUserById(int id);
//查询所有用户
public List<User> findAllUser();
//赠送积分
public void transfer(String outUser, String inUser, Integer jf);
}
- 在实现类中实现这个方法
package com.ssm.jdbc;
import java.util.List;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public class UserDaoImpl implements UserDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public int addUser(User user) {
String sql="insert into user(username,password) value(?,?)";
Object[] obj=new Object[]{
user.getUsername(),
user.getPassword()
};
int num=this.jdbcTemplate.update(sql,obj);
return num;
}
public int updateUser(User user) {
String sql="update user set username=?,password=? where id=?";
Object[] params=new Object[]{
user.getUsername(),
user.getPassword(),
user.getId()
};
int num=this.jdbcTemplate.update(sql,params);
return num;
}
public int deleteUser(int id) {
String sql="delete from user where id=?";
int num=this.jdbcTemplate.update(sql,id);
return num;
}
//通过id查询用户数据信息
public User findUserById(int id) {
String sql="select * from user where id=?";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.queryForObject(sql,rowMapper,id);
}
//查询所有用户数据信息
public List<User> findAllUser() {
String sql="select * from user";
RowMapper<User> rowMapper=new BeanPropertyRowMapper<User>(User.class);
return this.jdbcTemplate.query(sql,rowMapper);
}
//赠送积分
public void transfer(String outUser, String inUser, Integer jf) {
//接收积分
this.jdbcTemplate.update("update user set jf=jf+? where username=?",jf,inUser);
//模拟系统运行时的突发性问题
int i=1/0;
//赠送积分
this.jdbcTemplate.update("update user set jf =jf-? where username=?", jf, outUser);
}
}
在上述代码中,使用了两个update()方法对user表中的数据执行接收积分和赠送积分的更新操作。在两个操作之间添加了一行代码“int i=1/0;”来模拟系统运行时的突发性问题。如果没有事务控制,那么在transfer()方法执行后,接收积分用户的积分会增加,而赠送积分用户的积分会因为系统出现问题而不变,这显然是有问题的;如果增加了事务控制,那么在transfer()方法操作执行后,接收积分用户的积分和赠送积分用户的积分在问题出现前后都应该保持不变。
- 修改配置文件,增加编写事务管理的相关配置代码
<?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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--1.配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的ur1 -->
<property name="url" value="jdbc:mysql://localhost:3306/db_spring" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="root" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定义id为userDao的Bean -->
<bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
<!--将 jdbcTemplate注入到 userDao实例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事务管理器,依赖于数据源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.编写通知:对事务进行增强(通知),需要编写对切入点和具体执行事务细节 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
</tx:attributes>
</tx:advice>
<!--6.编写aop,让spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!--切入点 -->
<aop:pointcut expression="execution(* com.ssm.jdbc.*.*(..))" id="txPointCut" />
<!--切面 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
- 创建测试类
@Test
public void xmlTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
userDao.transfer("zhangsan","lisi", 100);
System.out.println("赠送积分成功!");
}
如果事务代码起作用,那么在整个赠送积分方法执行完毕后,zhangsan和lisi的积分应该都是原来的值。
执行完测试方法后,JUnit控制台的显示结果如图。
从中可以看到,JUnit控制台中报出了“/ y zero”的算术异常信息。
在执行赠送积分操作后,查看user表中的数据,zhangsan和lisi的积分没有发生变化,这说明Spring中的事务管理配置已经生效。
5.2.2 基于Annotation方式的声明式事务
Spring的声明式事务管理还可以通过Annotation的方式实现
第一步
在Spring容器中注册事务注解驱动
< tx:annotation-driven transaction-managers
ntransactionManager"/>
第二步
在需要使用事务的Spring Bean类或者Bean类的方法上添加注解@Transactional。
如果将注解添加在Bean类上,就表示事务的设置对整个Bean类的所有方法都起作用;如果将注解添加在Bean类中的某个方法上,就表示事务的设置只对该方法有效。
使用@Transactional注解时,可以通过其参数配置事务详情。
@Transactional注解可配置的参数信息如图
从图可以看出,@Transactional注解与< tx:method>元素中的事务属性基本是对应的,并且其含义也基本相似。
实例二,基于注解方式的声明式事务
在上例中直接进行修改
- 创建新的配置文件applicationContext-annotation.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-4.3.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!--1.配置数据源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!--数据库驱动 -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<!--连接数据库的ur1 -->
<property name="url" value="jdbc:mysql://localhost:3306/db_spring" />
<!--连接数据库的用户名 -->
<property name="username" value="root" />
<!--连接数据库的密码 -->
<property name="password" value="root" />
</bean>
<!--2.配置JDBC模板 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--默认必须使用数据源 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--3.定义id为userDao的Bean -->
<bean id="userDao" class="com.ssm.jdbc.UserDaoImpl">
<!--将 jdbcTemplate注入到 userDao实例中 -->
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
<!--4.事务管理器,依赖于数据源 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--5.注册事务管理器驱动 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
注意1
如果使用了注解式开发,就需要在配置文件中开启注解处理器,指定扫描哪些包下的注解。这里没有开启注解处理器是因为在配置文件中已经配置了UserDaoImpl类的Bean,而@Transactional注解就配置在该Bean类中,所以可以直接生效。
- 在UserDaoImpl实现类中的transfer方法上添加注解,添加后
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
public void transfer(String outUser, String inUser, Integer jf) {
//接收积分
this.jdbcTemplate.update("update user set jf=jf+? where username=?",jf,inUser);
//模拟系统运行时的突发性问题
int i=1/0;
//赠送积分
this.jdbcTemplate.update("update user set jf =jf-? where username=?", jf, outUser);
}
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
注意2
在实际开发中,事务的配置信息(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)通常是在Spring的配置文件中完成的,而在业务层类上只需使用@Transactional注解即可,不需要配置@Transactional注解的属性。
3.创建测试方法
@Test
public void annotationTest(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext-annotation.xml");
UserDao userDao=(UserDao)applicationContext.getBean("userDao");
userDao.transfer("zhangsan","lisi", 200);
System.out.println("赠送积分成功!");
}
执行结果如上