使用注解版本声明事务

    xiaoxiao2024-12-07  66

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.learn</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/javassist/javassist --> <dependency> <groupId>javassist</groupId> <artifactId>javassist</artifactId> <version>3.12.1.GA</version> </dependency> <!-- 引入Spring-AOP等相关Jar --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>3.0.6.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.5.3</version> </dependency> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.1_2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 --> <dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.37</version> </dependency> </dependencies> </project> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" 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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <!-- 这里表示扫包范围 因为我们是使用注解的, --> <context:component-scan base-package="com.learn"></context:component-scan> <!-- 这里表示开启事务的注解 你如果想要事务的话,你必须开启一个事务注解, --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> <!-- 开启事物注解 --> <!-- 1. 数据源对象: C3P0连接池 --> <!-- 第一步我们加载C3P0数据源 DBCP和C3P0的区别讲一下,数据库的连接池, --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day20"></property> <property name="user" value="root"></property> <property name="password" value="123456"></property> </bean> <!-- 2. JdbcTemplate工具类实例 --> <!-- 这里要引用到我的数据源 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 3.配置事务 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 开启注解事务 --> <!-- 这个不是默认就有的,而是需要开启的 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" /> </beans> package com.learn.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, age); System.out.println("updateResult:" + updateResult); } } package com.learn.service; //user 服务层 public interface UserService { public void add(); } package com.learn.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.learn.dao.UserDao; import com.learn.service.UserService; import com.learn.transaction.TransactionUtils; /** * 这就是编程式事务 * * @author Leon.Sun * */ @Service public class UserServiceImpl implements UserService { @Autowired private TransactionUtils transactionUtils; @Autowired private UserDao userDao; // public void add() { // TransactionStatus transactionStatus = null; // try { // transactionStatus = transactionUtils.begin(); // userDao.add("test001", 20); // System.out.println("开始报错了.........................."); int i = 1/0; // System.out.println("####################################"); // userDao.add("test002", 21); // if(transactionStatus!=null) { // transactionUtils.commit(transactionStatus); // } // } catch (Exception e) { // e.printStackTrace(); // if(transactionStatus!=null) { // transactionUtils.rollback(transactionStatus); // } // } // } /** * 一旦加了这个注解之后 * 就可以通过Spring来帮我们管理事务了 * 记住这是声明式事务 * 或者xml方式 * 声明:@Transactional 或者XML方式 * 一般我们只要加了这个事务注解之后 * logService.addLog(); * 这行代码以走完之后 * 会不会打印日志 * 必须当你代码走完之后 * 方法执行完毕之后,才会提交事务 * 在这里你可以思考一下他的原理 * 方法开始执行之前 * 开启这个事务 * 是不是这样的 * 但是只是我们的肉眼看不到 * 因为他是通过封装起来的 * 这个里面用到了什么技术呢 * 在你的方法之前和之后做了一个处理 * 正常只要你代码一结束 * 只要你方法执行完毕的情况下 * 方法一结束的情况下 * 肯定会提交事务 * 在这边我们来看看效果 * 刷了一下数据库是不是有这样两条数据了 * 这边呢我们怎么做呢 * 我们断点调试一下 * 我抛个异常出来 * 然后我打一个这样的断点 * 然后我们怎么做呢 * 我们这样做 * 我在这边打个断点之后 * 我们走到int i = 1/0;的时候 * 他不会提交到数据库里面去的 * 我把数据库的两条数据给删掉 * 删掉完了之后 * 这个时候我去debug调试一下 * 然后你们注意看 * 现在我刷新一下 * 是不是走完了 * 我们看一下是不是没数据 * 为什么呢 * 因为它需要当你方法执行完毕之后才会提交这样的一个事务的 * 这个就比较简单了 * 然后当我方法一抛异常的情况下 * 这个时候它会走异常通知 * 还没提交 * 这个时候他就会走异常通知 * 接收到异常之后 * 他就会对你的方法做一个回滚 * 你们看一下是不是抛异常了 * 抛异常之后 * 不用看 * 他肯定是不会提交到数据库里面去的 * 也会回滚的 * 那这个回滚的话 * 通过异常通知接收来进行回滚 * 在这里我来问一个问题 * * * * */ @Transactional public void add() { /** * 这样做它会回滚吗 * 说一下 * 会不会回滚 * 昨天讲过原理的 * 不要不知道原理 * 会不会 * 绝对不会 * 为什么不会 * 为什么我加上try之后他就不会回滚 * 谁能告诉我这是什么原因 * 谁能解释一下这是什么原因 * 解释一下 * 到底什么原因 * 屏蔽了异常捕获 * 因为异常通知没有接收到这个请求之后 * 没有接收到这个异常之后 * 他就不会做回滚 * 不用看肯定内部消化了 * 算是正常结束 * 我再运行一遍是不是没报错 * 然后发现是不是有test001了 * 但是你会发现 * userDao.add("test002", 21); * 这条数据没有插进去 * 因为它走到int i = 1/0;这一行的时候 * 报错了 * 进入到catch里面 * 那么我说过了昨天 * 以后你们在使用事务的时候 * 就是你不要去try他 * try的话异常通知 * 我们自己手写事务之后 * 你就知道原理了 * 就是他的异常需要抛到AOP里面去接收起来 * AOP如果是没有拿到异常的时候 * 没有拿到异常通知的情况下 * 因为你是正常结束的 * 我就把你整个事务进行提交了 * 就是这样的 * 出现异常的情况下既然没有提交 * 为什么要回滚呢 * 如果你不回滚的情况下 * 跟你们讲一下 * 会产生什么问题呢 * 就是如果长期这样的话 * 很容器产生死锁的一种现象的 * 我之前遇到一个问题 * 我自己写了一个编程事务 * 一套事务框架 * 就给他们用 * 就是基于这种原理进行封装的 * 然后有些人他不去rollback * 那么导致什么呢 * 你要知道我搭建个项目有好多人在用这个环境 * 有几百个成员一起跑这个项目 * 然后他的代码一写出来的时候 * 就会导致什么呢 * 这个数据如果没有提交的话 * 只是要mysql查一下 * 查询哪些数据只是没有提交的 * 在MYSQL内存里面的 * 当你提交了之后 * 才会提交到硬盘里面去的 * 那这个有什么坏处呢 * 如果你长期这样做的话 * 很容易导致死锁现象的产生的 * 这个我刚才遇到这个问题的 * 把那些别人没有提交的数据 * 全部给清掉了 * 就好了 * 这个说一下 * 你们一定要注意一下的 * 回滚记住一点 * 既然有人问到过 * 记住一点 * 你有begin * 还有一个commit * 然后还有rollback * rollback是不是就是回滚 * 在这边我给您讲一下 * 你只要begin了 * 那你一定要去commit * 你一定要去rollback * 你们知道这个目的是什么意思 * 记住啊 * 就是什么意思呢 * 你既然begin的时候 * 那一定要去commit和rollback * 如果你不这样的话 * 导致你的事务就一直在存在 * 而且没有把事务结束掉 * 那这样很浪费内存的 * 也很容易产生死锁现象 * 这是我要告诉你的一个原因 * 这个是标配 * 如果执行begin没有rollback或者commit的情况下 * 那MYSQL就会以为你的事务还是一直存在 * 长久情况下 * 就很容易产生死锁现象的 * 只要你们刚开始学事务的时候 * 一般都讲过的 * 我记得我那会都是学过的 * 一定要begin,commit,rollback * 这是比较基础的知识 * 然后我在这边我告诉你们 * 其实你们看一下文档里面 * 我当时是怎么给你们总结的 * 你们看一下 * 我当时有总结一段话 * 这是你们以后使用事务的一个注意事项 * 什么注意事项呢 * 你们在这边注意看一下 * 这一点你们一定要记住 * 为什么呢 * 不是数据库死锁 * 是表死锁现象 * 我到时讲MYSQL的时候讲一下 * 就会产生死锁现象的 * 事务一直存在就导致挂起了 * 你添加还好 * 尤其是你做修改的时候 * 修改的时候你事务又没有提交 * 别人操作不了 * 你又不提交 * 我又操作不了 * 这个时候肯定会产生死锁现象 * 你们想想是不是这样的 * 你又不提交 * 这个我会跟你讲的 * 不会导致数据库死锁 * 数据库死锁倒不至于 * 数据库死锁那就废了 * 那别人根本就不能操作你的项目了 * 这个我们会在数据库里面讲 * 这是我们讲我们这里的事务 * 然后你们记住一点 * 在使用事务的时候 * 一定要记住什么呢 * 一定不要去try * 如果直接try的话 * 那你就在catch里面做一个手动的回滚 * 这是我要跟你们讲的 * 获取当前事务 * 手动进行回滚 * 这是我昨天讲过的 * 那我就不再细说了 * 直接获取当前线程的事务 * 然后手动去做这样的回滚 * 写了try之后 * catch里面都会有这段代码 * 手动去回滚 * 这是我要去说的一个问题 * 如果你不手动回滚的话 * 那么我告诉你们 * 绝对产生死锁 * 你们下去试一下 * 绝对产生死锁现象 * 具体我就不给你们谈了 * 这个不谈了 * 然后这个声明式的事务 * 还有一个方式就是xml的方式 * 你们自己下去看一下就行了 * * * */ try { userDao.add("test001", 20); int i = 1/0; System.out.println("####################################"); userDao.add("test002", 21); } catch (Exception e) { e.printStackTrace(); } } } package com.learn.transaction; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; // 编程事务(需要手动begin 手动回滚 手都提交) @Component @Scope("prototype") // 每个事务都是新的实例 目的解决线程安全问题 多例子 public class TransactionUtils { // 全局接受事务状态 private TransactionStatus transactionStatus; // 获取事务源 @Autowired private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务 public TransactionStatus begin() { System.out.println("开启事务"); transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transactionStatus; } // 提交事务 public void commit(TransactionStatus transaction) { System.out.println("提交事务"); dataSourceTransactionManager.commit(transaction); } // 回滚事务 public void rollback() { System.out.println("回滚事务..."); dataSourceTransactionManager.rollback(transactionStatus); } } package com.learn.aop; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.learn.transaction.TransactionUtils; /** * 切面类 基于手手动事务封装 * 记住它是一个封装的事务 * 应该是使用环绕通知好呢 * 还是用后置通知好呢 * 最好是使用环绕通知好 * 因为他是拦截 * 在这里我们只需要定义一个方法就可以了 * * 这里其实就是类似于声明式事务 * 只是我都放到代码里面去了 * 我怎么验证搭建AOP事务是成功的呢 * * 解释一下注解里面是怎么封装事务的 * * * @author Leon.Sun * */ @Component @Aspect public class AopTransaction { /** * 我们把自己封装的类注入进来 * 注入进来了之后 * 写一个@Autowired * */ @Autowired private TransactionUtils transactionUtils; // TransactionUtils 不要实现为单例子: 如果为单例子的话可能会发生线程安全问题 // // 异常通知 /** * 如果你长期不提交会产生死锁的 * 会产生死锁现象的 * 是不能捕获异常的 * 我来问一下 * 如果我们这个时候不加异常通知 * public void around(ProceedingJoinPoint proceedingJoinPoint) * 这段代码这么写对不对 * 肯定不对的 * 他不会走提交 * 如果他不会走提交的话 * 那他就永远占着内存 * 他又不提交 * 这样就会长期的产生死锁对象 * 你们最好通过异常通知 * 去接收这样的一个通知 * 一旦方法抛异常的情况下 * 就做回滚 * * * @AfterThrowing这里是Spring底层的框架的默认封装好的 * 这段代码默认就封装好了 * 默认封装好的情况下你try的话 * 你想自己回滚的情况也可以 * 你如果不try把异常抛给我我会回滚的 * 因为首先框架很多是比较底层的 * 接下来会讲到很多 * 并发编程其实不难 * 之前都讲过的 * * */ @AfterThrowing("execution(* com.learn.service.UserService.add(..))") public void afterThrowing() { /** * 回滚事务 * */ System.out.println("回滚事务"); /** * 获取当前事务 直接回滚 * 这里不好写 * transactionStatus这个参数怎么传入进来呢 * 他又在两个不同的方法 * 怎么解决这个问题 * 不要用全局变量 * 用一个方法 * 获取当前的事务 * 怎么获取当前的事务 * 在这边给你讲一下 * TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() * 调用这个方法 * 这个方法干嘛用的 * 这个方法表示什么意思呢 * 直接回滚的 * 就是直接可以进入回滚的 * 那你们可以在这边看一下效果 * 你们不要去定义为全局变量 * 就回滚 * 直接回滚 * 获取当前事务就只解决回滚 * 这边讲一下该怎么做 * * */ TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } // 环绕通知 在方法之前和之后处理事情 /** * 这个方法我们在这里怎么做呢 * * * @param proceedingJoinPoint * @throws Throwable */ @Around("execution(* com.learn.service.UserService.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { /** * 这里表示开启事务 */ System.out.println("开启事务"); /** * 这里begin * 把这个状态transactionStatus传进去 * */ TransactionStatus transactionStatus = transactionUtils.begin(); /** * 然后他会调我们实例的方法 * */ proceedingJoinPoint.proceed();// 代理调用方法 注意点: 如果调用方法抛出溢出不会执行后面代码 /** * 这里提交事务 */ System.out.println("提交事务"); /** * 如果没有抛异常的情况下就commit * 然后把transactionStatus这个放进去 * 这里是AOP提交事务 * 提交你再查一下是不是就有了 * */ transactionUtils.commit(transactionStatus); } } package com.learn; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.learn.service.UserService; public class Test001 { public static void main(String[] args) { /** * 我们同样让他去加载spring.xml这个文件 * 去运行一下 * * */ ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userServiceImpl"); /** * 我们会去运行这个add方法 * */ userService.add(); } }

     

    最新回复(0)