Spring二:AOP和事务

1 AOP

1.1 概述

什么是 AOP

  1. AOP全称是Aspect-Oriented Programming,即面向切面编程。
  2. AOP采取横向抽取机制,将分散在各个方法中的代码提取出来,然后在程序编译或运行时,再将这些提取取来的代码用到需要执行的地方。
  3. AOP是一种新的编程思想,也是spring框架中的一个重要内容。它是OOP的延伸和补充,而不是其替代品。
  4. 利用AOP可以对业务逻辑的各个部分进行分离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提升开发效率。
  5. 目前流行的AOP框架有两个,分别为 Spring AOP 和 AspectJ【重点介绍】。

AOP的作用和优势

  1. 作用:在程序运行期间,不修改源码对已有方法进行增强处理。
  2. 优势:减少重复代码;提高开发效率;方便维护等。

AOP相关术语

  1. Joinpoint(连接点):指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。
  2. Pointcut(切入点):指我们要对哪些Joinpoint进行拦截的定义。
  3. Advice(通知/增强):指拦截到Joinpoint之后所要做的事情就是通知。
    • 通知的类型有:前置通知,后置通知,异常通知,最终通知,环绕通知。
  4. Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
  5. Target(目标对象):代理的目标对象。
  6. Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。
    • 补充:spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。[了解即可]
  7. Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
  8. Aspect(切面):是切入点和通知(引介)的结合。

AOP的实现方式

  • AOP的实现方式为动态代理技术。动态代理简介如下
  1. 特点:字节码随用随创建,随用随加载。
  2. 作用:不修改源码的基础上对方法增强。
  3. 分类:基于接口的动态代理(JDK动态代理)和基于子类的动态代理(CGLIB动态代理)。

说明

  • 新版本Spring建议使用AspectJ来开发AOP,Spring AOP 了解即可,下面的代码演示均基于 AspectJ 开发。

