小白学习spring第三天

第16节:SpringAOP机制详解

16.1 AOP 概述
16.1.1 什么是 AOP
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
AOP 动态代理  
动态代理: 反射 
反射体现了java语言的动态性。 
16.1.2 AOP编程思想
OOA OOP OOT OOSM 
AOP 面向切面编程是一种编程思想,是 OOP 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
16.1.3 Spring中AOP的常用术语
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
Pointcut(切入点):所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。 真正被增强了的方法。 transfer方法。 
Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。 transfer方法,事务相关
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。【重点】

Target(目标对象):代理的目标对象: accountService 对象。 
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类 Proxy
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入
Aspect(切面):是切入点和通知(引介)的结合
具体的描述了: 通知应用在具体的某个切点上~ 。 
16.1.4 AOP 的作用及优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
16.2 Spring基于XML的AOP配置
 1、搭建maven工程,沿用上一章节转账的业务场景
 2、准备好通知类TransactionManager
 3、讲解基于XML形式的AOP配置,详细介绍每个标签,每个标签当中属性的作用
 4、测试使用AOP进行事务配置
 5、常用通知类型的总结
 6、详解切入点表达式的语法,常用案例的列举
16.2.1 构建maven工程添加依赖
<properties>
        <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
</dependencies>
16.2.2 沿用转账业务的代码
准备实体类,业务层和持久层代码。我们沿用上一章节中的代码即可。
16.2.3 创建 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: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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">
        
</beans>
16.2.4 配置 Spring 的 IOC
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--连接数据库的必备信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils">
    <!-- 注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--配置queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>

<!--配置accountDao-->
<bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
    <property name="queryRunner" ref="queryRunner"></property>
    <property name="connectionUtils" ref="connectionUtils"></property>
</bean>

<!--配置accountService-->
<bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
</bean>
16.2.5 抽取公共代码制作成通知(advice)
/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    public  void release(){
        try {
            connectionUtils.removeConnection();
            connectionUtils.getThreadConnection().close();//还回连接池中
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
16.2.6 通知类用 bean 标签配置
<!--配置通知:txManager-->
<bean id="txManager" class="com.offcn.utils.TransactionManager">
    <property name="connectionUtils" ref="connectionUtils"></property>
</bean>
16.2.7 使用 aop:config 声明 AOP 配置
aop:config: 
   作用: 开始声明aop配置
<aop:config>
      <!-- 配置的代码都写在此处 -->
</aop:config>
16.2.8 使用 aop:aspect 配置切面
aop:aspect
   作用: 用于配置切面
   属性: 
         id :给切面提供一个唯一标识。
         ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="tdAdvice" ref="txManager">
     <!--配置通知的类型要写在此处-->  
</aop:aspect>
16.2.9 使用 aop:pointcut 配置切入点表达式
aop:pointcut
   作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
   属性: expression:用于定义切入点表达式。
         id:用于给切入点表达式提供一个唯一标识
<aop:pointcut id="point1"
               expression="execution( public void  com.offcn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double))"/>
        
16.2.10 使用 aop:xxx 配置对应的通知类型
aop:before
    作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
    属性:
         method:用于指定通知类中的增强方法名称
		 ponitcut-ref:用于指定切入点的表达式的引用
		 poinitcut:用于指定切入点表达式
    执行时间点:
         切入点方法执行之前执行   
<aop:before method="beginTransaction" pointcut-ref="point1"></aop:before>
aop:after-returning
	作用: 
    	用于配置后置通知
	属性:
   		method:指定通知中方法的名称。
  		pointct:定义切入点表达式
   		pointcut-ref:指定切入点表达式的引用
   	执行时间点:
   	    切入点方法正常执行之后。它和异常通知只能有一个执行
   	    
 <aop:after-returning method="commit" pointcut-ref="point1"/> 	    
aop:after-throwing
	作用:
		用于配置异常通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	执行时间点:
		切入点方法执行产生异常后执行。它和后置通知只能执行一个
 <aop:after-throwing method="rollback" pointcut-ref="point1"/>	
aop:after
	作用:
		用于配置最终通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
	执行时间点:
		无论切入点方法执行时是否有异常,它都会在其后面执行。
 
 <aop:after method="release" pointcut-ref="point1"/>
16.3 切入点表达式说明
16.3.1 切点表达式的语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
    案例: 
 execution(public void com.offcn.service.impl.AccountServiceImpl.transfer(String,String,Double))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*  代表任意
- 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
- 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

例如:

全匹配方式

    public void 
    com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

访问修饰符可以省略

    void com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

返回值可以使用*号,表示任意返回值

    * com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 *

    * *.*.*.*.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

使用..来表示当前包,及其子包

    * com..AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

类名可以使用*号,表示任意类

    * com..*.saveAccount(com.ujiuye.domain.Account)

方法名可以使用*号,表示任意方法

    * com..*.*( com.ujiuye.domain.Account)

参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数

    * com..*.*(*)

参数列表可以使用..表示有无参数均可,有参数可以是任意类型

    * com..*.*(..)

全通配方式:

    * *..*.*(..) 

注意: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。

    execution(* com.ujiuye.service.impl.*.*(..))

16.3.2 环绕通知配置事务管理
TransactionManager类当中添加方法
/**
     * 环绕通知:
     * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
     * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
     * @param pjp
     * @return
     */
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}
 aop:around:
	作用:
		用于配置环绕通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
	说明:
        它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
        注意:通常情况下,环绕通知都是独立使用的

<aop:config>
        <aop:aspect id="tdAdvice" ref="txManager">
            <aop:pointcut id="point1" expression="execution(* com.offcn.service.impl.*.*(..))"/>
            <!-- 配置环绕通知 -->
            <aop:around method="transactionAround" pointcut-ref="point1"/>
        </aop:aspect>
    </aop:config>
16.4 Spring基于注解的AOP配置
  AOP注解方式和XML方式完成的功能是一样的,只是采用了两种开发方式而已。将原有的XML方式使用注解逐一替代。
16.4.1 构建maven工程添加AOP注解的相关依赖
<properties>
        <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
</dependencies> 
16.4.2 在配置文件中导入 context 的名称空间且配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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/context
        http://www.springframework.org/schema/context/spring-context.xsd">

</beans>
16.4.3 资源使用注解配置
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--连接数据库的必备信息-->
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

<!--配置queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
</bean>
16.4.4 在配置文件中指定 spring 要扫描的包
 <!-- 告知 spring,在创建容器时要扫描的包 -->
 <context:component-scan base-package="com.offcn"></context:component-scan>
16.4.5 通知类使用注解配置
/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    
}
16.4.6 在通知类上使用@Aspect 注解声明为切面[重点]
/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
@Aspect //表明当前类是一个切面类
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    
}
16.4.7 在增强的方法上使用注解配置通知
@Before
	作用:
		把当前方法看成是前置通知
	属性:
		value:用于指定切入点表达式,还可以指定切入点表达式的引用。
		
