java:springAOP

108 阅读16分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第11天,点击查看活动详情

Spring AOP

今天我们来聊一下 springAOP。Spring AOP 其实就是我们以前学习过的 代理模式的体现。

代理模式

代理模式是 SpringAOP 的底层!面试必问【SpringAOP,SpringBoot】

代理模式分类:

  • 静态代理
  • 动态代理

静态代理

角色分析:

  • 抽象角色:一般会使用接口或抽象类来解决
  • 真实角色:被代理的角色
  • 代理角色:代理真实角色,代理真实角色后,我们一般会有一些附属操作。
  • 客户:访问代理对象的人。

以前的方式就是房东想要出租,找到客户。

image-20220404180846501

现在的方式就是 房东委托中介 中介不光出租还有收钱和带人看房的功能。客户只用和中介接触,房东只用和中介接触

image-20220404181018148

代码步骤:

  1. 接口

    package cn.hyz.demo01;
    
    /**
     * @author workplace
     * @date 2022/4/4 17:52
     */
    public interface Rent {
        void rent();
    }
    
  2. 真实角色

    package cn.hyz.demo01;
    
    /**
     * @author workplace
     * @date 2022/4/4 17:53
     */
    public class Host implements Rent {
        private String name;
    
        public Host() {
        }
    
        public Host(String name) {
            this.name = name;
        }
    
        @Override
        public void rent() {
            System.out.println(name + "要出租的房子");
        }
    }
    
  3. 代理角色

    package cn.hyz.demo01;
    
    /**
     * @author workplace
     * @date 2022/4/4 17:55
     */
    public class Proxy implements Rent {
    
        private Host host;
    
        public Proxy(Host host) {
            this.host = host;
        }
    
        public Proxy() {
        }
    
        @Override
        public void rent() {
            host.rent();
        }
    }
    
  4. 客户端访问代理角色

    package cn.hyz.demo01;
    
    /**
     * @author workplace
     * @date 2022/4/4 17:54
     */
    public class Client {
        public static void main(String[] args) {
            Proxy proxy = new Proxy(new Host("张三"));
            proxy.rent();
        }
    }
    

代理模式的好处:

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共业务就交给代理角色!实现了业务的分工
  • 公共业务发生拓展的时候,方便集中管理(只需要管理代理就可以了)

缺点

  • 一个真实角色就会创建一个代理角色;代码量翻倍,开发效率变低。

个人看法:什么是AOP?

  • 原来修改功能的方法是直接在功能代码上进行修改。这样子就会大量工作且有可能出现错误。这样子的修改方式在未来工作中必然是不现实的。把公司代码改崩了可能会受到法律的制裁。

    image-20220404203924705

    当我们使用了代理模式的时候,既可以实现不改变原代码的情况下直接进行修改。

    image-20220404204122834

  • 代码展示

    • 接口

      public interface Service {
      
          /**
           * function 功能是我们原有的功能,我们想要对它进行修改
           */
          void function();
      }
      
    • 真实角色,即原代码,也是本案例中的 serviceImpl

      public class ServiceImpl implements Service {
      
          /**
           * 这里就是功能的实现
           * 我们想要修改成在输出之前先输出 "ffffffffffffffffff" 这个字符串
           */
          @Override
          public void function() {
              System.out.println("Function");
          }
      }
      
      1. 原有修改方法

        public class ServiceImpl implements Service {
        
            /**
             * 这里就是功能的实现
             * 我们想要修改成在输出之前先输出 "ffffffffffffffffff" 这个字符串
             */
            @Override
            public void function() {
                System.out.println("ffffffffffffffffff");
                System.out.println("Function");
            }
        }
        

        我们会直接在 原代码 上进行修改,容易出错;如果功能繁杂修改量将大幅提升。

      2. 通过代理方式进行修改

        /**
         * 继承 Service 是为了了解 ServiceImpl 有什么功能
         * 这样子就能对 ServiceImpl 的功能有所了解和修改
         */
        public class Proxy implements Service {
            /**
             * 通过获取 ServiceImpl 的对象,来修改指定对象的功能
             */
            private ServiceImpl service;
        
            public void setService(ServiceImpl service) {
                this.service = service;
            }
        
            /**
             * 这样子修改就安全许多。如果修改有问题只需要删除此类就可以恢复修改前的状态。
             * 不会影响原代码运行效果
             */
            @Override
            public void function() {
                System.out.println("ffffffffffffffffff");
                service.function();
            }
        }
        

        我们会创建多一层代理类,将原有的功能导入再进行修改。这样子就不会出现影响原代码的问题了。本来调用 serviceImpl 的类都会改成去调用 Proxy 类。这样子就算是修改成功了。