1.2 xml方式

  • 普通maven工程,结构图如下

    在这里插入图片描述

  1. 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    
  2. 模拟持久层

    package cn.dao;
    
    public interface UserDao {
        public void addUser();
        public void deleteUser();
    }
    
    package cn.dao.impl;
    
    import cn.dao.UserDao;
    
    public class UserDaoImpl implements UserDao {
        public void addUser() {
            // 模拟异常(用于异常通知)
    //        int i = 10 / 0;
            System.out.println("*** userDao操作:添加用户 ***");
        }
    
        public void deleteUser() {
            System.out.println("*** userDao操作:删除用户 ***");
        }
    }
    
  3. 切面

    package cn.aop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    public class MyAspect {
    
        // 前置通知
        public void myBefore(JoinPoint joinPoint) {
            System.out.print("前置通知 :模拟执行权限检查...,");
            System.out.print("目标类是:" + joinPoint.getTarget());
            System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        }
    
        // 后置通知
        public void myAfterReturning(JoinPoint joinPoint) {
            System.out.print("后置通知:模拟记录日志...,");
            System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        }
    
        /**
         * 环绕通知
         * ProceedingJoinPoint 是JoinPoint子接口,表示可以执行目标方法
         * 1.必须是Object类型的返回值
         * 2.必须接收一个参数,类型为ProceedingJoinPoint
         * 3.必须throws Throwable
         */
        public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
                throws Throwable {
            // 开始
            System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
            // 执行当前目标方法
            Object obj = proceedingJoinPoint.proceed();
            // 结束
            System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
            return obj;
        }
    
        // 异常通知
        public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
            System.out.println("异常通知:" + "出错了" + e.getMessage());
        }
    
        // 最终通知
        public void myAfter() {
            System.out.println("最终通知:模拟方法结束后的释放资源...");
        }
    }
    
  4. 配置

    <?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">
        <!-- 1 目标类 -->
        <bean id="userDao" class="cn.dao.impl.UserDaoImpl"/>
        <!-- 2 切面 -->
        <bean id="myAspect" class="cn.aop.MyAspect"/>
        <!-- 3 aop编程 -->
        <aop:config>
            <!-- 配置切面 -->
            <aop:aspect ref="myAspect">
                <!-- 3.1 配置切入点,通知最后增强哪些方法 -->
                <!-- 含义:返回类型 包名.类名.方法名.(方法的参数) -->
                <!-- * 表示任意(注:返回类型与包名之间有一个空格),(..)表示任意参数 -->
                <aop:pointcut id="myPointCut" expression="execution(* cn.dao.*.*(..))"/>
                <!-- 3.2 关联通知Advice和切入点pointCut -->
                <!-- 3.2.1 前置通知 -->
                <aop:before method="myBefore" pointcut-ref="myPointCut"/>
                <!-- 3.2.2 后置通知,在方法返回之后执行,就可以获得返回值;returning属性:用于设置后置通知的第二个参数的名称,类型是Object -->
                <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal"/>
                <!-- 3.2.3 环绕通知 -->
                <aop:around method="myAround" pointcut-ref="myPointCut"/>
                <!-- 3.2.4 抛出通知:用于处理程序发生异常-->
                <!-- * 注:如果程序没有异常,将不会执行增强;throwing属性:用于设置通知第二个参数的名称,类型Throwable -->
                <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
                <!-- 3.2.5 最终通知:无论程序发生任何事情,都将执行 -->
                <aop:after method="myAfter" pointcut-ref="myPointCut"/>
            </aop:aspect>
        </aop:config>
    </beans>
    
  5. 运行

    package cn;
    
    import cn.dao.UserDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class App {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
            UserDao userDao = (UserDao) applicationContext.getBean("userDao");
            userDao.addUser();
    
            /**
             * 结果
             * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@1722011b,被织入增强处理的目标方法为:addUser
             * 环绕开始:执行目标方法之前,模拟开启事务...
             * *** userDao操作:添加用户 ***
             * 最终通知:模拟方法结束后的释放资源...
             * 环绕结束:执行目标方法之后,模拟关闭事务...
             * 后置通知:模拟记录日志...,被织入增强处理的目标方法为:addUser
             */
    
            /**
             * 异常结果 (开启 cn.dao.impl.UserDaoImpl#addUser() 中的 int i = 10 / 0;)
             * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@1722011b,被织入增强处理的目标方法为:addUser
             * 环绕开始:执行目标方法之前,模拟开启事务...
             * 最终通知:模拟方法结束后的释放资源...
             * 异常通知:出错了/ by zero
             */
        }
    }
    

