spring AOP开发和JDBC

本文介绍了Spring AOP的基础知识,包括AOP的概念、术语、动态代理技术和在Spring中的应用。详细讲解了JDK和CGLIB动态代理的实现,并通过示例展示了Spring AOP的XML和注解开发方式。此外,还阐述了JDBC Template的使用步骤和其在Spring框架中的重要性。最后,探讨了Spring的声明式事务管理,包括相关对象、配置和事务管理的优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


今天主要学习AOP的概念,简单地说,AOP将业务逻辑的各个部分进行隔离,采用横向抽取的机制,对模块间进行解耦,能有效提高开发效率。然后学习如何进行手动及spring进行AOP开发,利用spring进行AOP开发时,可以选择基于xml配置的方式或基于注释的方式实现AOP。基于注释的方式实现 AOP 的效果是最方便的方式,所以实际开发中推荐使用注解的方式。最后,基于昨天学习的配置数据源的基础上,学习了spring提供的JDBC Template的配置和基本CRUD操作的使用,JDBC模板可以对不同的数据源进行封装,并为用户提供了统一的大量的查询和更新数据库的方法,如 query()、update() 等,方便开发。

AOP简介

AOP相关概念

  • AOP 的全称是“Aspect Oriented Programming”,即面向切面编程,AOP和面向对象编程(OOP)类似,也是一种编程模
  • Spring AOP 是基于 AOP 编程模式的一个框架,它的使用有效减少了系统间的重复代码,达到了模块间的松耦合目的。它将业务逻辑的各个部分进行隔离,使开发人员在编写业务逻辑时可以专心于核心业务,从而提高了开发效率
  • AOP 采取横向抽取机制取代了传统纵向继承体系的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面
  • 目前最流行的 AOP 框架有两个,分别为 Spring AOP 和 AspectJ。Spring AOP 使用纯 Java 实现,不需要专门的编译过程和类加载器,在运行期间通过代理方式向目标类植入增强的代码。AspectJ 是一个基于 Java 语言的 AOP 框架,从 Spring 2.0 开始,Spring AOP 引入了对 AspectJ 的支持。AspectJ 扩展了 Java 语言,提供了一个专门的编译器,在编译时提供横向代码的植入
  • AOP作用及优势
    • 在程序运行期间,在不修改代码的情况下,对方法进行功能增强
    • 减少重复代码,提高开发效率,并且便于维护
  • AOP底层实现
    • AOP的底层是通过spring提供的动态代理技术实现的。在运行期间,spring通过动态代理技术动态地生成代理对象,代理对象方法执行时进行功能增强的介入,去调用目标对象的代方法,从而实现功能增强

AOP术语

为了更好地理解 AOP,就需要对 AOP 的相关术语有一些了解,这些专业术语主要包含 Joinpoint(spring中就是指方法)、Pointcut、Advice、Target、Weaving、Proxy 和 Aspect,它们的含义如下表所示:
在这里插入图片描述

AOP常用动态代理技术

  • JDK代理:基于接口的动态代理技术
  • cglib代理:基于父类的动态代理技术
    在这里插入图片描述

手写JDK动态代理示例

  • 创建目标接口并定义实现类
    package com.example.proxy.jdk;
    
    public interface TargetInterface {
        public void save();
    }
    
    // --------------------------------------
    package com.example.proxy.jdk;
    
    public class TargetInterfaceImpl implements TargetInterface{
        @Override
        public void save() {
            System.out.println("save running");
        }
    }
    
  • 定义增强类,类的方法为增强目标对象的方法
    package com.example.proxy.jdk;
    
    public class Advice {
        public void before(){
            System.out.println("前置增强");
        }
    
        public void after(){
            System.out.println("后置增强");
        }
    }
    
  • 定义测试类,调用jdk代理方法,代理目标对象,并进行方法增强
    package com.example.proxy.jdk;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JdkTest {
        public static void main(String[] args) {
            // 创建目标对象
            TargetInterfaceImpl target = new TargetInterfaceImpl();
            // 增强对象
            Advice advice = new Advice();
    		// 生成动态代理对象
            TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
                    target.getClass().getClassLoader(), // 目标对象加载器类
                    target.getClass().getInterfaces(),  // 与目标对象相同的接口字节码对象数组
                    new InvocationHandler() {
                        // 调用代理对象的任何方法,实质执行都是invoke方法
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            // 前置增强
                            advice.before();
                            Object ret = method.invoke(target, args);// 执行目标方法
                            // 后置增强
                            advice.after();
                            return ret;
                        }
                    }
            );
    
            // 调用代理对象的方法,实质调用的是invoke,所以具有前置、后置增强效果
            proxy.save();
        }
    }
    