动态代理

  • 动态代理和静态代理角色一样。
  • 动态代理类是动态生成的,不是我们直接写好的。
  • 动态代理可以分为两大类:
    • 基于接口的的动态代理。例如:基于 JDK 动态代理
    • 基于类的动态代理。例如:cglib
    • 现在又出现了一个比较新的动态代理:ava字节码实现。例如:javasist。

我看的教程:动态代理 - 廖雪峰的官方网站 (liaoxuefeng.com) + [Java 静态代理、Java动态代理、CGLIB动态代理 - 云+社区 - 腾讯云 (tencent.com)](cloud.tencent.com/developer/a… Proxy.newProxyInstance,通过反射机制用来动态生成代理类对象%2C 为接口创建一个代理类,这个代理类实现这个接口。)

学习 动态代理需要了解两个类:Proxy【代理】 和 InvocationHandler【调用处理程序】

  • 什么是动态代理:
    • 动态代理就是想办法根据接口或者目标对象计算出代理类的字节码然后加载进 JVM 中

Java 实现动态代理

  • 接口

    public interface Rent {
        void rent();
    }
    
  • 真实对象

    public class Host implements Rent {
        private String name;
    
        public Host() {
        }
    
        public Host(String name) {
            this.name = name;
        }
    
        @Override
        public void rent() {
            System.out.println(name + "要出租的房子");
        }
    }
    
  • 生成代理角色 重点看这里!这里显示了怎么生成通用代理类

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * @author workplace
     * @date 2022/4/4 21:23
     * 我们会用这个类自动生成代理类
     */
    public class ProxyInvocationHandler implements InvocationHandler {
    
        /**
         * 1. 传入要代理的真实角色
         */
        private Object target;
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        /**
         * 2. 生成得到代理对象/获取一个代理类实例
         * 我们调用 Proxy 类的 newProxyInstance 方法来获取一个代理类实例。
         * 这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。
         * -------------------------------------------------------
         * 通过 Proxy.newProxyInstance
         * 传入 ClassLoader loader【指定代理对象的类加载器】,
         * Class<?>[] interfaces【代理对象需要实现的接口,可以同时指定多个接口】,
         * InvocationHandler h【方法调用的实际处理者,代理对象的方法调用都会转发到这里】
         */
        public Object getProxy() {
            return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    this);
        }
    
        /**
         * 3. 处理代理实例并返回结果
         *
         * @param proxy  代理类对象
         * @param method 标识具体调用的是代理类的哪个方法
         * @param args   代理类方法的参数
         * @return 返回代理方法结果
         * @throws Throwable 返回错误
         */
        @Override
        public Object invoke(Object proxy,
                             Method method,
                             Object[] args) throws Throwable {
            // 动态代理本身就是用到了反射机制实现
            System.println.out("你好程序员");
            return method.invoke(target, args);
        }
    }
    
  • 访问代理角色

    public class Client {
        @Test
        public void test() {
            // 真实角色
            Host host = new Host();
    
            // 代理角色:现在没有
            ProxyInvocationHandler pih = new ProxyInvocationHandler();
    
            // 通过调用程序处理角色来处理我们要调用的接口对象
            pih.setTarget(host);
    
            Rent proxy = (Host) pih.getProxy();
    
            proxy.rent();
    
        }
    }
    

AOP

什么是AOP

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

