Back

Spring(二)

Spring的注解使用及整合MyBatis

Spring(二)

Beaen的自动装配

  • 自动装配是Spring满足Bean依赖的一种方式
  • Spring会在上下文中自动寻找,并自动给Bean装配属性
  1. byName自动装配

    byName:会自动在容器上下文中查找和自己对象set方法后面的值对应的bean id

    <?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">
    
        <bean id="cat" class="com.heng.pojo.Cat"></bean>
        <bean id="dog" class="com.heng.pojo.Dog"></bean>
    
        <bean id="person" class="com.heng.pojo.Person" autowire="byName">
            <property name="name" value="jack"></property>
        </bean>
    </beans>
    

    使用byName时,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致!

  2. byType自动装配

    byType:会自动在容器上下文中查找和自己对象属性类型相同的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">
    
        <bean id="cat" class="com.heng.pojo.Cat"></bean>
        <bean id="dog" class="com.heng.pojo.Dog"></bean>
    
        <bean id="person" class="com.heng.pojo.Person" autowire="byType">
            <property name="name" value="jack"></property>
        </bean>
    </beans>
    

    使用byType时,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致!

  3. 使用注解实现自动装配

    要使用注解,则需要做以下准备工作:

    1. 导入约束

      <?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"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
              https://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/>
      
      </beans>
      
    2. 配置注解的支持

      <context:annotation-config/>
      

    @Autowired

    • 该注解对类的成员变量、方法及构造方法进行标注,完成自动装配工作。**其作用是为了消除代码Java代码里面的getter/setter与bean属性中的property。**当然,getter看个人需求,如果私有属性需要对外提供的话,应当予以保留。

    • 该注解是按类型自动装配的,不支持id匹配

    • 代码测试:

      将User类中的set方法去掉,使用@Autowired注解

      package com.heng.pojo;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.beans.factory.annotation.Qualifier;
      
      /**
       * @Author: minster
       * @Date: 2021/10/25 8:56
       */
      public class Person {
          @Autowired
          private Cat cat;
          @Autowired
          private Dog dog;
          private String name;
          public String getName() {
              return name;
          }
          public Cat getCat() {
              return cat;
          }
          public Dog getDog() {
              return dog;
          }
      }
      

      此时配置文件的内容为

      <context:annotation-config></context:annotation-config>
      <bean id="cat1" class="com.heng.pojo.Cat"></bean>
      <bean id="dog1" class="com.heng.pojo.Dog"></bean>
      
      <bean id="person" class="com.heng.pojo.Person"></bean>
      

      编写测试代码,成功输出结果!

    • 扩展:当@Autowired(required=false)的时候,说明对象可以为null;@Autowired注解中该属性默认为true,即对象不能为null

      @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      public @interface Autowired {
      
         /**
          * Declares whether the annotated dependency is required.
          * <p>Defaults to {@code true}.
          */
         boolean required() default true;
      
      }
      

      如果允许对象为null,设置required = false,默认为true

      @Autowired(required = false)
      private Cat cat;
      

    @Qualifier

    • @Autowired是根据类型自动装配的,加上@Qualifier则可以根据byName的方式自动装配
    • @Qualifier不能单独使用

    代码测试:

    1. 修改配置文件内容,保证类型存在对象。且名字不为类的默认名字!

      <context:annotation-config></context:annotation-config>
      <bean id="cat1" class="com.heng.pojo.Cat"></bean>
      <bean id="cat2" class="com.heng.pojo.Cat"></bean>
      <bean id="dog1" class="com.heng.pojo.Dog"></bean>
      <bean id="dog2" class="com.heng.pojo.Dog"></bean>
      
      <bean id="person" class="com.heng.pojo.Person"></bean>
      

      此时我们使用@Autowired注解来标记对象,测试,程序报错

      image09

    2. 在属性上添加@Qualifier注解

      @Autowired
      @Qualifier(value = "cat1")
      private Cat cat;
      @Autowired
      @Qualifier(value = "dog2")
      private Dog dog;
      

      再进行测试,成功输出!

      image10

    @Resource

    • @Resource如果指定的name属性,则先按该属性进行byName方式进行查找装配
    • 其次再进行默认的byName方式进行装配
    • 如果以上都不成功,则按byType的方式进行自动装配

    代码测试

    1. 修改Person中的代码

      //指定name的属性
      @Resource(name = "cat1")
      private Cat cat;
      @Resource
      private Dog dog;
      
    2. 配置文件代码

      <context:annotation-config></context:annotation-config>
      <bean id="cat1" class="com.heng.pojo.Cat"></bean>
      <bean id="cat2" class="com.heng.pojo.Cat"></bean>
      <bean id="dog1" class="com.heng.pojo.Dog"></bean>
      

      测试,成功输出结果

      image10

  4. 小结:

    @Autowired与@Resource异同:

    1. @Autowired与@Resource都可以用来装配bean。都可以写在字段或setter方法上。
    2. @Autowired默认按照类型装配(属于Spring规范),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如@Autowired(required = false),如果我们想使用byName完成自动装配,则可以结合@Quaalifier注解进行使用
    3. @Resource(属于J2EE复返),默认按照名称进行装配,名称可以通过name属性进行指定。如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。

    它们的作用相同都是用注解方式注入对象,但执行顺序不同。@Autowired先byType,@Resource先byName。