//开启事务
@Before("execution(* com.offcn.service.impl.*.*(..)))")
public  void beginTransaction(){
    try {
        System.out.println("before..........................");
        connectionUtils.getThreadConnection().setAutoCommit(false);
    }catch (Exception e){
        e.printStackTrace();
    }
}
 @AfterReturning
	作用: 
		把当前方法看成是后置通知。
	属性: 
		value:用于指定切入点表达式,还可以指定切入点表达式的引用
		
// 提交事务
@AfterReturning("execution(* com.offcn.service.impl.*.*(..)))")
public  void commit(){
    try {
        connectionUtils.getThreadConnection().commit();
    }catch (Exception e){
        e.printStackTrace();
    }
}
@AfterThrowing
	作用: 
		把当前方法看成是异常通知。
	属性: 
		value:用于指定切入点表达式,还可以指定切入点表达式的引用
		
//回滚事务
@AfterThrowing("execution(* com.offcn.service.impl.*.*(..)))")
public  void rollback(){
    try {
        connectionUtils.getThreadConnection().rollback();
    }catch (Exception e){
        e.printStackTrace();
    }
}
@After
	作用: 
		把当前方法看成是最终通知。
	属性: 
		value:用于指定切入点表达式,还可以指定切入点表达式的引用

//释放连接
@After("execution(* com.offcn.service.impl.*.*(..)))")
public  void release(){
    try {
        connectionUtils.removeConnection();
        connectionUtils.getThreadConnection().close();//还回连接池中
    }catch (Exception e){
        e.printStackTrace();
    }
}
16.4.8 在 spring 配置文件中开启 spring 对注解 AOP 的支持
<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>
16.4.9 环绕通知注解配置
@Around
	作用: 
		把当前方法看成是环绕通知。
	属性: 
		value:用于指定切入点表达式,还可以指定切入点表达式的引用。