AOP的常用术语

  • 横切关注点

    • 跨越应用程序多个模块的方法或功能。我们要添加的功能
  • 切面

    • 横切关注点被模块化的对象。就是我们要添加的功能做成一个类
  • 连接点

    • 目标对象中有哪些方法可以被增强。
    • 就是在功能中有多少个方法可以给你修改
  • 切入点

    • 实际被增强的方法
    • 你实际上要修改那个功能
  • 通知(增强)

    • 实际被增强的逻辑部分
    • 你修改了什么
    • 通知分5类
      • 前置通知 @Before
      • 后置通知 @AfterReturning
      • 环绕通知 @Around
      • 异常通知 @AfterThrowing
      • 最终通知 @After
  • 目标

    • 被通知对象
  • 代理

    • 向目标对象应用通知之后创建的对象

image-20220405215826274


在 Spring 中使用 AOP

  • 第一步 :先导入依赖

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

    在 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: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">
    

    image-20220405214701340

有三种方法进行增强

  • 接口

    package cn.hyz.service;
    
    /**
     * @author workplace
     * @date 2022/4/5 18:39
     */
    public interface UserService {
        void add();
        void delete();
        void update();
        void query();
    }
    
  • 真实对象

    package cn.hyz.service;
    
    /**
     * @author workplace
     * @date 2022/4/5 18:40
     */
    public class UserServiceImpl implements UserService {
        @Override
        public void add() {
            System.out.println("增加一个用户");
        }
    
        @Override
        public void delete() {
            System.out.println("删除一个用户");
        }
    
        @Override
        public void update() {
            System.out.println("修改一个用户");
        }
    
        @Override
        public void query() {
            System.out.println("查找一个用户");
        }
    }
    

方式一:使用 Spring 的API 接口【主要 SpringAPI 接口实现】

  • 切面:

    package cn.hyz.log;
    
    import org.springframework.aop.MethodBeforeAdvice;
    
    import java.lang.reflect.Method;
    
    /**
     * @author workplace
     * @date 2022/4/5 18:44
     */
    public class Log implements MethodBeforeAdvice {
        /**
         * @param method 要执行的目标对象的方法
         * @param args   目标对象方法的参数
         * @param target 目标对象
         * @throws Throwable 抛出异常
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println(target.getClass().getName() + "的" + method.getName() + "被执行");
        }
    }
    
  • xml 配置

        <aop:config>
            <!-- 切入点:expression:表达式,execution(要进行修改的位置!)-->
            <aop:pointcut id="pointcut" expression="execution(* cn.hyz.service.*.*(..))"/>
            <!--先要添加的功能和添加功能的位置-->
            <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
         </aop:config>
    
  • 测试类

    public class MyTest {
        @Test
        public void test() {
          ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");
            userService.add();
        }
    }
    
  • 【总结】

    这个方法是通过 Spring 自带的 API 来实现,这个方法的切面的选择通知的类型是通过继承的类来决定的。


方式二:自定义来实现AOP【主要是切面定义】

  • 切面

    public class Diy {
        public void before() {
            System.out.println("=====before=======");
        }
    
        public void after() {
            System.out.println("======after=======");
        }
    }
    
  • xml 配置文件

    <!--方式二:自定义类-->
        <aop:config>
            <!--自定义切面, ref:要引用的类-->
            <aop:aspect ref="diy">
                <!--切入点-->
                <aop:pointcut id="point" expression="execution(* cn.hyz.service.*.*(..))"/>
                <!--通知-->
                <aop:before method="before" pointcut-ref="point"/>
                <aop:after method="after" pointcut-ref="point"/>
             </aop:aspect>
        </aop:config>
    
  • 测试类

    public class MyTest {
        @Test
        public void test() {
          ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            UserService userService = (UserService) context.getBean("userService");
            userService.add();
        }
    }
    
  • 【总结】

    相较于使用 Spring 的 API 接口,自定义实现 AOP 更加的方便和快捷。通过<aop:aspect ref=""> 来选择切入点。可以通过标签选择通知类型和对应的方法。


方式三

package com.atguigu.spring5.aopanno;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

/**
 * @author workplace
 * @date 2022/3/9 19:18
 * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
 */
@Component
@Aspect
public class UserProxy {
    /*
     * 我们通过在增强类里的通知方法上通过标签将切入点表达式注入value属性中指定增强
     *   @标签(value = "切入点表达式")
     *
     * 切入点表达式格式:
     *   exception(权限修饰符 返回类型 类全路径.方法名称(参数列表))
     *   权限修饰符:public,private,protect之类的,常用 * 标识任意权限修饰符
     *   返回类型:short,int,double,float,long,String,char,boolean,byte... 如果是 void 用空格代替
     *   类全路径:可以用 * 标识当前路径下的所有方法进行增强
     *   方法名称:可以用 * 标识当前类的所有方法增强
     *   参数列表:可以用 .. 来标表示所有的
     *
     * 例子:
     *   对指定包指定类的指定方法增强
     *   execution(* void com.atguigu.spring5.aopanno.User.add(..))
     *   对指定包指定类里所有方法增强
     *   execution(* public com.atguigu.spring5.aopanno.User.*(..))
     *   对包里所有类和方法增强(有几级包就写几级的 * )
     *   execution(* com.atguigu.spring5.aopanno.*.*(..))
     *
     * */


    /**
     * 打算让通知在方法之前运行。
     * 生成前置通知
     */
    @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void before() {
        System.out.println("UserProxy before......");
    }

    /**
     * 最终通知
     */
    @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void after() {
        System.out.println("UserProxy after......");
    }

    /**
     * 后置通知(返回通知)
     * 在被增强方法返回值之后执行
     */
    @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterReturning() {
        System.out.println("UserProxy afterReturning......");
    }

    /**
     * 异常通知。在执行被增强方法之后进行。
     */
    @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void afterThrowing() {
        System.out.println("UserProxy afterThrowing......");
    }

    /**
     * 环绕通知
     * @param point 被增强方法,连接点
     * @throws Throwable 被抛出的异常
     */
    @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("before around");

        // 被增强的方法执行
        point.proceed();

        System.out.println("after around");
    }
}
  • 切面
    • 把通知应用到切入点的过程。
    • 把修改过的功能放回到原来的位置就是切面。切面是个动作。

