Spring AOP讲解

大家好,我是一个爱举铁的程序员Shr。

 

本文介绍Spring框架中AOP(Aspect Oriented Programming面向切面编程)的概念。阅读本篇文章可能需要30分钟。

 

Spring使用AspectJ实现了AOP,提供XML配置方式注解配置方式

 

AOP中用到了代理模式。

 

先讲几个概念。

参考:https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-introduction-defn

 

Aspect(切面):横切关注点的抽象。切入点通知形成了切面

 

Join point(连接点):被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点,实际上Join point还可以是field或类构造器。

 

Advice(通知):拦截到Join point之后要做的事情就是通知。通知分为前置通知,后置通知,异常通知,最终通知,环绕通知。用来区别在Joint point之前、之后执行代码。

 

Pointcut(切入点):切入点是指对连接点进行拦截的定义。指定一个规则,拦截哪些方法。

 

Introduction(引入):不修改类代码的前提下,在运行期间为类动态地添加一些方法或字段。

 

Target object(目标对象):代理的目标对象。

 

AOP proxy(AOP代理):由AOP框架创建的对象。AOP代理是JDK动态代理或CGLIB代理。

 

Weaving(编织):将aspects应用到target对象并导致proxy对象创建的过程。

 

在上面几个概念中。

连接点可以直接理解成方法。

切入点就是拦截这些方法的规则。

通知就是拦截到方法后所做的事情。

切面就是切入点通知,因为事情可能是在被拦截到的方法之前做的或者之后做的。

 

接下来通过一段代码来更深刻得了解一下这些概念。

 

一、用代码了解AOP的概念

1.1 创建实体类Student

package com.shrmus.spring.aop.pojo;

public class Student {
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

 

1.2 目标对象

1.2.1 接口

package com.shrmus.spring.aop.service;

public interface StudentService {
    public void addStudent();
    public void getStudentList();
    public void updateStudent();
}

 

1.2.2 实现类

package com.shrmus.spring.aop.service.impl;

import com.shrmus.spring.aop.service.StudentService;

/**
 * Target object 目标对象
 * <p>Title:StudentServiceImpl</p>
 * <p>Description:</p>
 * @author Shr
 * @date 2018年7月31日上午1:09:48
 * @version
 */
public class StudentServiceImpl implements StudentService{

    /**
     * Joint point 连接点
     * 添加一个学生的信息
     */
    public void addStudent() {
        System.out.println("addStudent()");
    }

    /**
     * Joint point 连接点
     * 查找所有学生信息
     */
    public void getStudentList() {
        System.out.println("getStudentList()");
    }

    /**
     * Joint point 连接点
     * 修改学生信息
     */
    public void updateStudent() {
        System.out.println("updateStudent()");
    }
}

 

1.3 通知

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-advice-types实现一个通知。

package com.shrmus.spring.aop.advice;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

/**
 * Advice 通知
 * <p>Title:TxAdvice</p>
 * <p>Description:</p>
 * @author Shr
 * @date 2018年7月31日上午1:31:32
 * @version
 */
public class MyAdvice implements MethodInterceptor{
    /**
     * 在被拦截到的方法前后做相应的操作,也就是前置通知,后置通知等
     * @param invocation 在方法调用的时候截取程序
     */
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        System.out.println("调用方法前....");
        System.out.println("调用方法:" + invocation.getMethod().getName());
        // 获取正在调用的方法
        Object proceed = invocation.proceed();
        System.out.println("调用方法后...");
        return proceed;
    }
}

 

这里值得一提的是方法拦截器org.aopalliance.intercept.MethodInterceptororg.aopalliance.aop.Advice的子接口。

 

而方法调用org.aopalliance.intercept.MethodInvocationorg.aopalliance.intercept.Joinpoint的子接口。

 

如果是单独实现前置通知或者后置通知等,则需要实现相应的接口,如前置通知的接口org.springframework.aop.MethodBeforeAdvice。具体请参考文档。

 

1.4 测试类

package com.shrmus.spring.aop.test;

import org.springframework.aop.Advisor;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;

import com.shrmus.spring.aop.advice.MyAdvice;
import com.shrmus.spring.aop.service.StudentService;
import com.shrmus.spring.aop.service.impl.StudentServiceImpl;

/**
 * 测试类
 * <p>Title:AOPTest</p>
 * <p>Description:</p>
 * @author Shr
 * @date 2018年7月31日上午4:43:08
 * @version
 */