// 环绕通知:
@Around("execution(* com.offcn.service.impl.*.*(..)))")
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}
16.4.10 切入点表达式注解
@Pointcut("execution(* com.offcn.service.impl.*.*(..))")
private void point1() {}

// 环绕通知:
@Around("point1()")///注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}

总结: 
(1)applicationContext.xml当中配置的对象, 开启了包扫描器, 注解模式创建。 
(2TransactionManager : 事务管理器的类: @Aspect注解。 注解在核心配置文件当中。开启一个开关~ 

16.5 Spring事务详解【掌握】
 1、介绍Spring当中进行事务控制的常用对象以及作用
 2、事务的隔离级别,不同隔离级别产生的错误数据
 3、扩展事务的传播行为,事务传播行为的对应几种情况,面试问题
 4、完成基于XML形式事务配置
16.5.1 Spring中事务的API详解
1. PlatformTransactionManager作用(掌握)
PlatformTransactionManager 接口是 Spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

在这里插入图片描述

注意:

PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis,JdbcTemplate 时:org.springframework.jdbc.datasource.DataSourceTransactionManager .

Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager


等价于:  TransactionManager 常用的对象: .DataSourceTransactionManager

2. TransactionDefinition作用

TransactionDefinition 是事务的定义信息对象,里面有如下方法

在这里插入图片描述

2.1 事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

事务隔离级别说明
ISOLATION_DEFAULT默认级别,归属下列某一种
ISOLATION_READ_UNCOMMITTED未提交读,可以读取未提交数据
ISOLATION_READ_COMMITTED已提交读,只能读取已提交数据,解决脏读问题(Oracle默认级别)
ISOLATION_REPEATABLE_READ可重复读,解决不可重复度问题(MySQL默认级别)
ISOLATION_SERIALIZABLE串行化,节约幻读(虚读)问题
2.2 事务传播行为
事务传播行为说明
REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER以非事务方式运行,如果当前存在事务,抛出异常
NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作
2.3 事务超时时间
	默认值是-1,没有超时限制。如果有,以秒为单位进行设置。
2.4 是否是只读事务

		建议查询时设置为只读。 

		事务只读: 查询操作~  

		写事务:   数据库的写操作。 
4. TransactionStatus作用
	TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

在这里插入图片描述

5. 三个对象之间的关系:
	PlatformTransactionManager 会根据 TransactionDefinition 对象当中定义的信息,进行事务的管理操作。 
	TransactionStatus 记录了事务的状态信息。 
掌握: PlatformTransactionManager 
16.5.2 Spring基于XML的事务配置
讲解思路: 
 1、准备转账的业务场景,演示没有事务控制时,数据一致性受损
 2、在XML配置文件当中,使用Spring提供的声明式事务进行控制,详解事务控制的每个步骤,每个步骤当中涉及的每个标签,讲解的过程当中复习巩固AOP当中的相关概念,加深学员对概念的理解
 3、测试基于XML声明式事务是否生效
(1)添加相关技术依赖
<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
</dependencies>
(2)创建配置文件并导入约束
<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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        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
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

     <!-- 告知 Spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.offcn"></context:component-scan>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
(3)沿用转账业务的代码
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findByName(String name) {
        String sql ="select * from account where name =? ";
        Account account =  this.jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), name);
        return  account;
    }

    @Override
    public void update(Account account) {
        String sql ="update account set money =? where name =? ";
        this.jdbcTemplate.update(sql, account.getMoney(), account.getName());
    }
}
(4)配置事务管理器
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
(5)配置事务的通知引用事务管理器
<!--事务的配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">

</tx:advice>
(6)配置事务的属性
<!-- 
    指定方法名称:是业务核心方法
     read-only:是否是只读事务。默认 false,不只读。
     isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
     propagation:指定事务的传播行为。
     timeout:指定超时时间。默认值为:-1。永不超时。
     rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
     没有默认值,任何异常都回滚。
     no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
 -->
<tx:attributes>
    <tx:method name="*"/>
</tx:attributes>
(7)配置 AOP 切入点表达式
<!--事务的aop增强-->
<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* com.offcn.service.impl.*.*(..))"/>
</aop:config>
(8) 配置切入点表达式和事务通知的对应关系
<!--在aop:config标签内部:建立事务的通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>

第17节:Spring事务详解-注解