演示 AOP 操作(AspectJ 注解)

  1. 创建类,在类里定义方法。这个方法就是我们说的连接点。

    image-20220309193420889

    package com.atguigu.spring5.aopanno;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:15
     */
    public class User {
        public void add() {
            System.out.println("User add......");
        }
    }
    
  2. 创建增强类(编写增强逻辑)

    image-20220309193956282

    package com.atguigu.spring5.aopanno;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    public class UserProxy {
        /**
         * 前置通知
         */
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  3. 进行通知的配置。红框就是添加的信息。

    image-20220309194300159

    <?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: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 http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开始注解扫描-->
        <context:component-scan base-package="com.atguigu.spring5.aopanno"/>
    
        <!--开启AspectJ生成代理对象-->
        <aop:aspectj-autoproxy/>
    </beans>
    
  4. 通过注解生成对象(IoC 内容)

    image-20220309194610041

    image-20220309194637077

    package com.atguigu.spring5.aopanno;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:15
     */
    @Component
    public class User {
        public void add() {
            System.out.println("User add......");
        }
    }
    
    package com.atguigu.spring5.aopanno;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    public class UserProxy {
        /**
         * 前置通知
         */
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  5. 生成代理对象

    image-20220309195102433

    package com.atguigu.spring5.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    @Aspect
    public class UserProxy {
    }
    
  6. 通过注解对切入点生成前置增强

    image-20220309202245377

    package com.atguigu.spring5.aopanno;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    /**
     * @author workplace
     * @date 2022/3/9 19:18
     * 在增强类里面,创建方法,让不同方法代表不同通知类型增强的类
     */
    @Component
    @Aspect
    public class UserProxy {
        /*
         * 我们通过在增强类里的通知方法上通过标签将切入点表达式注入value属性中指定增强
         *   @标签(value = "切入点表达式")
         *
         * 切入点表达式格式:
         *   exception(权限修饰符 返回类型 类全路径.方法名称(参数列表))
         *   权限修饰符:public,private,protect之类的,常用 * 标识任意权限修饰符
         *   返回类型:short,int,double,float,long,String,char,boolean,byte... 如果是 void 用空格代替
         *   类全路径:可以用 * 标识当前路径下的所有方法进行增强
         *   方法名称:可以用 * 标识当前类的所有方法增强
         *   参数列表:可以用 .. 来标表示所有的
         *
         * 例子:
         *   对指定包指定类的指定方法增强
         *   execution(* void com.atguigu.spring5.aopanno.User.add(..))
         *   对指定包指定类里所有方法增强
         *   execution(* public com.atguigu.spring5.aopanno.User.*(..))
         *   对包里所有类和方法增强(有几级包就写几级的 * )
         *   execution(* com.atguigu.spring5.aopanno.*.*(..))
         *
         * */
    
    
        /**
         * 打算让通知在方法之前运行。
         * 生成前置通知
         */
        @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
        public void before() {
            System.out.println("UserProxy before......");
        }
    }
    
  7. 通过测试类创建被增强类的方法可以得到结果

    package test;
    
    import com.atguigu.spring5.aopanno.User;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @author workplace
     * @date 2022/3/9 20:25
     */
    public class TestAop {
        @Test
        public void test1() {
            ApplicationContext context
                    = new ClassPathXmlApplicationContext("bean1.xml");
            User user = context.getBean("user", User.class);
            user.add();
        }
    }
    

    结果

    image-20220309202729198

  8. 总结

    image-20220309203621290

抽取相同切入点

  • 如果多个增强都是指向同一个切入点,可以把此切入点抽取出来。这样子就不需要每写一个增强方法就要写一次相同的切入点表达式了。(懒狗必备)

  • 通过 @PointCut(value = "execution(切入点表达式)") 将切入点抽取出来。

  • image-20220309205755577

    @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))")
    public void pointCut() {
    
    }
    
    /**
     * 打算让通知在方法之前运行。
     * 生成前置通知
     */
    @Before(value = "pointCut")
    public void before() {
        System.out.println("UserProxy before......");
    }
    
    /**
     * 最终通知
     */
    @After(value = "pointCut")
    public void after() {
        System.out.println("UserProxy after......");
    }
    

