Spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所说的说明,就是指在配置文件中声明,用在Spring配置文 件中声明式的处理事务来代替代码式的处理事务
1、事务管理不侵入开发的组件。具体的说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因此事务管理是属于系统层面的服务,而不是业务逻 辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可。简单说就是系统层面(事务所在的层面)的代码与业务层面的代码分开,解耦
2、在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便
3、注意,Spring声明式事务控制的底层其实就是AOP(a_19_0笔记有学,可前去),也就是业务对象是切点、事务管理是增强,可以通过配置实现切点与增强的织入
声明式事务控制要明确的事项:
1、谁是切点 业务方法是切点,也就是AccountServiceImpl类的transfer方法
2、谁是通知(通知也叫增强) 事务控制是通知,
3、配置切面
具体操作:
第一步: 创建src/main/java/com.huanf目录
第二步: 在com.huanf目录下新建controller目录,里面新建AccountController类,里面写入如下
public class AccountController {
public static void main(String[] args) {
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService accountService = app.getBean(AccountService.class);
//模拟转账,tom转给lucy,转了500元
accountService.transfer("tom","lucy",500);
}
/*先这里运行一下,如果报错,就在pom.xml里面检查一下数据库驱动版本坐标,我是8.几的版本。在applicationContext.xml里面更改成你的数据库用
户名和密码,并且自行在你的数据库导入准备数据,如下
create table account(
name varchar(10) comment '姓名',
money int comment '金额') comment '账户表';
insert into account values ('lucy',5000),('tom',5000);*/
//分析我们要对哪个地方进行事务控制,答案是对业务层进行事务控制,也就是对AccountServiceImpl类的transfer方法进行事务控制
//测试验证: 当控制台出现/ by zero报错,并且双方的钱都没多没少,就事务控制证明成功了
}
第三步: 在com.huanf目录下新建dao目录,里面新建AccountDao接口,写入如下
public interface AccountDao {
public void out(String outMan,double money);
public void in(String inMan,double money);
}
第四步: 在com.huanf.dao目录下新建impl目录,里面新建AccountDaoImpl类,写入如下
public class AccountDaoImpl implements AccountDao {
//引入jdbc模板
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//转出钱的方法。这是一个事务
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money=money-? where name=?",money,outMan);
}
//转入钱的方法。这也是一个事务
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money=money+? where name=?",money,inMan);
}
}
第五步: 在com.huanf目录下新建domain目录,里面新建Account类,写入如下
//实体类,账户类,对应数据库的account表
public class Account {
private String name;
private double money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
第六步: 在com.huanf目录下新建service目录,里面新建AccountService接口,写入如下
public interface AccountService {
public void transfer(String outMan,String inMan,double money);
}
第七步: 在com.huanf.service目录下新建impl目录,里面新建AccountServiceImpl类,写入如下
xxxxxxxxxx
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
//转账的方法,需要提供转出人outMan、转入人inMan、转账金额。我们需要对transfer方法进行事务控制
public void transfer(String outMan, String inMan, double money) {
//开启事务,下面的out和in方法只能同时成功或同时失败
accountDao.out(outMan,money);
//模拟转账中间出现错误
int i = 1/0;//则下一行将不会被执行,也就是钱转出去,但是没人收到,就导致数据的不一致。解决:开启事务通知
accountDao.in(inMan,money);
//提交事务。当上面的转钱和收钱都没报错,我们再提交事务,如果报错就不提交,只要没提交事务,数据库的数据就还是原来的
}
//事务分析: 当上面调用accountDao.out()方法时,其实就是调用AccountDaoImpl类的out方法
//测试验证: 当控制台出现/ by zero报错,并且双方的钱都没多没少,就事务控制证明成功了
}
第八步: 在applicationContext.xml里面写入如下
<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">
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="228675"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="accountDao" class="com.huanf.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>
<!--目标对象,AccountServiceImpl类的transfer方法就是切点-->
<bean id="accountService" class="com.huanf.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"/>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入最上面的bean的DataSource数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知(也叫增强),事务的增强,需要在最上面引入tx命名空间,然后在下面配置事务通知。把上面那个平台事务管理器的bean放到下面那行-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息-->
<tx:attributes>
<!--可指定多个业务方法的属性信息,例如隔离级别、失效时间、是否只读。注意这个方法必须要在切点表达式的路径下,例
如com.huanf.service.impl.AccountServiceImpl类的transfer方法。一个方法就是一个事务,可对多个方法进行不同的事务控制-->
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="trans*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
<!--其中trans*表示以trans开头的方法。上面的属性值灰色表示默认,默认是当前数据库的属性(例如隔离级别),不写的话也是默认-->
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入-->
<aop:config>
<!--advisor是spring专门为事务增强提供的配置。把上面那个配置好的txAdvice增强写到下面那行,并自己写一个切点表达式作为切点,增强和切点就可以织入了-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.huanf.service.impl.*.*(..))"/>
<!--上面那行的切点表达式: 对com.huanf.service.impl包下的所有类的所有方法进行增强,方法可以是任意参数任意返回值-->
</aop:config>
</beans>
第九步: pom.xml里面写入如下
<dependencies>
<!--spring相关的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<!--事务相关的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--测试的坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!--数据源的坐标-->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.1</version>
</dependency>
<!--数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
第十步: 在AccountController类运行测试
上面是快速入门,下面我们会详细分析一下
也就是分析我们在applicationContext.xml里面配置的代码,会把源代码贴出来,然后在它下面进行分析
1、
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入最上面的bean的DataSource数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
其中DataSourceTransactionManager是我们在a_22_0学的编程式事务控制的第一个对象(PlatformTransactionManager平台事务管理器对象),当时就说过如下: PlatformTransactionManager对象是接口类型的,不同的Dao层技术则有不同的实现类,例如如下
2、Dao层技术是jdbc或mybatis时: 实现类是org.springframework.jdbc.datasource.DataSourceTransactionManager
3、Dao层技术是hibernate时: 实现类是org.springframework.orm.hibernate5.HibernateTransactionManager 我们在使用PlatformTransactionManager对象时,由于它是个接口我们不能直接用,得用它的实现类,用哪个实现类取决于是什么Dao层技术。所有由于我们使用的Dao层技术是jdbc,所以选取的实现类是DataSourceTransactionManager
4、
xxxxxxxxxx
<!--配置事务的aop织入-->
<aop:config>
<!--advisor是spring专门为事务增强提供的配置。把上面那个配置好的txAdvice增强写到下面那行,并自己写一个切点表达式作为切点,增强和切点就可以织入了-->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.huanf.service.impl.*.*(..))"/>
<!--上面那行的切点表达式: 对com.huanf.service.impl包下的所有类的所有方法进行增强,方法可以是任意参数任意返回值-->
</aop:config>
其中pointcut代表切点,里面我们要写切点表达式,由于我们是对事务增强,所以一般切点就是业务方法,所以切点表达式就写我们的业务方法(service业务层),例 如execution(* com.huanf.service.impl..(..))
advice-ref代表通知引用,它的值就是我们在tx:advice标签里写的id值
5.
xxxxxxxxxx
<!--通知(也叫增强),事务的增强,需要在最上面引入tx命名空间,然后在下面配置事务通知。把上面那个平台事务管理器的bean放到下面那行-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息-->
<tx:attributes>
<!--可指定多个业务方法的属性信息,例如隔离级别、失效时间、是否只读。注意这个方法必须要在上面第2点的切点表达式的路径下,例
如com.huanf.service.impl.AccountServiceImpl类的transfer方法。一个方法就是一个事务,可对多个方法进行不同的事务控制-->
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
<tx:method name="trans*" isolation="REPEATABLE_READ" propagation="REQUIRED" timeout="-1" read-only="false"/>
<!--其中trans*表示以trans开头的方法。上面的属性值灰色表示默认,默认是当前数据库的属性(例如隔离级别),不写的话也是默认-->
</tx:attributes>
</tx:advice>
其中transaction-manager表示引入一个事务管理器,就是上面第1点的事务管理器的