Spring的注解开发

由于现在注解渐渐流行 也简单了不少 spring中添加了很多的注解来帮我们做了很多繁琐的事情

所以现在注解开发是主流 需要熟练掌握

注解使用条件

  • Spring4 之后 要使用注解开发,必须要保证 **AOP(面向切面编程)**的包导入
  • 使用注解需要导入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
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--指定要扫描的包 这个包下的注解就会生效-->
    <context:component-scan base-package="cn.mianju.pojo"/>
    
    <!--    开启注解支持-->
    <context:annotation-config/><!--有了上面的扫描包可以去掉这一行-->
<beans/>

1、 基础注解

分别分为几块:

  • Bean
  • 属性如何注入
  • 衍生的注解
  • 作用域
  • 小结

1.1 Bean

package cn.mianju.pojo;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component//等价于 <bean id="user" class="cn.mianju.pojo.User"/>
public class User {

    @Value("xf") //等价于 bean中的 <property name="name" value="xf"/>
    public String name;

    public String getName() {
        return name;
    }

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

@Component 注解 就相当于在 applicationContext.xml 中配置了这个类

等价于<bean id="user" class="cn.mianju.pojo.User"/>

**@Value("xf")**注解 给bean中的属性赋值

等价于Bean标签 中的<property name="name" value="xf"/>

1.2 属性如何注入

通过注解注入属性的话只能注入简单的基本类型和String类型,复杂类型或者引用类型还是用DI注入

就使用 @Value(值) 去注入属性

@Value("xf") //等价于 bean中的 <property name="name" value="xf"/>
public String name;

如果在属性上面放了注解且在XML也配置了属性,最终会以XML配置的值为准

1.3 衍生的注解

@Component 有几个衍生的注解,他们的功能和效果一致;在web开发中,我们会按照规范和mvc三层架构分层

  • Dao(Mapper)【@Repository
  • Service【@Service
  • Controller【@Controller

==这四个注解功能都是一致的:都是将某个类注册到Spring容器中 装配成Bean==

1.4 作用域

作用域的注解是:@Scope

@Component//等价于 <bean id="user" class="cn.mianju.pojo.User"/>
@Scope("singleton")//使用注解声明为单例模式
public class User {

    @Value("xf") //等价于 bean中的 <property name="name" value="xf"/>
    public String name;

}

放在类上的注解 说明这个类是单例模式

1.5 小结

XML与注解:

  • XML会更加万能 配置更多更全面 适用于任何场合 维护起来简单方便
  • 注解维护相对复杂 只能适用于简单场合 但是使用简单
  • 现在企业级开发注解和XML都会使用到 在Spring Boot中注解用的会更多一些

2、 使用Java代码的方式配置

这个是Spring的新特性,可以完全不使用XML进行配置 可以全权交给Java来做

JavaConfig 是Spring的一个子项目 在Spring 4之后 它成为了一个核心功能

这种方式在Spring Boot中会比较常见

2.1 配置类

package cn.mianju.config;

import cn.mianju.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;


@Configuration//Configuration 代表它是一个配置类 和之前的xml一样
@ComponentScan("cn.mianju")//注册扫描cn.mianju包
public class MyConfig {

    //注册一个Bean 就相当于我们之前写的一个Bean标签
    //这个方法的名字就相当于bean标签中的ID
    //这个方法的返回值就相当于bean标签中的class值
    @Bean
    public User getUser() {
        return new User(); //这个就相当于要注入到bean中的对象
    }

}

来分析一下

  • **@Configuration:**表述这个类是一个配置类 可以和xml一样 相当于这个类就是一个xml
  • **@ComponentScan:**这个类要注册扫描某个包
  • @Import:和xml中的import一样;xml是导入其他文件的路径,这个Import是导入其他配置类的class
  • **@Bean:**注册一个bean 相当于一个bean标签,和 @Component 有点类似 但是是使用在配置类中的 @Bean注解是需要放在某个方法上面的

2.2 实体类

package cn.mianju.pojo;

import org.springframework.beans.factory.annotation.Value;

public class User {
    @Value("xf")
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • **@Value:**注入属性值
  • 这里就不需要加 @Component 在配置类中已经声明这个是一个Bean了

2.3 测试类

import cn.mianju.config.MyConfig;
import cn.mianju.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        User user = context.getBean("getUser", User.class);
        System.out.println(user.getName());
    }
}
  • 如果要使用配置类来进行配置而不是xml的话,就不能使用 ClassPathXMLApplicationContext 来添加配置了