1.3 annotation方式

  • 普通maven工程,结构图如下

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RsvawwQE-1638513463474)(assets/image-20211116220823927.png)]

  1. 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    
  2. 模拟持久层

    package cn.dao;
    
    public interface UserDao {
        public void addUser();
        public void deleteUser();
    }
    
    package cn.dao.impl;
    
    import cn.dao.UserDao;
    import org.springframework.stereotype.Repository;
    
    @Repository("userDao")
    public class UserDaoImpl implements UserDao {
        public void addUser() {
            // 模拟异常(用于异常通知)
    //        int i = 10 / 0;
            System.out.println("*** userDao操作:添加用户 ***");
        }
    
        public void deleteUser() {
            System.out.println("*** userDao操作:删除用户 ***");
        }
    }
    
  3. 切面

    package cn.aop;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    
    @Aspect
    @Component
    public class MyAspect {
    
        // 定义切入点表达式
        @Pointcut("execution(* cn.dao.*.*(..))")
        // 使用一个返回值为void、方法体为空的方法来命名切入点
        private void myPointCut() {
        }
    
        // 前置通知
        @Before("myPointCut()")  //不要忘记写括号
        public void myBefore(JoinPoint joinPoint) {
            System.out.print("前置通知 :模拟执行权限检查...,");
            System.out.print("目标类是:" + joinPoint.getTarget());
            System.out.println(",被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        }
    
        // 后置通知
        @AfterReturning(value = "myPointCut()")
        public void myAfterReturning(JoinPoint joinPoint) {
            System.out.print("后置通知:模拟记录日志...,");
            System.out.println("被织入增强处理的目标方法为:" + joinPoint.getSignature().getName());
        }
    
        // 环绕通知
        @Around("myPointCut()")
        public Object myAround(ProceedingJoinPoint proceedingJoinPoint)
                throws Throwable {
            // 开始
            System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
            // 执行当前目标方法
            Object obj = proceedingJoinPoint.proceed();
            // 结束
            System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
            return obj;
        }
    
        // 异常通知
        @AfterThrowing(value = "myPointCut()", throwing = "e")
        public void myAfterThrowing(JoinPoint joinPoint, Throwable e) {
            System.out.println("异常通知:" + "出错了" + e.getMessage());
        }
    
        // 最终通知
        @After("myPointCut()")
        public void myAfter() {
            System.out.println("最终通知:模拟方法结束后的释放资源...");
        }
    }
    
  4. 配置

    package cn.conf;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan("cn")
    @EnableAspectJAutoProxy  //对应xml中<aop:aspectj-autoproxy/>,即配置spring开启注解AOP的支持
    public class SpringConf {
        
    }
    
  5. 运行

    package cn;
    
    import cn.conf.SpringConf;
    import cn.dao.UserDao;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    public class App {
        public static void main(String[] args) {
            ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConf.class);
            UserDao userDao = (UserDao) applicationContext.getBean("userDao");
            userDao.addUser();
    
            /**
             * 结果
             * 环绕开始:执行目标方法之前,模拟开启事务...
             * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@22f31dec,被织入增强处理的目标方法为:addUser
             * *** userDao操作:添加用户 ***
             * 后置通知:模拟记录日志...,被织入增强处理的目标方法为:addUser
             * 最终通知:模拟方法结束后的释放资源...
             * 环绕结束:执行目标方法之后,模拟关闭事务...
             */
    
            /**
             * 异常结果 (开启 cn.dao.impl.UserDaoImpl#addUser() 中的 int i = 10 / 0;)
             * 环绕开始:执行目标方法之前,模拟开启事务...
             * 前置通知 :模拟执行权限检查...,目标类是:cn.dao.impl.UserDaoImpl@22f31dec,被织入增强处理的目标方法为:addUser
             * 异常通知:出错了/ by zero
             * 最终通知:模拟方法结束后的释放资源...
             */
        }
    }
    

2 事务

2.1 概述

简介

  1. 在Spring的所有JAR包中,包含一个名为spring-tx-4.3.6.RELEASE的JAR包(版本则需而定),该包就是Spring提供的用于事务管理的依赖包。
  2. 在该JAR包的org.springframework. transaction包中,我们可以找到3个接口文件PlatformTransactionManager、TransactionDefinition 和 TransactionStatus。接下来将对这三个核心接口类做详细介绍。

事务的核心接口类

  1. PlatformTransactionManager(平台事务管理器)

    • 该接口中提供了3个事务操作的方法,具体如下:
    1. TransactionStatus getTransaction ( TransactionDefinition definition ):用于获取事务状态信息。
    2. void commit ( TransactionStatus status ):用于提交事务。
    3. void rollback ( TransactionStatus status ):用于回滚事务。
  2. TransactionDefinìtion (事务定义)

    • 对象中定义了事务规则,并提供了获取事务相关信息的方法,具体如下。
    1. String getName():获取事务对象名称。
    2. int getlsolationLevel():获取事务的隔离级别。
    3. int getPropagationBehavior():获取事务的传播行为。
    4. int getTimeout():获取事务的超时时间。
    5. boolean isReadOnly():获取事务是否只读。
  3. TransactionStatus (事务的状态)

    • 它描述了某一时间点上事务的状态信息。
    1. void flush():刷新事务。
    2. boolean hasSavepoint():获取是否存在保存点。
    3. boolean isCompleted():获取事务是否完成。
    4. boolean isNewTransaction():获取是否是新事务。
    5. boolean isRollbackOnly():获取是否回滚。
    6. void setRollbackOnly():设置事务回滚。

2.2 xml方式

SQL脚本

-- 同上一篇文章增删改查的SQL脚本
create table account(
	id int primary key auto_increment,
	name varchar(40),
	money double
)character set utf8 collate utf8_general_ci;

insert into account(name,money) values('张三',500.0);
insert into account(name,money) values('李四',1000.0);
insert into account(name,money) values('王五',2000.0);

代码

  • 普通maven工程即可,结构图如下

    在这里插入图片描述

  1. 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>
    
  2. 实体类

    package cn.pojo;
    
    import lombok.Data;
    
    @Data
    public class Account {
        private Integer id;
        private String name;
        private Double money;
    }
    
  3. 持久层

    package cn.dao;
    
    import cn.pojo.Account;
    
    public interface AccountDao {
        Account findAccountById(Integer accountId);
    
        Account findAccountByName(String accountName);
    
        void updateAccount(Account account);
    }
    
    package cn.dao.impl;
    
    import cn.dao.AccountDao;
    import cn.pojo.Account;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    
    import java.util.List;
    
    public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
        @Override
        public Account findAccountById(Integer accountId) {
            List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
            return accounts.isEmpty() ? null : accounts.get(0);
        }
    
        @Override
        public Account findAccountByName(String accountName) {
            List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
            if (accounts.isEmpty()) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一");
            }
            return accounts.get(0);
        }
    
        @Override
        public void updateAccount(Account account) {
            super.getJdbcTemplate().update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
        }
    }
    
  4. 业务层

    package cn.service;
    
    import cn.pojo.Account;
    
    public interface AccountService {
        Account findAccountById(Integer accountId);
    
        void transfer(String sourceName, String targetName, Float money);
    }
    
    package cn.service.impl;
    
    import cn.dao.AccountDao;
    import cn.pojo.Account;
    import cn.service.AccountService;
    
    public class AccountServiceImpl implements AccountService {
    
        private AccountDao accountDao;
    
        // 提供set方法用于依赖注入
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
        @Override
        public void transfer(String sourceName, String targetName, Float money) {
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney() - money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney() + money);
            //2.5更新转出账户
            accountDao.updateAccount(source);
            // 模拟当第一个用户转出钱,但第二个用户没收到钱时发生异常
            int i = 1 / 0;
            //2.6更新转入账户
            accountDao.updateAccount(target);
        }
    }
    
  5. 配置

    <?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/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://192.168.192.130:3306/test?serverTimezone=CTT"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- 配置账户的持久层-->
        <bean id="accountDao" class="cn.dao.impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置业务层-->
        <bean id="accountService" class="cn.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
    
        <!-- spring中基于XML的声明式事务控制配置步骤
            1、配置事务管理器
            2、配置事务的通知
                    此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
                    使用tx:advice标签配置事务通知
                        属性:
                            id:给事务通知起一个唯一标识
                            transaction-manager:给事务通知提供一个事务管理器引用
            3、配置AOP中的通用切入点表达式
            4、建立事务通知和切入点表达式的对应关系
            5、配置事务的属性
                   是在事务的通知tx:advice标签的内部
         -->
    
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置事务的通知-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!-- 配置事务的属性
                    isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
                    propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
                    read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
                    timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
                    rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
                    no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
            -->
            <tx:attributes>
                <tx:method name="*" propagation="REQUIRED" read-only="false"/>
                <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
            </tx:attributes>
        </tx:advice>
    
        <!-- 配置aop-->
        <aop:config>
            <!-- 配置切入点表达式-->
            <aop:pointcut id="pt1" expression="execution(* cn.service.impl.*.*(..))"></aop:pointcut>
            <!--建立切入点表达式和事务通知的对应关系 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
        </aop:config>
    </beans>
    
  6. 测试

    package cn.service;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class AccountServiceTest {
    
        private AccountService accountService;
    
        @Before
        public void init() {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
            accountService = (AccountService) applicationContext.getBean("accountService");
        }
    
        @Test
        public void transfer() {
            accountService.transfer("张三", "李四", 100.0f);
        }
    }
    

