Spring框架(AOP、JDBCTemplate、事务控制)

纵饮孤独 提交于 2019-12-14 05:52:26

银行转账案例分析

在更新转入账户和转出账户时,中间加上这句:
在这里插入图片描述
造成:虽然报错,但是数据库更新了部分数据
再来看看QueryRunner的配置:

 <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

可以看到,每一次与数据库进行交互,都会产生一个Runner.
一个函数里面所有的操作,都应该由一个Connection来操作:
在这里插入图片描述
解决方法:让业务层实现业务的回滚
定义Utils.ConnectionUtils.java,连接的工具类,用于从数据源中获取一个链接,并且实现和线程的绑定:

public Connection getThreadConnection(){
        //1、先从ThreadLocal上获取
        Connection conn = tl.get();
        //2、判断当前线程上是否有链接
        if(conn == null){
            //从数据源中获取一个链接,和线程绑定,存入ThreadLocal中
            conn = datasource.getConnection();
            tl.set(conn);
        }
        //4、返回当前线程上的链接
        return conn;
    }

Utils.TransactionManager.java:
和事务管理相关的工具类,它包含了开启事务、提交事务、回滚事务和释放链接

public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public void beginTransaction(){
        connectionUtils.getThreadConnection().setAutoCommit();
    }
    /**
     * 提交事务
     */
    public void commit(){
        connectionUtils.getThreadConnection().commit();
    }
    /**
     * 回滚事务
     */
    public void rollback(){
        connectionUtils.getThreadConnection().rollback();
    }
    /**
     * 释放链接
     */
    public void release(){
        connectionUtils.getThreadConnection().close();
    }
}

在Service层里面每个方法都要存在事务处理:

void transfer(String sourcename,String targetname,Float money){
        try{
            //1、开启事务
            txmanager.beginTransaction();
            //2、执行操作
            //3、提交事务
            txmanager.commit();
            //4、返回结果
        }catch(Exception e){
            //5、回滚操作
            txmanager.rollback();
        }finally{
            //6、释放链接
            txmanager.release();
        }
}

结果:对事务的控制从持久层回到业务 ,但是配置和注入变得很复杂

AOP 面向切面编程

假设有A,B,C三个方法,但是在调用每一个方法之前,要求打印一个日志:某一个方法被开始调用了!在调用每个方法之后,也要求打印日志:某个方法被调用完了
–>把打印日志这个功能封装一下,然后让它能在指定的地方(比如执行方法前,或者执行方法后)自动的去调用
这样的话,业务代码中就不会掺杂一些其他的代码,比如,日志输出,事务控制,异常的处理等。AOP就是做了这一类的工作

在这里插入图片描述
专业术语
连接点:业务层中的所有方法
切入点:被增强的方法
通知:定义了切面要完成什么工作、何时执行的问题
在这里插入图片描述
target:目标对象,被代理对象
Weaving 织入:增强被代理对象的过程
proxy:代理对象

基于XML的AOP
在XML里面加入如下配置(解析切入点表达式所用):
aspectj:解析切入点表达式

<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
</dependency>

在bean.xml中导入关于AOP的约束(头部):

<?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">

spring中基于XML的AOP配置步骤
1、把通知Bean也交给spring来管理:

<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>

2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

<!--配置AOP-->
    <aop:config>
        <!--配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>
        </aop:aspect>
    </aop:config>

切入点表达式的写法
关键字:execution(表达式)
表达式
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准的表达式写法
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略:
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值:
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*

* *.*.*.*.AccountServiceImpl.saveAccount())

包名可以使用…表示当前包及其子包:

* *..AccountServiceImpl.saveAccount()

类名和方法名都可以使用*来实现通配:

* *..*.*()

参数列表:
可以直接写数据类型:
基本类型直接写类型名 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符* *表示任意类型,但是必须有参数
可以使用…表示有无参数均可,有参数可以是任意类型
全通配写法:

 * *..*.*(..)

实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法

* com.itheima.service.impl.*.*(..)

四种常用的通知类型
通知的五种类型:
前置通知 Before:目标方法被调用之前调用通知功能
后置通知 After:目标方法完成之后调用通知功能
返回通知 After-returning:目标方法成功执行之后调用通知
异常通知 After-throwing :目标方法抛出异常后调用通知
环绕通知 Around:在被通知的方法调用之前和之后执行行为

环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object aroundPringLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需的参数

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

            rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
        }
    }

配置切入点表达式
在切面内定义:

<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>

通知标签里面,将pointcut属性改成pointcut-ref标签,传入配置切入点表达式的id属性。

基于注解的AOP配置
配置文件中,配置spring开启注解AOP的支持 :

<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

在切面类中:
类定义之前加上:

@Component("logger")
@Aspect//表示当前类是一个切面类

类里面配置切入点表达式:

@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){}

每个方法前面加上注解:

@Before("pt1()")
@AfterReturning("pt1()")
@AfterThrowing("pt1()")
@After("pt1()")
@Around("pt1()")

JDBCTemplate

作用:和数据库进行交互,实现对表的CRUD操作

pom.xml中的配置:

<dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-tx</artifactId>
       <version>5.0.2.RELEASE</version>
</dependency>

<dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.6</version>
</dependency>  

spring的内置数据源 DriverManagerDataSource

DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/eesy");
        ds.setUsername("root");
        ds.setPassword("1234");
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate();
//给jt设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(name,money)values('ccc',1000)");

创建对象中,构造函数的参数可以选择传入数据源,在构造JdbcTemplate时,构造函数内部调用SetDataSource方法,这样就不用自己再去调用SetDataSource方法了。

Spring中的事务控制

spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
</bean>

2、配置事务的通知

<tx:advice id="txAdvice" transaction-manager="transactionManager">

此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知

属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用

3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系

    <!-- 配置aop-->
    <aop:config>
        <!-- 配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

5、配置事务的属性
是在事务的通知tx:advice标签的内部

isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。

<tx:attributes>
             //name为方法名
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>

基于注解的事务控制

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!