使用注解开发

常用的注解有:

  • @Component 是所有受Spring 管理组件的通用形式,@Component注解可以放在类的头上,@Component不推荐使用。
  • @Controller 通过@Controller注解说明该类非普通类,而是一个控制器类。
  • @Repository 用于注解dao层,在daoImpl类上面注解。
  • @Service 用于注解Service层,在ServiceImpl类上面注解。
  • @RequestMapping 通过RequestMapping注解映射请求URL
  • @Autowired默认按类型匹配的方式,在容器查找匹配的Bean,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。
  • @Scope 设置Spring容器如何新建Bean实例(方法上,得有@Bean)
    • Singleton (单例,一个Spring容器中只有一个bean实例,默认模式)
    • Protetype (每次调用新建一个bean)
    • Request (web项目中,给每个http request新建一个bean)
    • Session (web项目中,给每个http session新建一个bean)
    • GlobalSession(给每一个 global http session新建一个Bean实例)

在Spring4之后,要使用注解开发,需要导入AOP的包

image11

在配置文件中,还需要引入一个context约束

<?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"
       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">
    
	<!--指定注解扫描包-->
    
    <context:annotation-config></context:annotation-config>
</beans>

代码测试:

  1. 编写User类,添加注解

    package com.heng.pojo;
    
    import org.springframework.stereotype.Component;
    
    /**
     * @Author: minster
     * @Date: 2021/10/25 15:08
     */
    //相当于配置文件中的 <bean id="user" class="com.heng.pojo.User>
    @Component("user")
    public class User {
        public String name = "jack";
    }
    
  2. 在xml配置文件中加入指定注解扫描包

    <!--指定注解扫描包-->
    <context:component-scan base-package="com.heng.pojo"></context:component-scan>
    <!--支持注解-->
    <context:annotation-config></context:annotation-config>
    
  3. 编写测试类

    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        User user = context.getBean("user", User.class);
        System.out.println(user.name);
    }
    

    成功输出

使用注解注入属性:

  1. 可以不用提供set方法,直接在属性上面添加@Value(“值”)注解

    /** 相当于配置文件中的 
     * <bean id="user" class="com.heng.pojo.User>
     *  <property name="name" value="jack"></property>
     * </bean>
    * */
    @Component("user")
    public class User {
        @Value("jack")
        public String name ;
    }
    
  2. 如果提供了set方法,可以在set方法上添加Value(“值”);

    public int age;
    @Value("10")
    public void setAge(int age) {
        this.age = age;
    }
    
  3. 测试结果:

    image12