public class AOPTest {
    public static void main(String[] args) {
        // 1、Pointcut 切入点,指定一个规则,拦截哪些方法。
        JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
        pointcut.setPatterns(".*add.*",".*update.*");

//        NameMatchMethodPointcut pointcut = new NameMatchMethodPointcut();
//        pointcut.addMethodName("add*");
//        pointcut.addMethodName("update*");

//        // 2、Advice 通知,表示对拦截到的方法加上新功能(如日志,事务等)
        MyAdvice advice = new MyAdvice();
//
//        // 3、Pointcut 切入点 + Advice 通知 = Aspect 切面(横切关注点)
        Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);

        // 4、AOP proxy AOP代理
        // 创建目标对象
        StudentService studentService = new StudentServiceImpl();
        ProxyFactory proxyFactory = new ProxyFactory(studentService);
        // 添加通知或切面,可以添加多个
//        proxyFactory.addAdvice(advice);
        proxyFactory.addAdvisor(advisor);
        // 生成代理对象
        StudentService studentServiceProxy = (StudentService) proxyFactory.getProxy(); 

        // 5、使用代理对象
        studentServiceProxy.addStudent();
        System.out.println();

        studentServiceProxy.getStudentList();
        System.out.println();

        studentServiceProxy.updateStudent();
    }
}

 

第一步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-pointcuts-regex设置一个Pointcut(切入点),添加拦截方法的规则。另外在注释中还有另一种方式添加切入点。

 

第二步

实例化一个通知对象。

 

第三步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-api-advisor实例化一个切面。

 

第四步

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-prog生成代理对象。

 

第五步

使用代理对象调用方法。

 

控制台输出:

调用方法前....

调用方法:addStudent

addStudent()

调用方法后...

 

getStudentList()

 

调用方法前....

调用方法:updateStudent

updateStudent()

调用方法后...

 

AOP的几个概念就讲到这里了,AOP常用作记录日志,事务管理等。

 

二、在Spring配置文件中的配置

2.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"
    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-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">   

</beans>

 

引入aop和tx的命名空间,这样就能使用aop和tx开头的标签了。

 

2.2 切入点表达式

参考官方文档https://docs.spring.io/spring/docs/4.3.17.RELEASE/spring-framework-reference/htmlsingle/#aop-pointcuts-examples设置拦截方法的规则。

 

拦截所有public修饰的方法:

execution(public * *(..))

拦截任何set开头的方法:

execution(* set*(..))

拦截com.shrmus.spring.aop.service.impl包下的所有方法:

execution(* com.shrmus.spring.aop.service.impl.*.*(..))

拦截com.shrmus.spring.aop包及其子包下的所有方法:

execution(* com.shrmus.spring.aop..*.*(..))

 

公式就是:execution(修饰符 返回值类型 类的全限定名.方法名(参数))

 

返回值类型可不写。

 

2.3 配置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:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">   

    <!-- 设置自动扫描包和扫描bean,扫描包和子包下的类,多个包用逗号隔开 -->
    <!-- <context:component-scan base-package="com.shrmus.spring.ioc"></context:component-scan> -->

    <bean id="studentService" class="com.shrmus.spring.aop.service.impl.StudentServiceImpl"></bean>

    <!-- 配置通知对象 -->
    <bean id="myAdvice" class="com.shrmus.spring.aop.advice.MyAdvice"></bean>

    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.shrmus.spring.aop.service.impl.StudentServiceImpl.*(..))" id="myPointcut"/>

        <!-- 配置切面 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut"/>

        <!-- 配置通知类型 -->
        <aop:aspect ref="myAdvice">
            <!-- 前置通知 ,在方法执行前运行-->
            <aop:before method="before" pointcut-ref="myPointcut"/>

            <!-- 环绕通知,可以控制方法的执行-->
            <aop:around method="around" pointcut-ref="myPointcut"/>

            <!-- 异常通知,在匹配方法抛出异常退出时执行-->
            <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut"/>

            <!-- 最终通知,在匹配方法退出后执行,不论一个方法是如何结束的,最终通知都会运行-->
            <aop:after method="after" pointcut-ref="myPointcut"/>

            <!-- 后置通知,在方法完全执行后运行-->
            <aop:after-returning method="afterReturning" pointcut-ref="myPointcut"/>
        </aop:aspect>

    </aop:config>

</beans>

 

2.4 修改MyAdvice类

在com.shrmus.spring.aop.advice.MyAdvice类中添加几个通知类型的方法。

    public void before() {
        System.out.println("前置通知...");
    }

    public void around() {
        System.out.println("环绕通知...");
    }

    public void after() {
        System.out.println("最终通知...");
    }

    public void afterReturning() {
        System.out.println("后置通知...");
    }

    public void afterThrowing() {
        System.out.println("异常通知...");
    }

 

2.5 测试类

package com.shrmus.spring.aop.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.shrmus.spring.aop.service.StudentService;