则是需要 AnnotationConfigApplicationContext 来添加 参数是配置类的class

  • getBean方法的参数不是Bean的Id(都没见到哪里有Id)也不是类的类名,而是在配置类中的方法名

2.4 小结

  • 这个方法是Spring的新特性 在Spring 4之后成为核心功能

  • 在Spring Boot中 这种配置类的方式随处可见

  • 配置类中的方法写了@Bean则实体类中不再需要添加@Component

  • **使用这个方法记得 类一定是new AnnotationConfigApplicationContext 这个类 **

  • getBean要get它的方法名,来获得这个Bean;那个方法名就相当于Bean的Id

  • 注解@Configuration 的底层 就是 @Component

    • import java.lang.annotation.Documented;
      import java.lang.annotation.ElementType;
      import java.lang.annotation.Retention;
      import java.lang.annotation.RetentionPolicy;
      import java.lang.annotation.Target;
      import org.springframework.core.annotation.AliasFor;
      import org.springframework.stereotype.Component;
      
      @Target({ElementType.TYPE})
      @Retention(RetentionPolicy.RUNTIME)
      @Documented
      @Component
      public @interface Configuration {
          @AliasFor(
              annotation = Component.class
          )
          String value() default "";
      
          boolean proxyBeanMethods() default true;
      }
      

1、 什么是AOP?

面向切面编程(Aspect Oriented Programming) AOP :通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

AOP类似一种装饰器,它可以在不修改源代码的情况下拓展其他的功能;这样既达成了需求,也不会违反OOP原则

  • Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的 Advice。
    • 切面也就是一个个的逻辑代码模块:例如日志模块、权限模块等
  • **Joint point(连接点):**表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它 joint point。
    • 连接点是程序执行过程中明确的点,一般是类中方法的调用。连接点是程序在运行过程中能够插入切面的地点,比如方法调用、异常抛出、字段修改等。
  • **Pointcut(切点):**表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
    • 切点就是需要在哪个地方切入 也就是讲你逻辑代码放置的地方 在 Advice(通知)中定义
  • **Advice(通知):**Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通过 before、after 和 around 来区别是在每个 joint point 之前、之后还是代替执行的代码。
    • 通知可以分为前置通知Before、后置通知AfterReturning、异常通知AfterThrowing、最终通知After、环绕通知Around五类。
  • **Target(目标对象):**织入 Advice 的目标对象.。
    • 需要将新逻辑代码(方法)合并到其他代码(方法)中的对象
  • **Weaving(织入):**将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程
    • 通过切点合并在一起 将创建的切面 应用到目标的对象从而创建新的代理对象的过程 ;通过动态代理和反射来实现

AOP可以在不影响我们业务的情况下 实现动态的增强

2、 AOP的实现

假设在 CRUD 功能的基础上需要加上日志的功能

这个时候就需要用到AOP来实现

首先要实现AOP必须要导入对应的包:

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

然后 **applicationContext.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">

</beans>

2.1 配置文件实现AOP

配置文件实现AOP有两种方式:

2.1.1 方式一:使用Spring API接口来实现

UserService:

package cn.mianju.demo01.service;

public interface UserService {
    void add();
    void delect();
    void modify();
    void query();
}

UserServiceImpl:

package cn.mianju.demo01.service;

public class UserServiceImpl implements UserService{

    @Override
    public void add() {
        System.out.println("add");
    }

    @Override
    public void delect() {
        System.out.println("delect");
    }

    @Override
    public void modify() {
        System.out.println("modify");
    }

    @Override
    public void query() {
        System.out.println("query");
    }
}

然后现在 需要写两个日志类,一个在模块运行前 一个模块运行后

Log(运行前日志):

package cn.mianju.demo01.log;