手写cglib动态代理示例

  • 直接定义目标类,cglib是基于父类的,不需要像Jdk代理一样定义接口
    package com.example.proxy.cglib;
    
    
    public class Target {
        public void save() {
            System.out.println("save running");
        }
    }
    
  • 增强类与Jdk代理一致
  • 定义测试类,使用cglib代理方法,对目标进行增强
    package com.example.proxy.cglib;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    import java.lang.reflect.Method;
    
    public class CglibTest {
        public static void main(String[] args) {
            // 创建目标对象
            Target target = new Target();
            // 增强对象
            Advice advice = new Advice();
            // 基于cglib,生成动态代理对象
            // 1. 创建增强器
            Enhancer enhancer = new Enhancer();
            // 2. 设置父类(目标)
            enhancer.setSuperclass(Target.class);
            // 3. 设置回调
            enhancer.setCallback(new MethodInterceptor() {
                // 与jdk代理中的invoke类似
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    // 执行前置增强
                    advice.before();
                    // 执行目标
                    Object invoke = method.invoke(target, args);
                    // 执行后置增强
                    advice.after();
                    return invoke;
                }
            });
            // 4. 创建代理对象,cglib基于父类的,代理是Target的子类对象
            Target proxy = (Target) enhancer.create();
    
            // 调用增强后的方法
            proxy.save();
        }
    }
    

spring进行AOP开发事项

  • 需要编写的内容
    • 编写核心业务代码(目标类的目标方法)
    • 编写切面类,切面类中有通知(增强功能方法)
    • 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
  • AOP技术实现的内容
    • spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能切入,完成完整的代码逻辑运行。
  • AOP底层使用哪种代理方式
    • spring框架会根据目标类是否实现了接口来决定采用哪种动态代理方式

基于XML的AOP开发

切点表达式

  • 切点表达式语法execution([修饰符] 返回类型 包名.类名.方法名(参数))
    • 访问修饰符可以省略
    • 返回值类型、包名、类名、方法名可以使用*代表任意
    • 包名与类名之间一个点.代表当前包下的类,两个点… 代表当前包及其子包下的类
    • 参数列表可以使用两个点… 表示任意个数、任意类型的参数列表

通知配置语法及类型

在这里插入图片描述

开发步骤

  • 导入AOP相关坐标
  • 创建目标接口和目标类(内部须有切点)
  • 创建切面类(内部有增强方法)
  • 将目标类和切面类的对象创建权交给spring
  • 在applicationContext.xml文件中配置织入关系
  • 测试代码

开发示例

  • pom.xml导入AOP相关坐标,使用aspectj开发AOP
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.6</version>
    </dependency>
    
  • 创建目标接口和目标类
    package com.example.aop;
    
    // 目标接口
    public interface TargetInterface {
        public void save();
    }
    // ----------------------------------
    package com.example.aop;
    
    
    // 目标类
    public class Target{
        public void save() {
            System.out.println("save running");
        }
    }
    
  • 创建切面类(内部有增强方法)
    package com.example.aop;
    // 创建切面类
    public class MyAspect {
        public void before(){
            System.out.println("前置增强");
        }
    
        public void after(){
            System.out.println("后置增强");
        }
    }
    
  • 将目标类和切面类的对象创建权交给spring,需要导入aop命名空间,然后进行aop织入关系的配置
    <!--命名空间-->
    xmlns:aop="http://www.springframework.org/schema/aop"
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    
    
    <!--目标对象-->
    <bean id="target" class="com.example.aop.Target"></bean>
    
    <!--切面对象-->
    <bean id="myAspect" class="com.example.aop.MyAspect"></bean>
    
    <!--配置织入关系,告诉spring框架,哪些方法(切点)需要做哪些增强(通知)-->
    <aop:config>
        <!--声明切面-->
        <aop:aspect ref="myAspect">
            <!--配置切点,通配符,指定Target类所有方法都是切点-->
            <aop:pointcut id="pointCut" expression="execution(* com.example.aop.Target.*(..))"/>
            <!--切面 = 切点 + 通知-->
            <aop:before method="before" pointcut-ref="pointCut"/>
            <!--指定切点-->
            <!-- <aop:before method="before" pointcut="execution(public void com.example.aop.Target.save())"/> -->
            <aop:after method="after" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
    
  • 定义测试类,测试
    package com.example.aop;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class AopTest {
        // 要测试的目标对象
        @Autowired
        private Target target;
    
        @Test
        public void test(){
            target.save();
        }
    }
    /* 输出
    前置增强
    save running
    后置增强
    */
    