public class StudentTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-aop.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");

        userService.addStudent();
        System.out.println();

        userService.getStudentList();
        System.out.println();

        userService.updateStudent(); 
    }
}

 

控制台打印结果:

调用方法前....

调用方法:addStudent

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:getStudentList

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:updateStudent

前置通知...

环绕通知...

最终通知...

后置通知...

调用方法后...

 

2.6 修改配置文件

可以看到控制台没有再打印调用方法时,方法内部打印的语句了,是因为配置了环绕通知。把配置文件中配置环绕通知的语句注释掉。

<!-- 环绕通知,可以控制方法的执行-->
<!-- <aop:around method="around" pointcut-ref="myPointcut"/> -->

 

2.7 修改目标对象

目标对象是com.shrmus.spring.aop.service.impl.StudentServiceImpl,现在我们要测试异常通知。修改getStudentList()方法。

    /**
     * Joint point 连接点
     * 查找所有学生信息
     */
    public void getStudentList() {
        System.out.println("getStudentList()");
        int i = 1/0;
    }

 

2.8 再次测试

测试类的代码不需要修改,直接运行,控制台打印结果:

调用方法前....

调用方法:addStudent

前置通知...

addStudent()

最终通知...

后置通知...

调用方法后...

 

调用方法前....

调用方法:getStudentList

前置通知...

getStudentList()

异常通知...

最终通知...

Exception in thread "main" java.lang.ArithmeticException: / by zero

 

可以看到注释掉环绕通知后,被调用的方法内部的语句打印出来了。

 

而第二个被调用的方法出现异常之后没有再调用后置通知。

 

这些就是通知的类型了。

 

三、事务

前面说过,AOP常用于日志记录和事务处理,接下来讲一讲事务处理。

 

3.1 数据库