import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Log implements MethodBeforeAdvice {


    /**
     * @param method :要执行的目标对象的方法
     * @param args :参数
     * @param target :目标对象 就是真实对象的那个类
     * **/
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("方法是" + method + " | " + "target是" + target);
    }
}

解析:

  • **method:**是代理后类中将要执行的方法
  • **args:**传入的参数
  • **target:**是代理的真是类/对象

注意:这个方法会在运行前运行一遍,需要实现MethodBeforeAdvice接口 并重写before方法

before 中的代码就是会在模块运行前运行一遍的代码

AfterLog(运行后日志):

package cn.mianju.demo01.log;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

import java.lang.reflect.Method;

public class Afterlog implements AfterReturningAdvice {

    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("这是返回之后的方法:Afterlog");
    }
}

解析:

  • **returnValue:**模块执行完后的返回值
  • **method:**方法
  • **args:**参数
  • **target:**代理的真实对象

注意:这个方法会在运行前运行一遍,需要实现AfterReturningAdvice接口 并重写afterReturning方法

afterReturning 中的代码就是会在模块运行前运行一遍的代码

然后到 applicaitonContext.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">

<!--    注册bean-->
    <bean id="userService" class="cn.mianju.demo01.service.UserServiceImpl"/>
    <bean id="log" class="cn.mianju.demo01.log.Log"/>
    <bean id="Afterlog" class="cn.mianju.demo01.log.Afterlog"/>

<!--    配置aop:需要导入aop的约束-->
    <aop:config>
<!--        切入点:expression表达式:execution(*:返回值类型 cn.mianju…….*(..):某个类下所有的方法包含所有参数的)-->
        <aop:pointcut id="userService1" expression="execution(* cn.mianju.demo01.service.UserServiceImpl.*(..))"/>

<!--        执行环绕增强-->
        <aop:advisor advice-ref="log" pointcut-ref="userService1"/>
        <aop:advisor advice-ref="Afterlog" pointcut-ref="userService1"/>
    </aop:config>

</beans>

解析:

  • 首先标签 **<aop:config>**是配置aop的标签,在标签中进行aop的设置
  • aop设置需要一个切入点,使用 <aop:pointcut> 来表示 其中有个属性是 expression 是个表达式,表达式中的内容就是切入的点
    • expression(返回值类型 返回值的类+方法+参数)
    • 如果是所有的方法和参数就是 ( cn.mianju.demo01.service.UserServiceImpl.(..))** 两个 ==..== 很重要 代表所有的参数
  • 然后就执行环绕增强 也就是执行两个log方法
    • 参数的 advice-ref 就是上面注册的log的bean
    • 参数的 pointcut-ref 就是 **<aop:pointcut>**的id 也就是切入点

最后去写一个类进行测试:

MyTest:

import cn.mianju.demo01.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userService");
        userService.add();
    }
}

**注意:**getBean的时候的参数是接口 不是UserServiceImpl

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

首先这个自定义类来实现 切面 相对于第一张 Spring的API实现简单很多 但是功能相对于不够全面

少了 **method、args、target、returnValue **等参数

在简单环境下可以使用这种方式

首先自定义一个类:

package cn.mianju.demo01.diy;

public class MyDiy {

    void before(){
        System.out.println("========运行之前========");
    }

    void after(){
        System.out.println("========运行之后========");
    }
}

其中2个方法是需要在我模块运行前后插入的

然后就可以到 applicationContext.xml 中去注册:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">

<!--        注册Bean-->
    <bean id="userService" class="cn.mianju.demo01.service.UserServiceImpl"/>
    <bean id="diy" class="cn.mianju.demo01.diy.MyDiy"/>

    <aop:config>

<!--        还是获取切入点 和之前一样-->
        <aop:pointcut id="point" expression="execution(* cn.mianju.demo01.service.UserServiceImpl.*(..))"/>

        <aop:aspect ref="diy">
            <aop:after method="after" pointcut-ref="point"/>
            <aop:before method="before" pointcut-ref="point"/>
        </aop:aspect>

    </aop:config>

</beans>