小结

  • XML与注解比较
    • XML可以适用任何场景,结构清晰,维护方便
    • 注解不是自己提供的类适用不了,开发简单方便
  • xml与注解整合开发:推荐最佳实践!
    • xml管理bean
    • 注解完成属性注入
    • 适用过程中,可以不用扫描,扫描是为了类上的注解
  • <context:annotation-config/> 作用
    • 进行注解驱动注册,从而使注解生效
    • 用于激活那些已经在Spring容器里注册过的Bean上面的注解,也就是显示的向Spring注册
    • 如果不扫描包,就需要手动配置bean
    • 如果不加载注解驱动,则注入的值为Null

基于Java类进行配置

  • JavaConfig原本是Spring的一个子项目,它通过Java类的方式提供了Bean的定义信息,在Spring4的版本,JavaConfig已正式成为Spring4的核心功能!

    image15

  • 代码测试

    1. 编写一个实体类Dog

      @Component
      public class Dog {
          public String name = "dog";
      }
      
      
    2. 新建一个Config包,编写配置类MyConfig

      //Configuration代表这是一个配置类
      @Configuration
      public class MyConfig {
          //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
          @Bean
          public Dog dog(){
              return new Dog();
          }
      }
      
    3. 编写测试方法

      @Test
      public void test02(){
          ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
          Dog dog = (Dog) context.getBean("dog");
          System.out.println(dog.name);
      }
      
    4. 测试结果:

      image13

    5. 注意:如果需要对实体类的属性进行注入,使用@Value(“值”)注解即可

      package com.heng.pojo;
      
      import org.springframework.beans.factory.annotation.Value;
      import org.springframework.stereotype.Component;
      
      /**
       * @Author: minster
       * @Date: 2021/10/25 15:38
       */
      @Component
      public class Dog {
          public String name = "dog";
          @Value("10")
          public int age;
      }
      

      image14

如果此时需要导入其他类,我们可以使用@Import

  1. 其他配置类MyConfig2

    @Configuration
    public class MyConfig2 {
    }
    
  2. 修改MyConfig

    package com.heng.config;
    
    import com.heng.pojo.Dog;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    /**
     * @Author: minster
     * @Date: 2021/10/25 15:39
     */
    @SuppressWarnings({"all"})
    //Configurationd代表这是一个配置类
    @Configuration
    @Import(MyConfig2.class)
    public class MyConfig {
        //通过方法注册一个bean,这里的返回值就Bean的类型,方法名就是bean的id!
        @Bean
        public Dog dog(){
            return new Dog();
        }
    }
    

    关于这种Java类的配置方式,我们在之后的SpringBoot 和 SpringCloud中还会大量看到,我们需要知道这些注解的作用即可~