2.3 annotation方式

  • 普通maven工程,结构图如下

    在这里插入图片描述

  1. 依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.18.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.21</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>
    
  2. 实体类

    package cn.pojo;
    
    import lombok.Data;
    
    @Data
    public class Account {
        private Integer id;
        private String name;
        private Double money;
    }
    
  3. 持久层

    package cn.dao;
    
    import cn.pojo.Account;
    
    public interface AccountDao {
        Account findAccountById(Integer accountId);
    
        Account findAccountByName(String accountName);
    
        void updateAccount(Account account);
    }
    
    package cn.dao.impl;
    
    import cn.dao.AccountDao;
    import cn.pojo.Account;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Repository("accountDao")
    public class AccountDaoImpl implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        @Override
        public Account findAccountById(Integer accountId) {
            List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), accountId);
            return accounts.isEmpty() ? null : accounts.get(0);
        }
    
        @Override
        public Account findAccountByName(String accountName) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<Account>(Account.class), accountName);
            if (accounts.isEmpty()) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一");
            }
            return accounts.get(0);
        }
    
        @Override
        public void updateAccount(Account account) {
            jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
        }
    }
    
  4. 业务层

    package cn.service;
    
    import cn.pojo.Account;
    
    public interface AccountService {
        Account findAccountById(Integer accountId);
    
        void transfer(String sourceName, String targetName, Float money);
    }
    
    package cn.service.impl;
    
    import cn.dao.AccountDao;
    import cn.pojo.Account;
    import cn.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    @Service("userService")
    public class AccountServiceImpl implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        @Override
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
        @Override
        @Transactional
        public void transfer(String sourceName, String targetName, Float money) {
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney() - money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney() + money);
            //2.5更新转出账户
            accountDao.updateAccount(source);
            // 模拟当第一个用户转出钱,但第二个用户没收到钱时发生异常
            int i = 1 / 0;
            //2.6更新转入账户
            accountDao.updateAccount(target);
        }
    }
    
  5. 配置

    # 数据源配置,路径为:resources/dataSource.properties
    datasource.driver=com.mysql.cj.jdbc.Driver
    datasource.url=jdbc:mysql://192.168.192.130:3306/test?serverTimezone=CTT
    datasource.username=root
    datasource.password=root
    
    package cn.conf;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import javax.sql.DataSource;
    
    public class DataSourceConf {
        @Value("${datasource.driver}")
        private String driver;
        @Value("${datasource.url}")
        private String url;
        @Value("${datasource.username}")
        private String username;
        @Value("${datasource.password}")
        private String password;
    
        @Bean(name = "jdbcTemplate")
        public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
            return new JdbcTemplate(dataSource);
        }
    
        @Bean(name = "dataSource")
        public DataSource createDataSource() {
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            return ds;
        }
    }
    
    package cn.conf;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.sql.DataSource;
    
    public class TransactionConf {
        // 用于创建事务管理器对象
        @Bean(name = "transactionManager")
        public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
    package cn.conf;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    @Configuration
    @ComponentScan({"cn.dao", "cn.service"})
    @Import({DataSourceConf.class, TransactionConf.class})
    @PropertySource("dataSource.properties")
    @EnableTransactionManagement
    public class SpringConf {
    
    }
    
  6. 测试

    package cn.service;
    
    import cn.conf.SpringConf;
    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(classes = SpringConf.class)
    public class AccountServiceTest {
    
        @Autowired
        private AccountService accountService;
    
    
        @Test
        public void transfer() {
            accountService.transfer("李四", "张三", 100.0f);
        }
    }
    
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值