17.1 基于注解的事务配置
17.1.1 添加相关技术依赖
<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
17.1.2 创建配置文件导入约束
<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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        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
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 告知 spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.offcn"></context:component-scan>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
17.1.3 沿用转账业务的代码
17.2 事务管理配置步骤(重点)
17.2.1 配置事务管理器并注入数据源
<!-- 配置事务管理器 --> 
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
<property name="dataSource" ref="dataSource"></property>
</bean>

17.2.2 在业务层使用注解
/**
 该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
 出现接口上,表示该接口的所有实现类都有事务支持。
 出现在类上,表示类中所有方法有事务支持
 出现在方法上,表示方法有事务支持。
 以上三个位置的优先级:方法>类>接口 
 */
@Service("accountService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
    //依赖dao层
    @Autowired
    private AccountDao accountDao ;

    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    @Override
    public void transfer(String sourceAccountName, String targetAccountName, Double money) {
            Account sAccount = accountDao.findByName(sourceAccountName);
            Account tAccount = accountDao.findByName(targetAccountName);

            //来源账户减钱,目标账户加钱
            sAccount.setMoney(sAccount.getMoney()-money);
            tAccount.setMoney(tAccount.getMoney()+money);

            //持久化到数据库
            accountDao.update(sAccount);
            //模拟异常发生
            //int i=1/0;
            accountDao.update(tAccount);
    }
}
17.2.3 在配置文件中开启注解事务
<!-- 开启 spring 对注解事务的支持 --> 
<tx:annotation-driven transaction-manager="transactionManager"/>

17.3 Spring事务源码分析(了解)
17.3.1 DataSourceUtils源码分析
		看名字就能知道这个类是对DataSource的一个封装,这个类提供了一系列操作数据库连接的工具方法。这个类在Spring事务中非常重要,最主要的作用就是提供了能够从当前线程获取开启事务时绑定的连接。其中Spring Jdbc里的JdbcTemplate类就是采用DataSourceUtils.getConnection()方法获取连接的。
public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
        try {
            return doGetConnection(dataSource);
        } catch (SQLException var2) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection", var2);
        } catch (IllegalStateException var3) {
            throw new CannotGetJdbcConnectionException("Failed to obtain JDBC Connection: " + var3.getMessage());
        }
    }

    public static Connection doGetConnection(DataSource dataSource) throws SQLException {
        Assert.notNull(dataSource, "No DataSource specified");
        ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
        if (conHolder == null || !conHolder.hasConnection() && !conHolder.isSynchronizedWithTransaction()) {
            logger.debug("Fetching JDBC Connection from DataSource");
            Connection con = fetchConnection(dataSource);
            if (TransactionSynchronizationManager.isSynchronizationActive()) {
                try {
                    ConnectionHolder holderToUse = conHolder;
                    if (conHolder == null) {
                        holderToUse = new ConnectionHolder(con);
                    } else {
                        conHolder.setConnection(con);
                    }

                    holderToUse.requested();
                    TransactionSynchronizationManager.registerSynchronization(new DataSourceUtils.ConnectionSynchronization(holderToUse, dataSource));
                    holderToUse.setSynchronizedWithTransaction(true);
                    if (holderToUse != conHolder) {
                        TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
                    }
                } catch (RuntimeException var4) {
                    releaseConnection(con, dataSource);
                    throw var4;
                }
            }

            return con;
        } else {
            conHolder.requested();
            if (!conHolder.hasConnection()) {
                logger.debug("Fetching resumed JDBC Connection from DataSource");
                conHolder.setConnection(fetchConnection(dataSource));
            }

            return conHolder.getConnection();
        }
    }
17.3.2 TransactionSynchronizationManager源码分析
	这个类的作用就是绑定资源到当前线程、注册TransactionSynchronization接口、绑定事务的各个属性。
public abstract class TransactionSynchronizationManager {
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");

    public static void initSynchronization() throws IllegalStateException {
        if (isSynchronizationActive()) {
            throw new IllegalStateException("Cannot activate transaction synchronization - already active");
        } else {
            logger.trace("Initializing transaction synchronization");
            synchronizations.set(new LinkedHashSet());
        }
    }
}

第18节:Spring整合Mybatis(重点)

18.1 整合思路分析
		Mybatis框架是一个持久层ORM框架,而Spring则是一个综合性一站式框架。所以整合是MybatisSpring上整合。就是让Spring框架接管Mybatis的组件。

		Mybatis单独运行时,数据源的管理,事务的管理, SqlSessionFactory 以及接口的实现类都是Mybatis管理的,整合后以上组件交给Spring管理。

