第5章Spring 事务(测试)-Spring的事务注解(小项目中),AspectJ的AOP配置管理事务(大项目中)
spring框架中提供的事务处理方案
适合中小项目使用的, 注解方案。
1.适合中小项目使用的, 注解方案。
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等
使用@Transactional的步骤:
1.需要声明事务管理器对象
<bean id="xx" class="DataSourceTransactionManager">
1. 使用 Spring 的事务注解管理事务(掌握)
通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。
当然,若只有一个异常类时,可以不使用数组。
需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public
方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该
方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
-
实现注解的事务步骤:
-
声明事务管理器
<!--使用spring的事务处理--> <!--1. 声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--连接的数据库, 指定数据源--> <property name="dataSource" ref="myDataSource" /> </bean>
-
开启注解驱动
<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象 transaction-manager:事务管理器对象的id --> <tx:annotation-driven transaction-manager="transactionManager" />
-
业务层 public 方法加入事务属性
@Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NullPointerException.class, NotEnoughException.class }
-
-
此时的spring配置文件:
<?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:context="http://www.springframework.org/schema/context" 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/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容 spring知道jdbc.properties文件的位置 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!--声明数据源DataSource, 作用是连接数据库的--> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--set注入给DruidDataSource提供连接数据库信息 --> <!-- 使用属性配置文件中的数据,语法 ${key} --> <property name="url" value="${jdbc.url}" /><!--setUrl()--> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.passwd}" /> <property name="maxActive" value="${jdbc.max}" /> </bean> <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的 SqlSessionFactory sqlSessionFactory = new .. --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--set注入,把数据库连接池付给了dataSource属性--> <property name="dataSource" ref="myDataSource" /> <!--mybatis主配置文件的位置 configLocation属性是Resource类型,读取配置文件 它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置 --> <property name="configLocation" value="classpath:mybatis.xml" /> </bean> <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class) MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定SqlSessionFactory对象的id--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!--指定包名, 包名是dao接口所在的包名。 MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行 一次getMapper()方法,得到每个接口的dao对象。 创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写 --> <property name="basePackage" value="com.bjpowernode.dao"/> </bean> <!--声明service--> <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl"> <property name="goodsDao" ref="goodsDao" /> <property name="saleDao" ref="saleDao" /> </bean> <!--使用spring的事务处理--> <!--1. 声明事务管理器--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--连接的数据库, 指定数据源--> <property name="dataSource" ref="myDataSource" /> </bean> <!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象 transaction-manager:事务管理器对象的id --> <tx:annotation-driven transaction-manager="transactionManager" /> </beans>
-
com.bjpowernode.service.impl.BuyGoodsServiceImpl
package com.bjpowernode.service.impl; import com.bjpowernode.dao.GoodsDao; import com.bjpowernode.dao.SaleDao; import com.bjpowernode.domain.Goods; import com.bjpowernode.domain.Sale; import com.bjpowernode.excep.NotEnoughException; import com.bjpowernode.service.BuyGoodsService; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; public class BuyGoodsServiceImpl implements BuyGoodsService { private SaleDao saleDao; private GoodsDao goodsDao; /** * * rollbackFor:表示发生指定的异常一定回滚. * 处理逻辑是: * 1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中 * 如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。 * 2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException, * 如果是一定回滚。 * */ /* @Transactional( propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, readOnly = false, rollbackFor = { NullPointerException.class, NotEnoughException.class } )*/ //使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT //默认抛出运行时异常,回滚事务。 @Transactional(rollbackFor=Exception.class) @Override public void buy(Integer goodsId, Integer nums) { System.out.println("=====buy方法的开始===="); //记录销售信息,向sale表添加记录 Sale sale = new Sale(); sale.setGid(goodsId); sale.setNums(nums); saleDao.insertSale(sale); //更新库存 Goods goods = goodsDao.selectGoods(goodsId); if( goods == null){ //商品不存在 throw new NullPointerException("编号是:"+goodsId+",商品不存在"); } else if( goods.getAmount() < nums){ //商品库存不足 throw new NotEnoughException("编号是:"+goodsId+",商品库存不足"); } //修改库存了 Goods buyGoods = new Goods(); buyGoods.setId( goodsId); buyGoods.setAmount(nums); goodsDao.updateGoods(buyGoods); System.out.println("=====buy方法的完成===="); } public void setSaleDao(SaleDao saleDao) { this.saleDao = saleDao; } public void setGoodsDao(GoodsDao goodsDao) { this.goodsDao = goodsDao; } }
-
测试入下:
-
正常查询:
-
查询一个不存在的一条数据
-
购买的数量超过库中的数据
-
此时我们在查询一条正常的
-
2.使用 AspectJ 的 AOP 配置管理事务(掌握)
适合大型项目,有很多的类,方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式业务方法和事务配置完全分离。
-
Step1:maven 依赖 pom.xml
新加入 aspectj 的依赖坐标 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.2.5.RELEASE</version> </dependency>
-
Step:在容器中添加事务管理器
<!--声明式事务处理:和源代码完全分离的--> <!--1.声明事务管理器对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource" /> </bean>
-
修改spring配置文件(重点)
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容 spring知道jdbc.properties文件的位置 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!--声明数据源DataSource, 作用是连接数据库的--> <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <!--set注入给DruidDataSource提供连接数据库信息 --> <!-- 使用属性配置文件中的数据,语法 ${key} --> <property name="url" value="${jdbc.url}" /><!--setUrl()--> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.passwd}" /> <property name="maxActive" value="${jdbc.max}" /> </bean> <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的 SqlSessionFactory sqlSessionFactory = new .. --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--set注入,把数据库连接池付给了dataSource属性--> <property name="dataSource" ref="myDataSource" /> <!--mybatis主配置文件的位置 configLocation属性是Resource类型,读取配置文件 它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置 --> <property name="configLocation" value="classpath:mybatis.xml" /> </bean> <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class) MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定SqlSessionFactory对象的id--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> <!--指定包名, 包名是dao接口所在的包名。 MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行 一次getMapper()方法,得到每个接口的dao对象。 创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写 --> <property name="basePackage" value="com.bjpowernode.dao"/> </bean> <!--声明service--> <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl"> <property name="goodsDao" ref="goodsDao" /> <property name="saleDao" ref="saleDao" /> </bean> <!--声明式事务处理:和源代码完全分离的--> <!--1.声明事务管理器对象--> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="myDataSource" /> </bean> <!--2.声明业务方法它的事务属性(隔离级别,传播行为,超时时间) id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的 transaction-manager:事务管理器对象的id --> <tx:advice id="myAdvice" transaction-manager="transactionManager"> <!--tx:attributes:配置事务属性--> <tx:attributes> <!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性 name:方法名称,1)完整的方法名称,不带有包和类。 2)方法可以使用通配符,* 表示任意字符 propagation:传播行为,枚举值 isolation:隔离级别 rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚 --> <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/> <!--使用通配符,指定很多的方法--> <tx:method name="add*" propagation="REQUIRES_NEW" /> <!--指定修改方法--> <tx:method name="modify*" /> <!--删除方法--> <tx:method name="remove*" /> <!--查询方法,query,search,find--> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <!--配置aop--> <aop:config> <!--配置切入点表达式:指定哪些包中类,要使用事务 id:切入点表达式的名称,唯一值 expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象 com.bjpowernode.service com.crm.service com.service --> <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/> <!--配置增强器:关联adivce和pointcut advice-ref:通知,上面tx:advice哪里的配置 pointcut-ref:切入点表达式的id --> <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" /> </aop:config> </beans>
-
此时达到的效果和上面spring的注解完成的效果是一样的,这里就不测试了。