AOP

  • 什么是AOP

    AOP(Aspect Oriented Programming):面向切面编程,能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可拓展性和可维护性。

    Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP就会使用JDK Proxy,去创建代理对象,而对没有实现接口的对象,就无法使用JDK Proxy去进行代理了,这时候Spring AOP就会使用Cglib生成一个被代理对象的子类来作为代理,如下图所示:

    image16

    当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。

  • AOP的相关名词

    • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 ….
    • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。
    • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。
    • 目标(Target):被通知对象。
    • 代理(Proxy):向目标对象应用通知之后创建的对象。
    • 切入点(PointCut):切面通知 执行的 “地点”的定义。
    • 连接点(JointPoint):与切入点匹配的执行点。
  • 现在我们举一个例子来理解什么是AOP

    AOP:Aspect oriented programming 面向切面编程,AOP 是 OOP(面向对象编程)的一种延续。

    例如:现有三个类:HorsePigDog,这三个类中都有eat和run两个方法

    通过OOP思想中的继承,我们可以提取出一个Animal的父类,然后将eat和run方法放入父类中,HorsePigDog通过继承Animal类即可自动获得eat()run()方法。这样将会少写很多代码。

    image17

    OOP 编程思想可以解决大部分的代码重复问题。但是有一些问题是处理不了的。比如在父类 Animal 中的多个方法的相同位置出现了重复的代码,OOP 就解决不了。

    /**
     * 动物父类
     */
    public class Animal {
    
        /** 身高 */
        private String height;
    
        /** 体重 */
        private double weight;
    
        public void eat() {
            // 性能监控代码
            long start = System.currentTimeMillis();
    
            // 业务逻辑代码
            System.out.println("I can eat...");
    
            // 性能监控代码
            System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
        }
    
        public void run() {
            // 性能监控代码
            long start = System.currentTimeMillis();
    
            // 业务逻辑代码
            System.out.println("I can run...");
    
            // 性能监控代码
            System.out.println("执行时长:" + (System.currentTimeMillis() - start)/1000f + "s");
        }
    }
    

    这部分重复的代码,一般统称为 横切逻辑代码

    image18

    横切逻辑代码存在的问题:

    • 代码重复问题
    • 横切逻辑代码和业务代码混杂在一起,代码臃肿,不变维护

    AOP就是用来解决这些问题的!

    AOP 另辟蹊径,提出横向抽取机制,将横切逻辑代码和业务逻辑代码分离

    image19

    代码拆分比较容易,难的是如何在不改变原有业务逻辑的情况下,悄无声息的将横向逻辑代码应用到原有的业务逻辑中,达到和原来一样的效果。

  • AOP 解决了什么问题

    通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

  • AOP 为什么叫面向切面编程

    :指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑

    :横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念

在AOP中,通过Advice定义横切逻辑,Spring中支持5中类型的Advice:

image20