CREATE TABLE `jdbc01_student` (
  `stu_id` int(11) NOT NULL auto_increment,
  `stu_name` varchar(20) default NULL,
  `stu_age` int(11) default NULL,
  `stu_gender` int(11) default NULL,
  PRIMARY KEY  (`stu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

3.2 实体类

package com.shrmus.spring.tx.pojo;

import java.io.Serializable;

public class Student implements Serializable{
    private int id;
    private String name;
    private int age;
    private int gender;

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + ", gender=" + gender + "]";
    }

    public Student() {

    }

    public Student(String name, int age, int gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public Student(int id, String name, int age, int gender) {
        super();
        this.id = id;
        this.name = name;
        this.age = age;
        this.gender = gender;
    }

    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public int getGender() {
        return gender;
    }
    public void setGender(int gender) {
        this.gender = gender;
    }
}

 

3.3 数据源文件

jdbc.url=jdbc:mysql://localhost:3306/design-pattern-20180602?characterEncoding=utf8
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=shrmus

 

3.4 配置文件

配置文件名是applicationContext-tx.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:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">

    <!-- 加载数据库属性配置文件 -->
    <context:property-placeholder location="classpath:dbconfig.properties" ignore-unresolvable="true" />

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 数据库基本信息配置 -->
        <property name="driverClassName" value="${jdbc.driverClassName}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

    <!-- 配置Jdbc模板 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- <bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        装配数据源
        <constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
    </bean> -->

    <!-- 注入 -->
    <bean id="studentDao" class="com.shrmus.spring.tx.dao.impl.StudentDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>

    <bean id="studentService" class="com.shrmus.spring.tx.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"></property>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!-- 配置通知属性 -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="add*" propagation="REQUIRED" />
            <tx:method name="update*" propagation="REQUIRED" />
            <tx:method name="get*" propagation="REQUIRED" read-only="true" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut expression="execution(* com.shrmus.spring.tx.service.impl.StudentServiceImpl.*(..))" id="myPointcut" />
        <!-- 配置切面 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="myPointcut" />
    </aop:config>

</beans>

 

3.5 Dao

3.5.1 接口

package com.shrmus.spring.tx.dao;

import java.util.List;

import com.shrmus.spring.tx.pojo.Student;

public interface StudentDao {
    public List<Student> getStudentList();
    public void addStudent(Student student);
}

 

3.5.2 实现类

package com.shrmus.spring.tx.dao.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.shrmus.spring.tx.dao.StudentDao;
import com.shrmus.spring.tx.pojo.Student;

/**
 * 
 * <p>Title:StudentDao</p>
 * <p>Description:</p>
 * @author Shr
 * @date 2018年7月31日下午7:12:54
 * @version
 */
public class StudentDaoImpl implements StudentDao{
    private JdbcTemplate jdbcTemplate;

    /**
     * 查找所有学生
     * @return
     */
    public List<Student> getStudentList(){
        String sql = "select stu_id,stu_name,stu_age,stu_gender from jdbc01_student";

        List<Student> studentList = jdbcTemplate.query(sql, new RowMapper<Student>() {
            @Override
            public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
                Integer id = Integer.parseInt(rs.getString(1));
                String name = rs.getString(2);
                Integer age = Integer.parseInt(rs.getString(3));
                Integer gender = Integer.parseInt(rs.getString(4));
                return new Student(id,name,age,gender);
            }
        });
        return studentList;
    }

    /**
     * 添加一条记录
     * @param student
     * @return
     */
    public void addStudent(Student student) {
        String sql = "insert into jdbc01_student(stu_name,stu_age,stu_gender) "
                + "values(?,?,?)";
        jdbcTemplate.update(sql, student.getName(),+student.getAge(),student.getGender());
    }

    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
}

 

3.6 Service

3.6.1 接口

package com.shrmus.spring.tx.service;

import java.util.List;

import com.shrmus.spring.tx.pojo.Student;

public interface StudentService {
    public void addStudent(Student student);
    public List<Student> getStudentList();
    public void updateStudent();
}

 

3.6.2 实现类

package com.shrmus.spring.tx.service.impl;

import java.util.List;

import com.shrmus.spring.tx.service.StudentService;
import com.shrmus.spring.tx.dao.StudentDao;
import com.shrmus.spring.tx.pojo.Student;

/**
 * 事务
 * <p>Title:StudentServiceImpl</p>
 * <p>Description:</p>
 * @author Shr
 * @date 2018年7月31日下午7:13:13
 * @version
 */
public class StudentServiceImpl implements StudentService{
    private StudentDao studentDao;

    /**
     * 添加一个学生的信息
     */
    public void addStudent(Student student) {
        studentDao.addStudent(student);
    }

    /**
     * 查找所有学生信息
     */
    public List<Student> getStudentList() {
        List<Student> studentList = studentDao.getStudentList();
        return studentList;
    }

    /**
     * 修改学生信息
     */
    public void updateStudent() {
        // TODO 待实现
    }

    public StudentDao getStudentDao() {
        return studentDao;
    }

    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
}

 

3.7 测试类

package com.shrmus.spring.tx.test;

import java.util.List;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.shrmus.spring.tx.pojo.Student;
import com.shrmus.spring.tx.service.StudentService;

public class StudentTest {

    @Test
    public void addStudent() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        Student student = new Student("hehe",20,1);
        userService.addStudent(student);
    }

    @Test
    public void getStudentList() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        List<Student> studentList = userService.getStudentList();
        System.out.println(studentList);
    }
}

 

3.8 验证结果

3.8.1 验证查找

先来看看数据库原来的数据

只有两条数据。

 

执行测试类中的getStudentList()方法,控制台打印结果:

[Student [id=1, name=张三, age=20, gender=1], Student [id=2, name=李四, age=33, gender=0]]

 

查找是没有问题的。

 

3.8.2 验证添加

执行测试类中的addStudent()方法,控制台是没有打印结果的,让我们看看数据库。

数据库中添加了一条数据,说明添加也没问题。

 

但是我们要验证事务是否正常。

 

3.8.3 验证事务管理

在Service的实现类中修改addStudent(Student student)方法。

    public void addStudent(Student student) {
        studentDao.addStudent(student);
        int i = 1/0;
    }

 

还是用原来的方法制造一个异常,然后执行测试类中的addStudent()方法。

 

再查看数据库。

还是3条数据。

 

把制造异常的那条语句注释掉。

        // int i = 1/0;

 

修改测试类的addStudent()方法中的数据。

    @Test
    public void addStudent() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext-tx.xml");
        StudentService userService = (StudentService) ac.getBean("studentService");
        Student student = new Student("haha",22,1);
        userService.addStudent(student);
    }

 

执行之后再看数据库。

 

可以看到主键已经不是连续自增的了,就是刚刚那个异常导致添加数据失败。

好了,事务管理就到这里了。

 

总结

首先感谢你能看到总结,说明你还是有耐心的嘛,哈哈哈。

这篇文章主要是回顾之前学Spring的时候懵逼的知识,本人之前在学Spring的时候,配置文件都是直接复制粘贴,什么意思都不明白,现在有点时间再慢慢消化这些知识。

在做事务处理中用到了JdbcTemplate,之前没学过这个,只是听说过,正好在官方文档看到了这个东西,又看了一下。

Spring的IoC和AOP就结束了,如果后面又看了新东西,会继续补充。

 

AOP源代码:

https://github.com/ShrMus/Design-Pattern/tree/master/design-pattern-20180602/src/main/java/com/shrmus/spring/aop

 

事务管理源代码:

https://github.com/ShrMus/Design-Pattern/tree/master/design-pattern-20180602/src/main/java/com/shrmus/spring/tx

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值