18.2 添加技术依赖
<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>

    <!--mybatis-Spring适配包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.0</version>
    </dependency>
    <!-- mybatis orm框架 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
</dependencies>
18.3 构建数据库表并创建实体User
create table User(
 id int primary key auto_increment,
 name varchar(32) not null,
 address varchar(32) not null,
 birthday date 
);
public class User implements Serializable {
    private Integer id;
    private String name;
    private String address;
    private Date birthday;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", birthday=" + birthday +
                '}';
    }
}
18.4 编写dao层的接口UserMapper
public interface UserMapper {
    int insert(User user);
    int update(User user);
    int delete(Integer id);
    User findById(Integer id);
    List<User> findAll();
}
18.5 构建mapper接口对应的sql配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.offcn.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.offcn.pojo.User">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="address" jdbcType="VARCHAR" property="address" />
        <result column="birthday" jdbcType="DATE" property="birthday" />
    </resultMap>

    <insert id="insert" parameterType="com.offcn.pojo.User">
        insert into user (name, birthday, address)
        values (#{name}, #{birthday},#{address})
    </insert>

    <update id="update">
        update user
        set
        name= #{name},
        birthday=#{birthday},
        address = #{address}
        where id=#{id}
    </update>

    <delete id="delete">
        delete from user
        where id =#{id}
    </delete>

    <select id="findById" resultMap="BaseResultMap">
        select * from user where id=#{id}
    </select>

    <select id="findAll" resultMap="BaseResultMap">
        select * from user
    </select>
</mapper>
18.6 构建服务层接口UserService
public interface UserService {
    int insert(User user);
    int update(User user);
    int delete(Integer id);
    User findById(Integer id);
    List<User> findAll();
}
18.7 构建服务层实现类UserServiceImpl
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public int insert(User user) {
        int num = userMapper.insert(user);
        return  num;
    }

    @Override
    public int update(User user) {
        int num = userMapper.update(user);
        return num;
    }

    @Override
    public int delete(Integer id) {
        int num = userMapper.delete(id);
        return num;
    }

    @Override
    public User findById(Integer id) {
        User user = userMapper.findById(id);
        return user;
    }

    @Override
    public List<User> findAll() {
        List<User> userList = userMapper.findAll();
        return userList;
    }
}
18.8 构建Spring框架的配置文件配置IOC管理的对象【重点】
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!--开启包扫描-->
    <context:component-scan base-package="com.offcn"> </context:component-scan>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--Mybatis 核心对象: 工厂对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--工厂创建必须注入一个数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--指定mapper文件位置-->
        <property name="mapperLocations" value="classpath:com/offcn/mapper/*Mapper.xml"></property>
        <!--引入Mybatis的核心配置文件:
           如果Mybaits的核心配置要保留,需要再此处配置:
        -->
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>

        <!--别名配置-->
        <property name="typeAliasesPackage" value="com/offcn/pojo"/>
        <!--进行分页插件的配置-->
        <property name="plugins">
			<array>
				<bean class="com.github.pagehelper.PageInterceptor">
					<property name="properties">
						<value>
							helperDialect=MySQL
							reasonable=true
							supportMethodsArguments=true
							params=count=countSql
							autoRuntimeDialect=true
						</value>
					</property>
				</bean>
			</array>
		</property>   
    </bean>

    <!--配置接口的扫描-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定了包: 能够将包下的接口生成实现类: -->
        <property name="basePackage" value="com.offcn.mapper"></property>
    </bean>

    <!--配置平台管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
18.9 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestAccountTransfer {

    @Autowired
    private UserService userService;

    //save
    @Test
    public void testInsert(){
        User user = new User();
        user.setName("admin");
        user.setAddress("china");
        user.setBirthday(new Date());
        int num = userService.insert(user);
        System.out.println("num:"+num);
    }
    //update
    @Test
    public void testUpdate(){
        User user = new User();
        user.setName("marry");
        user.setAddress("America");
        user.setBirthday(new Date());
        user.setId(1);
        int num = userService.update(user);
        System.out.println("num:"+num);
    }

    //delete:
    @Test
    public void testDelete(){
        int num = userService.delete(1);
        System.out.println("num:"+num);
    }

    //findById
    @Test
    public void testFindById(){
        User user = userService.findById(2);
        System.out.println("user:"+user);
    }
    //findAll
    @Test
    public void testFindByAll(){
        List<User> userList = userService.findAll();
        System.out.println("userList:"+userList);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值