即 Aop 在 不改变原有代码的情况下 , 去增加新的功能 .

  • 使用Spring来实现AOP

    1. 导入新的相关依赖包

      <!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
         <version>1.9.4</version>
      </dependency>
      

    第一种实现方式:通过Spring API实现

    • 编写业务接口和实现类

      UserService业务接口:

      package com.heng.service;
      
      /**
       * @Author: minster
       * @Date: 2021/10/25 19:45
       */
      @SuppressWarnings({"all"})
      public interface UserService {
          public void add();
          public void delete();
          public void update();
          public void select();
      }
      

      UserServiceImpl接口实现类

      package com.heng.service;
      
      /**
       * @Author: minster
       * @Date: 2021/10/25 19:45
       */
      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 select() {
              System.out.println("查询了一个用户");
          }
      }
      

      编写横切逻辑代码(就是增强类 , 我们编写两个 , 一个前置增强 一个后置增强)

      前置增强类BeforeLog

      package com.heng.log;
      
      import org.springframework.aop.MethodBeforeAdvice;
      
      import java.lang.reflect.Method;
      
         //method : 要执行的目标对象的方法
         //args : 被调用的方法的参数
         //target : 目标对象
      public class BeforeLog implements MethodBeforeAdvice {
          @Override
          public void before(Method method, Object[] args, Object target) throws Throwable {
              System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了");
          }
      }
      

      后置增强类AfterLog

      package com.heng.log;
      
      import org.springframework.aop.AfterReturningAdvice;
      
      import java.lang.reflect.Method;
      
         //returnValue 返回值
         //method被调用的方法
         //args 被调用的方法的对象的参数
         //target 被调用的目标对象
      public class AfterLog implements AfterReturningAdvice {
          @Override
          public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
              System.out.println("执行了" + target.getClass().getName()+"的"+method.getName()+"方法,"+"返回值:"+returnValue);
          }
      }
      

      最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

      <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:aop="http://www.springframework.org/schema/aop"
             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">
      
          <!--注册Bean-->
          <bean id="userService" class="com.heng.service.UserServiceImpl"></bean>
          <bean id="afterLog" class="com.heng.log.AfterLog"></bean>
          <bean id="beforeLog" class="com.heng.log.BeforeLog"></bean>
          <!--AOP配置-->
          <aop:config>
              <!--切入点 expression:表达式匹配要执行的方法,execution(要执行的位置 * * * * *)
                execution(修饰符(一般省略)  返回值  包.类.方法名(参数) throws异常(一般省略))-->
              <aop:pointcut id="pointcut" expression="execution(* com.heng.service.UserServiceImpl.*(..))"/>
              <!--执行环绕:advice-ref 执行方法,pointcut-ref 切入点-->
              <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"></aop:advisor>
              <aop:advisor advice-ref="beforeLog" pointcut-ref="pointcut"></aop:advisor>
          </aop:config>
      </beans>
      

      测试方法

      @Test
      public void testAop(){
          ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
          UserService userService = context.getBean("userService", UserService.class);
          userService.add();
      }
      

      运行结果:

      image21

      Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .

    第二种方式:自定义类来实现AOP

    目标业务类不变依旧是userServiceImpl

    1. 写我们自己的一个切入类

      public class DiyPointcut {
      
         public void before(){
             System.out.println("---------方法执行前---------");
        }
         public void after(){
             System.out.println("---------方法执行后---------");
        }
      
      }
      
    2. 去applicationContext中配置

      <!--第二种方式自定义实现-->
      <!--注册bean-->
      <bean id="diy" class="com.kuang.config.DiyPointcut"/>
      
      <!--aop的配置-->
      <aop:config>
         <!--第二种方式:使用AOP的标签实现-->
         <aop:aspect ref="diy">
             <aop:pointcut id="diyPonitcut" expression="execution(* com.kuang.service.UserServiceImpl.*(..))"/>
             <aop:before pointcut-ref="diyPonitcut" method="before"/>
             <aop:after pointcut-ref="diyPonitcut" method="after"/>
         </aop:aspect>
      </aop:config>
      
    3. 测试,成功输出!

    第三种方式:使用注解完成

    1. 编写一个注解实现的增强类

      package com.heng.config;
      
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.After;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      
      /**
       * @Author: minster
       * @Date: 2021/10/26 8:08
       */
      @Aspect
      public class AnnotationPointcut {
          @Before("execution(* com.heng.service.UserServiceImpl.*(..))")
          public void before(){
              System.out.println("方法执行前");
          }
          @After("execution(* com.heng.service.UserServiceImpl.*(..))")
          public void after(){
              System.out.println("方法执行后");
          }
          @Around("execution(* com.heng.service.UserServiceImpl.*(..))")
          public void around(ProceedingJoinPoint jp) throws Throwable {
              System.out.println("环绕前");
              Object proceed = jp.proceed();
              System.out.println("环绕后");
          }
      }
      
    2. 在配置文件中,注册Bean,并增加支持注解的配置

      <bean id="annotationPointcut" class="com.heng.config.AnnotationPointcut"></bean>
      <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
      

      aop:aspectj-autoproxy:说明

      通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

      <aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false,表示使用jdk动态代理织入增强

      当配为<aop:aspectj-autoproxy poxy-target-class="true"/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

    3. 测试,成功输出

    **注意:**使用注解增强类完成切入时,切入顺序为环绕前->方法执行前->方法->方法执行后->环绕后

整合MyBatis

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"></property>
    <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    <property name="mapperLocations" value="classpath:com/heng/dao/UserMapper.xml"></property>
 </bean>

注意:SqlSessionFactory需要一个 DataSource(数据源)。这可以是任意的 DataSource,只需要和配置其它 Spring 数据库连接一样配置它就可以了。

