使用
AspectJ
的
AOP
配置管理事务
使用
XML
配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类
较多,配置文件会变得非常臃肿。
使用
XML
配置顾问方式可以自动为每个符合切入点表达式的类生成事务代理。其用法
很简单,只需将前面代码中关于事务代理的配置删除,再替换为如下内容即可。
在第一个实例上进行修改,详细见第一个实例
项目结构
Step1
:
maven
依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>spring3</artifactId>
<groupId>com.it</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springTransactionAspectJ</artifactId>
<name>springTransactionAspectJ</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--springIOC-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- spring事务-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- jdbc依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!-- mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- mybatis和spring集成的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!-- 阿里公司的数据库连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>
<build>
<!-- 设置编译compile生成traget文件时可以把后缀为xml的文件一起加入进去-->
<!-- 现在是把src/main/java目录中的xml文件包含到输出结果中,输出到target/classes目录中-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<!-- 指定jdk的版本 -->
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
新加入
aspectj
的依赖坐标
<
dependency
>
<
groupId
>
org.springframework
</
groupId
>
<
artifactId
>
spring-aspects
</
artifactId
>
<
version
>
5.2.5.RELEASE
</
version
>
</
dependency
>
以下3个步骤均是在applicationContext.xml配置文件中添加代码
Step2
:在容器中添加事务管理器
Step3:配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到
buy
方法上的事务要求是必须的,且当
buy
方法发生异常后要回滚业务。
Step4:配置增强器
指定将配置好的事务通知,织入给谁。
applicationContext.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: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}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="maxActive" value="${jdbc.max}"/>
</bean>
<!-- 声明mybatis中提供的SqlSessionFactoryBean类-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--set注入,把德鲁伊数据库连接池赋给dataSource属性-->
<property name="dataSource" ref="myDataSource"/>
<!-- mybais主配置文件的位置-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!-- 创建dao对象,使用SqlSession的getMapper(Student.class)
MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 指定sqlSessionFactory对象的id-->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 指定包名,包名是dao接口所在的包名。
MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次,getMapper()方法,
得到每个接口的dao对象,创建好的dao对象是放入到spring的容器中的。
-->
<property name="basePackage" value="com.it.dao"/>
</bean>
<!-- 声明service-->
<bean id="buyService" class="com.it.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:自定义名称,transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<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.it.exception.NotEnoughException"/>
<!-- 使用通配符,指定很多方法 -->
<!-- <tx:method name="add*" propagation="REQUIRES_NEW" />-->
</tx:attributes>
</tx:advice>
<!--3.配置aop-->
<aop:config>
<!-- 配置切入点表达式:指定哪些包中的哪些类要,应用事务
id:切入点表达式,唯一值
expression:切入点表达式 ,指定哪些类要使用事务,aspectj会创建代理对象
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!-- 配置增强器:关联advice和pointcut
advice-ref:通知,上面的tx:advice那里的配置
pointcut-ref:切入点表达式的id,
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
</beans>
Step6:测试类
package com.it;
import com.it.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Unit test for simple App.
*/
public class AppTest
{
// 测试正常购买
@Test
public void test1(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
// 从容器获取service
BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");
System.out.println("测试获取的service对象是否是代理对象:"+buyService.getClass().getName());
// 调用方法
buyService.buy(1001,10);
}
// 测试购买不存在的商品
@Test
public void test2(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");
buyService.buy(1003,10);
}
// 测试购买超出库存的商品
@Test
public void test3(){
String config="applicationContext.xml";
ApplicationContext ac=new ClassPathXmlApplicationContext(config);
BuyGoodsService buyService = (BuyGoodsService) ac.getBean("buyService");
buyService.buy(1002,100);
}
}
数据库初始内容
使用AspectJ的Aop管理事务
测试正常购买
测试购买不存在的商品
测试购买超出库存的商品
再次正常购买,查看sale表中的id是否间隔两个数