在这里要注意:

  • 切入点一样要获取 需要在哪个方法添加这几个功能就切入到哪个地方
  • 使用的是 <aop:aspect> 标签 也就是 切面 标签 属性 ref指定自定义的类
  • 然后在**<aop:aspect>**标签中 有前后方法的标签
    • **<aop:after>:**在方法执行前执行
    • <aop:before>: 在方法执行后执行

注意 这种方式简单一些 但是没有获取到 **method、args、target、returnValue **等参数

如果需要这些参数还是使用方法一来实现AOP

2.2 注解实现AOP

最后一种实现AOP的方式: 注解方式实现

注解实现AOP和方式二:自定义类实现AOP比较类似 都需要定义注册一个自定义的 切面类

只是一个是在XML中配置 一个是使用注解去实现

首先也需要一个自定义一个切面类:

package cn.mianju.demo02.diy;

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;

@Aspect //声明是一个切面类
public class MyZhujieDiy {

    @Before("execution(* cn.mianju.demo02.service.UserServiceImpl2.*(..))")
    //Before:是代码运行之前运行
    void before(){
        System.out.println("==前==");
    }

    @After("execution(* cn.mianju.demo02.service.UserServiceImpl2.*(..))")
    //after : 代码运行之后运行
    void after(){
        System.out.println("==后==");
    }

    @Around("execution(* cn.mianju.demo02.service.UserServiceImpl2.*(..))")
    //环绕运行 :运行前后运行后都可以运行一些代码
    void around(ProceedingJoinPoint point){
        System.out.println("环绕前");
        
        try {
            //执行方法 比如add()
            Object proceed = point.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        
        System.out.println("环绕后");
    }

}

这段话的注解有:

  • @Aspect: 声明是一个切面类、自定义的AOP类
  • **@Before:**方法执行之前运行
  • **@After:**方法执行之后运行
  • **@Around:**环绕运行 可以写执行前也可以写执行后 中间执行的方法就是 point.proceed()

注意 这段代码的结果是:

环绕前
==前==
add
==后==
环绕后

也就是说 环绕运行的优先级最高 然后中途 运行代码的时候 也就是到 Object proceed = point.proceed(); 这句话的时候 才相当于开始执行add那个方法 才会执行befoce和after

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

    <bean id="userService2" class="cn.mianju.demo02.service.UserServiceImpl2"/>
    
<!--    这个自定义的也需要注册-->
    <bean id="zhujie" class="cn.mianju.demo02.diy.MyZhujieDiy"/>
    <!--    开启注解支持-->
    <aop:aspectj-autoproxy/>

</beans>
  • AOP的约束一定要有
  • 自定义的切面类也需要注册
  • 需要开启AOP注解的支持(否则虽然不会报错 但是AOP失效)

3、 小结

首先 AOP是:可以在不影响我们业务的情况下 实现动态的增强

AOP的实现有三种

  • Spring API实现
    • 相对繁琐 但是更全面 通过XML来配置 也可以获得 target对象等属性
  • 自定义切面类实现
    • 相对简单 只需要创建一个切面,编写需要插入的逻辑代码后,在XML中注册并配置 适用于简单业务
  • 注解实现
    • 自定义切面类实现 类似;不过更简单 在xml中开启注解说明后 只需要创建一个切面类 在对应的位置添加注解后就可以实现
    • 但是和自定义切面类一样 不适应与复杂业务 因为都没有target对象等重要属性

按我的理解:AOP就是一个类似装饰器的东西,在不修改原本的代码下进行动态的代码的拓展;底层的实现用到了动态代理和反射机制

Spring 声明式事务

什么是事务:

  • 要么都执行 要么都不执行
  • 中途出现问题就全部回滚到开始执行的阶段
  • 事务可以保证一致性和准确性

之前学的事务是try catch的那种

try {
      userMapper.insertUser(user);
    } catch (Exception e) {
      transactionManager.rollback(txStatus);
      throw e;
    }
transactionManager.commit(txStatus);

这一种属于 编程式事务管理

这种事务需要修改源码 这样就违反了OOP原则

而spring有一种 声明式事务管理

利用AOP织入到模块中,这样即不会违反OOP也可以实现事务

测试环境

Spring + Mybatis

UserMapper:

package cn.mianju.mapper;

import cn.mianju.pojo.User;

import java.util.List;

public interface UserMapper {
    List<User> query();
    