多个增强类同时增强一个切入点,执行优先级

  • 如果一个方法在同一时间有多个增强类的增强方法,我们可以通过设置增强类的优先级来调整增强方法的执行次序。

  • 通过给类设置标签 @Order(优先级) 设置优先级。数字越小优先级越高。

    image-20220309210616962

    @Component
    @Aspect
    @Order(1)
    public class UserProxy {}
    

整合 MyBatis

回忆 MyBatis

步骤:

  1. 我的配置 导入相关 jar 包

    • junit

      <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
      </dependency>
      
    • mysql

      <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.27</version>
      </dependency>
      
    • mybatis

      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.7</version>
      </dependency>
      
    • spring

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>5.3.17</version>
      </dependency>
      
    • aop

      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.8</version>
      </dependency>
      
    • mybatis-spring 请根据自己实际情况下载对应的版本!

      image-20220406114629419

      <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>2.0.7</version> 
      </dependency>
      
    • Spring操作数据库,需要 Spring-jdbc

      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>5.3.17</version>
      </dependency>
      
  2. 编写配置文件

    我们需要把配置文件写好。对应的 Mapper 创建对应的 Mapper.xml 文件。

    image-20220406113819781

    • jdbc.properties 模板

      jdbc.driver=com.mysql.cj.jdbc.Driver(对应的数据库驱动)
      jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC(对应数据库版本的url)
      jdbc.username=root(数据库连接账号)
      jdbc.password=root(数据库连接密码)
      
    • mybatis-config.xml 模板

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
      
          <!--引入jdbc的配置文件-->
          <properties resource="jdbc.properties"/>
      
          <!--设置Mybatis的全局配置-->
          <settings>
              <!--将字段的下划线自动映射为驼峰,默认为 false 不开启。Emp_name:EmpName  -->
              <setting name="mapUnderscoreToCamelCase" value="true"/>
              <!--开启延迟加载,就是分步查询既可以穿起来也可以拆开来。-->
              <setting name="lazyLoadingEnabled" value="true"/>
          </settings>
      
          <!--设置 pojo 包/类路径别名-->
          <typeAliases>
              <package name=""/>
          </typeAliases>
      
          <!--连接数据库文件,需要有数据库文件 jdbc.properties-->
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"/>
                      <property name="url" value="${jdbc.url}"/>
                      <property name="username" value="${jdbc.username}"/>
                      <property name="password" value="${jdbc.password}"/>
                  </dataSource>
              </environment>
          </environments>
      
          <!--以包为单位设置包内的所有类都设置默认的类别名且不区分大小写 -->
          <!--设置 mapper 包/类路径别名-->
          <mappers>
              <package name=""/>
          </mappers>
      </configuration>
      
    • mybatis-mapper.xml 模板

      <?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">
      <!--
      在 resources 下一次创建多层文件,文件名之间需要用 '/' 分隔开。例如:aa/bb/cc/dd/mapper.xml
      mapper 的 namespace 属性必须和 mapper 类路径一致
      insert、delete、update 标签的 id 属性必须和 mapper 类中的方法名一致
      -->
      
      <mapper namespace="">
      
      </mapper>
      
    • sqlSession 模板

      import org.apache.ibatis.io.Resources;
      import org.apache.ibatis.session.SqlSession;
      import org.apache.ibatis.session.SqlSessionFactory;
      import org.apache.ibatis.session.SqlSessionFactoryBuilder;
      
      import java.io.IOException;
      import java.io.InputStream;
      
      /**
       * @author workplace
       * @date 2022/3/22 21:56
       */
      public class SqlSessionUtils {
          public static SqlSession getSqlSession() {
              SqlSession sqlSession = null;
              InputStream is = null;
              try {
                  // 1. 加载核心配置文件
                  is = Resources.getResourceAsStream("mybatis-config.xml");
      
                  // 2. 创建 SqlSessionFactoryBuilder 对象
                  SqlSessionFactoryBuilder sqlSessionFactoryBuilder
                          = new SqlSessionFactoryBuilder();
      
                  // 3. 获取 SqlSessionFactory
                  SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
      
                  // 4. 获取 SqlSession。
                  // 默认 SqlSession 的 openSession 的参数为 false。
                  // 设置 SqlSession 的 openSession 为 true 可以开启自动提交。则不需要步骤 6
                  sqlSession = build.openSession(true);
              } catch (IOException e) {
                  e.printStackTrace();
              }
      
              return sqlSession;
          }
      }
      