基于注解的AOP开发

  • 在 Spring 中,尽管使用 XML 配置文件可以实现 AOP 开发,但是如果所有的相关的配置都集中在配置文件中,势必会导致 XML 配置文件过于臃肿,从而给维护和升级带来一定的困难
  • 为此,AspectJ 框架为 AOP 开发提供了另一种开发方式——基于 Annotation 的声明式。AspectJ 允许使用注解定义切面、切入点和增强处理,而 Spring 框架则可以识别并根据这些注解生成 AOP 代理

AspectJ注解类型

在这里插入图片描述

开发步骤

  • 创建目标接口和目标类(内部有切点)
  • 创建切面类(内部有增强方法)
  • 将目标类和切面类的对象创建权交给spring
  • 在切面类中使用注解配置织入关系
  • 在配置文件中开启注解的组件扫描和AOP自动代理
  • 测试

开发示例

  • 创建目标接口或目标类,并标识@Component注解,将创建权交给spring容器
    package com.example.aopanno;
    import org.springframework.stereotype.Component;
    
    // 目标类
    @Component("target")
    public class Target{
        public void save() {
            int i = 1/0;    // 测试AOP异常通知
            System.out.println("save running");
        }
    }
    
  • 创建切面类,使用@Component和@Aspect注解,并在切面类中使用注解配置织入关系
    package com.example.aopanno;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    // 创建切面类
    @Component("myAspect")  // 加入spring容器
    @Aspect // 标示当前类是一个切面类
    public class MyAspect {
        // 定义切点表达式
        // 用于取代:<aop:pointcut id="pointCut" expression="execution(* com.example.aopanno.Target.*(..))"/>
        // 要求:方法必须是private,没有值,名称自定义,没有参数
        @Pointcut("execution(* com.example.aopanno.Target.*(..))")
        private void pointCut() {
        }
    
        // 前置通知
        @Before("pointCut()")
        public void myBefore(JoinPoint joinPoint) {
            System.out.print("前置通知,目标:");
            System.out.print(joinPoint.getTarget() + "方法名称:");
            System.out.println(joinPoint.getSignature().getName());
        }
    
        // 后置通知
        @AfterReturning(value = "pointCut()")
        public void myAfterReturning(JoinPoint joinPoint) {
            System.out.println("后置通知,方法名称:" + joinPoint.getSignature().getName());
        }
    
        // 环绕通知
        @Around("pointCut()")
        public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
                throws Throwable {
            System.out.println("环绕开始"); // 开始
            Object obj = proceedingJoinPoint.proceed(); // 执行当前目标方法
            System.out.println("环绕结束"); // 结束
            return obj;
        }
    
        // 异常通知
        @AfterThrowing(value = "pointCut()", throwing = "e")
        public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
            System.out.println("异常通知" + "出错了" + e.getMessage());
        }
    
        // 最终通知
        @After("pointCut()")
        public void myAfter() {
            System.out.println("最终通知");
        }
    }
    
  • 在配置文件中开启注解的组件扫描和AOP自动代理
    <!--开启组件扫描-->
    <context:component-scan base-package="com.example.aopanno"/>
    
    <!--开启AOP自动代理-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  • Junit测试
    package com.example.aopanno;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class AopAnnoTest {
        // 要测试的目标对象
        @Autowired
        private Target target;
    
        @Test
        public void test(){
            target.save();
        }
    }
    
    /* 测试结果
    环绕开始
    前置通知,目标:com.example.aopanno.Target@e4d2696方法名称:save
    异常通知出错了/ by zero
    最终通知
    */
    