    void insert(User user);
}

UserMapper.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">
<mapper namespace="cn.mianju.mapper.UserMapper">
    <select id="query" resultType="cn.mianju.pojo.User">
        select * from user
    </select>
    <insert id="insert" parameterType="cn.mianju.pojo.User">
        insert into user values (#{name},#{age})
    </insert>
</mapper>

UserMapperImpl:

package cn.mianju.mapper;

import cn.mianju.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;

import java.util.List;

public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
    @Override
    public List<User> query() {
        System.out.println("查询方法中");
        return getSqlSession().getMapper(UserMapper.class).query();
    }

    @Override
    public void insert(User user) {
        System.out.println("insert");
        getSqlSession().getMapper(UserMapper.class).insert(new User("aa",2));
        
        //这块的抛异常是来测试事务
        if (user.getAge()>0){
            throw new NullPointerException();
        }
        getSqlSession().getMapper(UserMapper.class).insert(user);
    }
}
  • 首先插入一个对象
  • 然后再抛异常
  • 再插入传入的对象

如果事务生效 那么new User("aa",2) 这个对象是插入不了的

反之则会插入

声明式事务的添加

导包

需要添加声明式事务就要在

applicationContext.xml 文件中配置 因为是AOP织入 所以需要导入对应的包

 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.17</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.17</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>

		<!--千万不能拉下-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.8</version>
        </dependency>
    </dependencies>

<!--maven导出问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources/</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

applicationContext.xml 头部依赖

导包以后在applicationContext.xml文件中修改头部信息,添加依赖:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">
    
</beans>

xmlns:tx依赖就是事务的依赖

配置Mybatis在Spring中的bean

然后配置:

DataSource:

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://192.168.117.100:3306/mybatis?useSSL=false"/>
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</bean>

SqlSessionFactory:

<bean id="sqlSeesionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="mybatis-config.xml"/>
</bean>

UserMapper:

<bean id="userMapper" class="cn.mianju.mapper.UserMapperImpl">
    <property name="sqlSessionFactory" ref="sqlSeesionFactory"/>
</bean>

配置声明式事务的Bean

transactionManager:

<!--    配置声明式事务 固定写法-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--        <constructor-arg ref="dataSource" />-->
        <property name="dataSource" ref="dataSource"/>
</bean>

可以通过构造方法来注入dataSource 也可以通过set 方法来注入

txAdvice:

<!--    tx通知 也就是对怎么样的 、哪一种方法添加事务-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
<!--            对含有add名字的方法添加事务-->
<!--            <tx:method name="add"/>-->

<!--            对所有方法添加事务-->
            <tx:method name="*"/>
            
        </tx:attributes>
    </tx:advice>

将什么模块添加到事务中去 在这里来声明

AOP织入:

<!--    利用aop将事务的通知织入进代码-->
<aop:config>
    <!--        切入点-->
    <aop:pointcut id="txPointcut" expression="execution(* cn.mianju.mapper.*.*(..))"/>
    <!--        aop 通知 :织入的是什么-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
</aop:config>

测试

import cn.mianju.mapper.UserMapper;
import cn.mianju.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
//        System.out.println(userMapper.query());
        userMapper.insert(new User("bb",2));

    }
}

如果没有添加事务 我们抛出了一个空指针异常 它会执行

getSqlSession().getMapper(UserMapper.class).insert(new User("aa",2));

这个方法 但是因为异常中断了就不会执行最后的

getSqlSession().getMapper(UserMapper.class).insert(user);

也就是数据库中添加了 name="aa",age=2 的但是没有添加 name="bb",age=2 的数据

但是添加了事务后 抛出了异常

我们会看见两个数据都没有加入进去

如果我们不抛出异常 那么两个数据都加入了数据库中

也就达成了我们事务的 要么都执行 要么都不执行

而且可以看到 我们在添加声明式事务的时候 全程操作的是applicationContext.xml 文件

并没有修改源代码 所以也符合我们的OOP原则

上一篇 下一篇