MyBatis-Spring

我们对比一下 Mybatis 的操作和 Mybatis-Spring 的操作有什么不同

  • MyBatis

    1. 根据数据库创建 pojo 包下对应的类

      package cn.hyz.pojo;
      
      import lombok.Data;
      
      /**
       * @author workplace
       * @date 2022/4/6 11:15
       */
      
      @Data
      public class User {
          private Integer id;
          private String userName;
          private String password;
          private Integer age;
          private String sex;
          private String email;
      }
      
    2. 创建 mapper 包下对应的 Mapper 接口类

      package cn.hyz.mapper;
      
      import cn.hyz.pojo.User;
      
      import java.util.List;
      
      /**
       * @author workplace
       * @date 2022/4/6 11:18
       */
      public interface UserMapper {
          /**
           * 搜索全部内容
           * @return 用户信息列表
           */
          List<User> selectUser();
      }
      
    3. 创建 Mapper-config 配置文件

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
      
          <!--引入jdbc的配置文件-->
          <properties resource="jdbc.properties"/>
      
          <!--设置Mybatis的全局配置-->
          <settings>
              <!--将字段的下划线自动映射为驼峰,默认为 false 不开启。Emp_name:EmpName  -->
              <setting name="mapUnderscoreToCamelCase" value="true"/>
              <!--开启延迟加载,就是分步查询既可以穿起来也可以拆开来。-->
              <setting name="lazyLoadingEnabled" value="true"/>
          </settings>
      
          <!--设置 pojo 包/类路径别名-->
          <typeAliases>
              <package name="cn.hyz.pojo"/>
          </typeAliases>
      
          <!--连接数据库文件,需要有数据库文件 jdbc.properties-->
          <environments default="development">
              <environment id="development">
                  <transactionManager type="JDBC"/>
                  <dataSource type="POOLED">
                      <property name="driver" value="${jdbc.driver}"/>
                      <property name="url" value="${jdbc.url}"/>
                      <property name="username" value="${jdbc.username}"/>
                      <property name="password" value="${jdbc.password}"/>
                  </dataSource>
              </environment>
          </environments>
      
          <!--以包为单位设置包内的所有类都设置默认的类别名且不区分大小写 -->
          <!--配置需要加载的 sql 映射配置文件路径-->
          <mappers>
              <package name="cn.hyz.mapper"/>
          </mappers>
      </configuration>
      
    4. 创建 Mapper 接口类的 mapper.xml 文件(需要导入 jdbc 配置文件,具体格式参照上方)

      <?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">
      <!--
      在 resources 下一次创建多层文件,文件名之间需要用 '/' 分隔开。例如:aa/bb/cc/dd/mapper.xml
      mapper 的 namespace 属性必须和 mapper 类路径一致
      insert、delete、update 标签的 id 属性必须和 mapper 类中的方法名一致
      -->
      
      <mapper namespace="cn.hyz.mapper.UserMapper">
      
          <select id="selectUser" resultType="cn.hyz.pojo.User">
              select *
              from t_user;
          </select>
      </mapper>
      
    5. 创建测试类

      InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
      SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory build = sqlSessionFactoryBuilder.build(is);
      SqlSession sqlSession = build.openSession(true);
      UserMapper mapper = sqlSession.getMapper(UserMapper.class);
      List<User> users1 = mapper.selectUser();
      users1.forEach(System.out::println);
      
  • mybatis-spring

    1. 根据数据库创建 pojo 包下对应的类

    2. 创建 mapper 包下对应的 Mapper 接口类

    3. 创建 Mapper-config 配置文件,将其中的 连接数据库配置需要加载的 sql 映射配置文件路径 操作放在了 Spring 里去操作

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <!--设置Mybatis的全局配置-->
          <settings>
              <!--将字段的下划线自动映射为驼峰,默认为 false 不开启。Emp_name:EmpName  -->
              <setting name="mapUnderscoreToCamelCase" value="true"/>
              <!--开启延迟加载,就是分步查询既可以穿起来也可以拆开来。-->
              <setting name="lazyLoadingEnabled" value="true"/>
          </settings>
      
          <!--设置 pojo 包/类路径别名-->
          <typeAliases>
              <package name="cn.hyz.pojo"/>
          </typeAliases>
      </configuration>
      
    4. 创建 Spring-dao。用 Spring 自带的 JDBC 连接数据库;用自带的 SqlSessionFactoryBean 来导入数据库,绑定 mybatis 配置文件,配置需要加载的 sql 映射配置文件路径,并且生成 SqlSessionTemplate。

      SqlSessionTemplate 等效与 SqlSession 并且功能更多。

      <?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:p="http://www.springframework.org/schema/p"
             xmlns:c="http://www.springframework.org/schema/c"
             xmlns:context="http://www.springframework.org/schema/context"
             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">
      
          <!--开启自动装配注解支持-->
          <context:annotation-config/>
      
          <!--针对指定包路径下所有的类开启注解支持-->
          <context:component-scan base-package="cn.hyz.pojo"/>
      
          <!--
          DataSource:使用 Spring 的数据源替换 Mybatis 的配置 可选择 c3p0 dbcp druid
          这里我们使用 Spring 提供的 JDBC:
          -->
          <!--使用 Spring 提供的 JDBC 连接数据库-->
          <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
              <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
              <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC"/>
              <property name="username" value="root"/>
              <property name="password" value="root"/>
          </bean>
      
          <!--sqlSessionFactory-->
          <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
              <property name="dataSource" ref="dataSource"/>
              <!--绑定 mybatis 配置文件 -->
              <property name="configLocation" value="classpath:mybatis-config.xml"/>
              <!--配置需要加载的 sql 映射配置文件路径 -->
              <property name="mapperLocations" value="classpath:cn/hyz/mapper/*.xml"/>
          </bean>
      
          <!--
          SqlSessionTemplate:就是我们的 sqlSession
          这里只能通过构造方法传入 sqlSessionFactory 生成 SqlSessionTemplate(sqlSession)
          -->
          <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"
                c:sqlSessionFactory-ref="sqlSessionFactory"/>
      </beans>
      
    5. 创建 Mapper 的实现类

      public class UserMapperImpl implements UserMapper {
          // 在原来我们所有的操作都是用 sqlSession 来执行。现在都使用 SqlSessionTemplate
      
          private SqlSessionTemplate sqlSession;
      
          public void setSqlSession(SqlSessionTemplate sqlSession) {
              this.sqlSession = sqlSession;
          }
      
          @Override
          public List<User> selectUser() {
              UserMapper mapper = sqlSession.getMapper(UserMapper.class);
              return mapper.selectUser();
          }
      }
      
    6. 创建 applicationContext.xml 来 导入Spring-dao管理 bean

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             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">
      
          <import resource="spring-dao.xml"/>
          <bean id="userMapper" class="cn.hyz.mapper.UserMapperImpl">
              <property name="sqlSession" ref="sqlSession"/>
          </bean>
      </beans>
      
    7. 创建测试类

      package cn.hyz.test;
      
      import cn.hyz.mapper.UserMapper;
      import cn.hyz.pojo.User;
      import lombok.SneakyThrows;
      import org.junit.Test;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      import java.util.List;
      
      /**
       * @author workplace
       * @date 2022/4/6 11:22
       */
      public class MyTest {
          @SneakyThrows
          @Test
          public void selectUserTest() {
              ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
              UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
              List<User> users = userMapper.selectUser();
              users.forEach(System.out::println);
          }
      }
      

【总结】

声明式事务

回顾事务

  • 把一组业务当成一个业务;要么都成功,要么都失败
  • 事务在开发项目中十分重要,涉及到数据的一致性问题,不能马虎!
  • 保证完整性和一致性。

事务的ACID原则

  • 原子性
  • 一致性
  • 隔离性
    • 多个业务操作一个资源,防止数据损坏
  • 持久性
    • 事务一旦提交,系统无论发生什么问题,结果都不会发生影响。被持久化的写在存储性中。

Spring 中的事务管理

  • 声明式事务:AOP

    • 我们使用 Spring 自带的事务管理生成一个切面

    • 再将切面执行对应的包即可

      <!--配置声明式事务-->
      <bean id="transactionManager" 
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <constructor-arg ref="dataSource" />
      </bean>
      <!--结合AOP实现事务的织入-->
      <!--配置事务的类:Spring-->
      <tx:advice id="tx" transaction-manager="transactionManager">
          <!--给哪些方法配置事务-->
          <!--配置事务的传播特性-->
          <tx:attributes>
              <tx:method name="*"/>
          </tx:attributes>
      </tx:advice>
      
      <!--配置事务切入-->
      <aop:config>
          <aop:pointcut id="txPointCut" expression="execution(* cn.hyz.mapper.*.* (..))"/>
          <aop:advisor advice-ref="tx" pointcut-ref="txPointCut"/>
      </aop:config>
      

      如果需要使用,只需要修改这里

      image-20220409150418648

  • 编程式事务:需要在代码中对事务进行管理

思考:【为什么要配置事务】

image-20220409145606121