在基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。而在 MyBatis-Spring 中,则使用 SqlSessionFactoryBean 来创建

  • 在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession。一旦你获得一个 session 之后,你可以使用它来执行映射了的语句,提交或回滚连接,最后,当不再需要它的时候,你可以关闭 session。

  • SqlSessionFactory有一个唯一的必要属性:用于 JDBC 的 DataSource。这可以是任意的 DataSource 对象,它的配置方法和其它 Spring 数据库连接是一样的。

  • 一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings>< typeAliases>元素

    需要注意的是,这个配置文件并不需要是一个完整的 MyBatis 配置。确切地说,任何环境配置(<environments>),数据源(<DataSource>)和 MyBatis 的事务管理器(<transactionManager>)都会被忽略。SqlSessionFactoryBean 会创建它自有的 MyBatis 环境配置(Environment),并按要求设置自定义环境的值。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession。

public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}

模板可以参与到 Spring 的事务管理中,并且由于其是线程安全的,可以供多个映射器类使用,你应该总是用 SqlSessionTemplate 来替换 MyBatis 默认的 DefaultSqlSession 实现。在同一应用程序中的不同类之间混杂使用可能会引起数据一致性的问题。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
 <constructor-arg index="0" ref="sqlSessionFactory" />
</bean>

现在,这个 bean 就可以直接注入到你的 DAO bean 中了。你需要在你的 bean 中添加一个 SqlSession 属性,就像下面这样:

public class UserDaoImpl implements UserDao {

 private SqlSession sqlSession;

 public void setSqlSession(SqlSession sqlSession) {
   this.sqlSession = sqlSession;
 }

 public User getUser(String userId) {
   return sqlSession.getMapper(UserMapper.class).getUser();
 }
}

按下面这样,注入 SqlSessionTemplate:

<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
 <property name="sqlSession" ref="sqlSession" />
</bean>

图示:

image23

整合实现示例

  1. 引入Spring配置文件spring-dao.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:aop="http://www.springframework.org/schema/aop"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           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
           http://www.springframework.org/schema/mvc
           http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
  2. 配置数据源替换MyBatis数据源

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"></property>
        <property name="username" value="root"></property>
        <property name="password" value="777777"></property>
    </bean>
    

    如果在配置文件中写了数据库的配置的话,用以下方法配置数据源

    db.properties

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis?userSSL=true&userUnicode=true&characterEncoding=utf8
    username=root
    password=777777
    

    spring-dao.xml

    <context:property-placeholder location="db.properties"></context:property-placeholder>
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driver}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${username}"></property>
        <property name="password" value="${password}"></property>
    </bean>
    
  3. 配置SqlSessionFactory,关联MyBatis

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property><!--关联MyBatis-->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
        <property name="mapperLocations" value="classpath:com/heng/dao/UserMapper.xml"></property>
     </bean>
    
  4. 注册sqlSessionTemplate,关联sqlSessionFactory;

    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!--利用构造器注入-->
        <constructor-arg index="0" ref="sqlSessionFactory"></constructor-arg>
    </bean>
    
  5. 增加Dao接口的实现类;私有化sqlSessionTemplate

    package com.heng.dao;
    
    import com.heng.pojo.User;
    import org.mybatis.spring.SqlSessionTemplate;
    
    import java.util.List;
    
    public class UserMapperImpl implements UserMapper{
        private SqlSessionTemplate sqlSession;
    
        public void setSqlSession(SqlSessionTemplate sqlSession) {
            this.sqlSession = sqlSession;
        }
    
        @Override
        public List<User> getUser(){
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            return mapper.getUser();
        }
    }
    
  6. 注册Bean实现

    <bean id="userDao" class="com.heng.dao.UserDaoImpl">
       <property name="sqlSession" ref="sqlSession"/>
    </bean>
    
  7. 测试

       @Test
       public void test2(){
           ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
           UserMapper mapper = (UserMapper) context.getBean("userDao");
           List<User> user = mapper.selectUser();
           System.out.println(user);
      }
    

    image24

    此时我们查看MyBAtis配置文件,可以发现大部分功能被整合!

    <?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>
        <settings>
            <setting name="logImpl" value="STDOUT_LOGGING"/>
        </settings>
        <typeAliases>
            <package name="com.heng.pojo"/>
        </typeAliases>
    </configuration>
    