JDBC Template

  • JDBC模板是spring框架提供的一个对象,是对原始繁琐的JDBC API对象的简单封装。spring框架为我们提供了很多的操作模板类。例如:操作关系型数据库的JDBC Template和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等
  • Spring 框架针对数据库开发中的应用提供了 JDBCTemplate 类,该类是 Spring 对 JDBC 支持的核心,它提供了所有对数据库操作功能的支持
  • Spring 框架提供的JDBC支持主要由四个包组成,分别是 core(核心包)、object(对象包)、dataSource(数据源包)和 support(支持包),org.springframework.jdbc.core.JdbcTemplate 类就包含在核心包中。作为 Spring JDBC 的核心,JdbcTemplate 类中包含了所有数据库操作的基本方法

JDBCTemplate开发步骤

  • 导入spring-jdbc和spring-tx坐标
  • 创建数据库和实体
  • 创建JDBC Template对象
  • 执行数据库操作

示例

  • 导入spring-jdbc和spring-tx坐标
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
    </dependency>
    
  • spring xml配置文件配置数据源对象和JDBC Template对象
    <!-- 导入properties文件,设置数据源对象 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源,setter注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  • 定义Acount类,与数据库所要操作的表格字段一致,用来接收查询结果
    package com.example.datasource.jdbc;
    
    public class Account {
        private String name;
        private double money;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public double getMoney() {
            return money;
        }
    
        public void setMoney(double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
  • 测试JDBC Template CRUD基本使用
    package com.example.datasource.jdbc;
    
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class JdbcTemplateTest {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Test
        public void testUpdate(){
            // 更新记录
            int row = jdbcTemplate.update("update account set money=? where name=?", 200, "张三");
            System.out.println(row);
        }
    
        @Test
        public void testDelete(){
            // 删除记录
            int row = jdbcTemplate.update("delete from account where name=?", "李四");
            System.out.println(row);
        }
    
        @Test
        public void testInsert(){
            // 插入记录
            int row = jdbcTemplate.update("insert into account values(?,?)", "Tony", 123.4);
            System.out.println(row);
        }
    
        @Test
        public void testQueryAll(){
            // 查询所有记录
            List<Account> accountList = jdbcTemplate.query("select * from account", new BeanPropertyRowMapper<Account>(Account.class));
            System.out.println(accountList);
        }
    
        @Test
        public void testQueryOne(){
            // 查询单条记录
            Account record = jdbcTemplate.queryForObject("select * from account where name=?", new BeanPropertyRowMapper<Account>(Account.class), "Tony");
            System.out.println(record);
        }
    
        @Test
        public void testQueryCount(){
            // 聚合查询,查询符合条件的记录数
            Long recordNum = jdbcTemplate.queryForObject("select count(*) from account where name=?", Long.class, "Tony");
            System.out.println(recordNum);
        }
    }
    
    

spring声明式事务管理

  • spring的声明式事务顾名思义就是采用声明的方式来处理事务。这里所指的声明,就是指在配置文件中声明,用在spring配置文件中声明式的处理事务来代替代码式的处理事务
  • Spring 的事务管理是基于 AOP 实现的,而 AOP 是以方法为单位的。Spring 的事务属性分别为传播行为、隔离级别、只读和超时属性,这些属性提供了事务应用的方法和描述策略
  • 在 Java EE 开发经常采用的分层模式中,Spring 的事务处理位于业务逻辑层,它提供了针对事务的解决方案
  • spring-tx是 Spring 提供的用于事务管理的依赖包,其中包括事务管理的三个核心接口:PlatformTransactionManagerTransactionDefinitionTransactionStatus
  • 声明式事务管理的作用
    • 事务管理不侵入开发的组件。具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理式属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
    • 在不需要事务管理的时候,只要在设定文件上修改,即可移去事务管理服务,无需改变代码重新编译,这样维护起来极其方便

事务管理相关对象

  • PlatformTransactionManager
    • PlatformTransactionManager 接口是 Spring 提供的平台事务管理器,用于管理事务。该接口中提供了三个事务操作方法,具体如下
      • TransactionStatus getTransaction(TransactionDefinition definition):用于获取事务状态信息
      • void commit(TransactionStatus status):用于提交事务
      • void rollback(TransactionStatus status):用于回滚事务
    • 在项目中,Spring 将 xml 中配置的事务详细信息封装到对象 TransactionDefinition 中,然后通过事务管理器的 getTransaction() 方法获得事务的状态(TransactionStatus),并对事务进行下一步的操作
  • TransactionDefinition
    • TransactionDefinition 接口是事务定义(描述)的对象,它提供了事务相关信息获取的方法,其中包括五个操作,具体如下
      • String getName():获取事务对象名称
      • int getIsolationLevel():获取事务的隔离级别
      • int getPropagationBehavior():获取事务的传播行为
      • int getTimeout():获取事务的超时时间
      • boolean isReadOnly():获取事务是否只读
    • 在上述五个方法的描述中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为的种类如下表所示
      在这里插入图片描述
      在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务。通常情况下,数据的查询不会改变原数据,所以不需要进行事务管理,而对于数据的增加、修改和删除等操作,必须进行事务管理
  • TransactionStatus
    • TransactionStatus 接口是事务的状态,它描述了某一时间点上事务的状态信息。其中包含六个操作,具体如下表所示
      在这里插入图片描述

基于XML的声明式事务管理

  • 配置要点
    • 平台事务管理器配置
    • 事务通知配置
    • 事务aop织入关系配置

示例

  • pom文件导入以下坐标
    	<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
        </dependency>
    
    	<dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
    	
    	<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
    
    	<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
        </dependency>
    
  • 定义Account类,与要操作的数据库表对应
    package com.example.tx.domain;
    
    
    public class Account {
        private String name;
        private double money;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public double getMoney() {
            return money;
        }
    
        public void setMoney(double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
  • 定义dao层接口,并定义其对应实现类,定义方法模拟转账操作中付款,收款操作
    package com.example.tx.dao;
    
    public interface AccountDao {
        // 收款
        public void in(String inUser, double money);
    
        // 付款
        public void out(String outUser, double money);
    }
    // -----------------------------------------------
    
    package com.example.tx.dao.impl;
    
    import com.example.tx.dao.AccountDao;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    public class AccoutDaoImpl implements AccountDao {
        // 依赖Jdbc模板
        private JdbcTemplate jdbcTemplate;
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
        
        // 收款实现方法
        @Override
        public void in(String inUser, double money) {
            jdbcTemplate.update("update account set money=money+? where name=?", money, inUser);
        }
    
        // 付款实现方法
        @Override
        public void out(String outUser, double money) {
            jdbcTemplate.update("update account set money=money-? where name=?", money, outUser);
        }
    }
    
  • 定义service层接口,定义其对应实现类,定义交易操作方法,同时该方法作为切点,利用AOP进行方法增强(事务管理)
    package com.example.tx.service;
    
    public interface AccountService {
        // 转账方法
        public void transfer(String inUser, String outUser, double money);
    }
    // -----------------------------------------------
    package com.example.tx.service.impl;
    
    import com.example.tx.dao.AccountDao;
    import com.example.tx.service.AccountService;
    
    public class AccountServiceImpl implements AccountService {
    
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao){
            this.accountDao = accountDao;
        }
    
        // 转账方法实现,将此方法看成切点,执行前进行开启事务增强,执行后进行提交事务增强
        @Override
        public void transfer(String inUser, String outUser, double money) {
            accountDao.out(outUser, money);
            int i = 1/0; // 模拟断电等异常情况,需进行事务回滚
            accountDao.in(inUser, money);
        }
    }
    
  • xml配置
    • 导入aop和tx命名空间
      xmlns:aop="http://www.springframework.org/schema/aop"
      xmlns:tx="http://www.springframework.org/schema/tx"
      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
      
    • 数据源对象和Jdbc模板配置
      <!-- 导入properties文件,设置数据源对象 -->
      <context:property-placeholder location="classpath:jdbc.properties"/>
      <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
          <property name="driverClass" value="${jdbc.driver}"/>
          <property name="jdbcUrl" value="${jdbc.url}"/>
          <property name="user" value="${jdbc.username}"/>
          <property name="password" value="${jdbc.password}"/>
      </bean>
      
      <!--配置JDBC模板-->
      <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
          <!--默认必须使用数据源,setter注入数据源-->
          <property name="dataSource" ref="dataSource"/>
      </bean>
      
    • Bean容器对象配置
      <!--配置AccountDao-->
      <bean id="accountDao" class="com.example.tx.dao.impl.AccoutDaoImpl">
          <property name="jdbcTemplate" ref="jdbcTemplate"></property>
      </bean>
      <!--配置AccountService,注入accountDao依赖-->
      <bean id="accountService" class="com.example.tx.service.impl.AccountServiceImpl">
          <property name="accountDao" ref="accountDao"></property>
      </bean>
      
    • 平台事务管理器配置
      <!-- 事务管理器,依赖于数据源 -->
      <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
          <property name="dataSource" ref="dataSource" />
      </bean>
      
    • 事务通知配置
      <!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
      <tx:advice id="txAdvice" transaction-manager="txManager">
          <tx:attributes>
              <!-- 给切入点方法添加事务详情,name表示方法名称,*表示任意方法名称,propagation用于设置传播行为,read-only表示隔离级别,是否只读 -->
              <!--对find开头的切点方法设置事务增强属性-->
              <!--<tx:method name="find*" propagation="SUPPORTS" rollback-for="Exception" read-only="true"/>-->
              <!--单独对transfer方法设置事务增强属性-->
              <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception" read-only="false"/>
              <!--配置对所有切点方法事务增强属性-->
              <!--<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false" />-->
          </tx:attributes>
      </tx:advice>
      
    • 事务aop织入关系配置
      <!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
      <aop:config>
          <!-- 配置切入点为service.impl包下所有类的方法 -->
          <aop:pointcut expression="execution(* com.example.tx.service.impl.*.*(..))" id="txPointCut" />
          <!-- 切面:将切入点与通知整合 -->
          <aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
      </aop:config>
      
  • 定义controller层,模拟事务操作
    package com.example.tx.controller;
    
    import com.example.tx.service.AccountService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AccountController {
        // 模拟控制层,测试转账事务管理
        public static void main(String[] args) {
            ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
            AccountService as = app.getBean(AccountService.class);
            as.transfer("Tony", "tom", 100);
        }
    }
    

基于注解的声明式事务管理

  • 对于基于注解的声明式事务管理,与原来的xml配置文件相比,只修改了事务管理器部分,新添加并注册了事务管理器的驱动
    <!-- 导入properties文件,设置数据源对象 -->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="jdbcUrl" value="${jdbc.url}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    
    <!--配置JDBC模板-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--默认必须使用数据源,setter注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!--配置AccountDao-->
    <bean id="accountDao" class="com.example.tx.anno.dao.impl.AccoutDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--配置AccountService,注入accountDao依赖-->
    <bean id="accountService" class="com.example.tx.anno.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    
    <!-- 事务管理器,依赖于数据源 -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    
    <!--注册事务管理驱动,使用注解开发时使用-->
    <tx:annotation-driven transaction-manager="txManager"></tx:annotation-driven>
    
  • 定义基于注解的service实现类
    package com.example.tx.anno.service.impl;
    
    import com.example.tx.anno.dao.AccountDao;
    import com.example.tx.anno.service.AccountService;
    import org.springframework.transaction.annotation.Isolation;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    //@Transactional(isolation = Isolation.READ_COMMITTED)  // 指定整个类的切点方法,事务管理的隔离级别
    public class AccountServiceImpl implements AccountService {
    
        private AccountDao accountDao;
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        // 转账方法实现,将此方法看成切点,执行前进行开启事务增强,执行后进行提交事务增强
        /*
        <tx:advice id="txAdvice" transaction-manager="txManager">
            <tx:attributes>
                <!--单独对transfer方法设置事务增强属性-->
                <tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT" rollback-for="Exception" read-only="false"/>
            </tx:attributes>
        </tx:advice>
        */
    
        // 相当于
        @Transactional(isolation = Isolation.DEFAULT, rollbackFor = Exception.class,
                readOnly = false, propagation = Propagation.REQUIRED)
        @Override
        public void transfer(String inUser, String outUser, double money) {
            accountDao.out(outUser, money);
            int i = 1/0; // 模拟断电等异常情况,需进行事务回滚
            accountDao.in(inUser, money);
        }
    }
    
  • 测试
    package com.example.tx.anno.controller;
    
    import com.example.tx.anno.service.AccountService;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("classpath:applicationContext.xml")
    public class AccountController {
        @Autowired
        private AccountService accountService;
    
        // 模拟控制层,测试转账事务管理
        @Test
        public void test() {
            accountService.transfer("Tony", "tom", 100);
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值