当然,我们还有一种整合MyBatis的方式,这种方式更加简洁,不需要管理SqlSessionTemplate,而且对事物支持更加友好

image25

示例代码:

  1. 将上述UserDaoImpl修改一下,继承SqlSessionDaoSupport类

    package com.heng.dao;
    
    import com.heng.pojo.User;
    import org.mybatis.spring.SqlSessionTemplate;
    import org.mybatis.spring.support.SqlSessionDaoSupport;
    
    import java.util.List;
    public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{
        @Override
        public List<User> getUser(){
            UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
            return mapper.getUser();
        }
    }
    
  2. 修改Bean的配置

    <bean id="userMapper" class="com.heng.dao.UserMapperImpl">
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
    </bean>
    
  3. 测试,输出结果与上面一样

Spring事物管理

  • 回顾事物

    • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!
    • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

    事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用

  • 事务的四个特性

    1. 原子性(atomicity)

    2. 事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

    3. 一致性(consistency)

    4. 一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

    5. 隔离性(isolation)

    6. 可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

    7. 持久性(durability)

    8. 事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

  • Spring中的事物管理

    • 编程式事务
      • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
      • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码
    • 声明式事务
      • 一般情况下比编程式事务好用。
      • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
      • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

使用声明式事务代码演示

  1. 头文件导入约束

    xmlns:tx="http://www.springframework.org/schema/tx"
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd
    
  2. 配置事务管理器

    • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的
    • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource " ref="dataSource"></property>
    </bean>
    
  3. 配置事务通知

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!--配置哪些方法可以使用什么样的事物,配置事务的传播特性默认为REQUIRED-->
            <tx:method name="add" propagation="REQUIRED"/>
            <tx:method name="delete" propagation="REQUIRED"/>
            <tx:method name="update" propagation="REQUIRED"/>
            <!--为所有方法配置事务的方式-->
            <tx:method name="*" propagation="REQUIRED"></tx:method>
            <!--查询方法,配置事务为只可读,保护数据-->
            <tx:method name="query" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    

    事务的传播特性:

    事务传播行为就是多个事务方法相互调用时,事务如何在这些方法间传播。spring支持7种事务传播行为:

    • propagation_requierd:如果当前没有事务,就新建一个事务,如果已存在一个事务中,加入到这个事务中,这是最常见的选择。
    • propagation_supports:支持当前事务,如果没有当前事务,就以非事务方法执行。
    • propagation_mandatory:使用当前事务,如果没有当前事务,就抛出异常。
    • propagation_required_new:新建事务,如果当前存在事务,把当前事务挂起。
    • propagation_not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • propagation_never:以非事务方式执行操作,如果当前事务存在则抛出异常。
    • propagation_nested:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与propagation_required类似的操作

    Spring 默认的事务传播行为是 PROPAGATION_REQUIRED,它适合于绝大多数的情况。

  4. 在Spring中使用AOP的方式配置事务

    <aop:config>
        <!--配置切入点-->
        <aop:pointcut id="txPointcut" expression="execution(* com.heng.dao.*.*(..) )"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor>
    </aop:config>
    
  5. 测试

    @Test
    public void testTransaction(){
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        User jack = new User(7, "jack", "111111");
        Map map = new HashMap();
        map.put("id",3);
        map.put("name","陈立业");
        map.put("pwd","111111");
        int addUser = userMapper.addUser(jack);
        int deleteUser = userMapper.deleteUser(5);
        int updateUser = userMapper.updateUser(map);
    
        List<User> users = userMapper.getUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
    

    image26

Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy