蓝冠在线网站建设,江苏盐城网络科技有限公司,网站开发 打标签,网站选项卡【JavaEE】Spring事务#xff08;1#xff09; 文章目录 【JavaEE】Spring事务#xff08;2#xff09;1. 为什么要使用事务2. Spring中事务的实现2.1 事务针对哪些操作2.2 MySQL 事务使用2.3 Spring 编程式事务#xff08;手动挡#xff09;2.4 Spring 声明式事务#… 【JavaEE】Spring事务1 文章目录 【JavaEE】Spring事务21. 为什么要使用事务2. Spring中事务的实现2.1 事务针对哪些操作2.2 MySQL 事务使用2.3 Spring 编程式事务手动挡2.4 Spring 声明式事务自动挡2.5 小疑问Transactional注解原理2.5.1 Transactional注解原理2.5.2 为什么必须被五大类注解修饰2.5.3 为什么Transactional不支持static方法 3. 实践3.1 创建项目3.2 编写代码3.3 测试3.4 注意事项3.4.1 事务不会自动回滚解决方案1重新抛出3.4.2 事务不会自动回滚解决方案2手动回滚 【JavaEE】Spring事务2
1. 为什么要使用事务
比如跟钱相关的两个操作 第一步操作小马卡里 - 100元 第二步操作老马卡里 100元 这就是一个事务捆在一起的一组行为就是事务 而它能保证的是这个行为的原子性一致性隔离性持久性 两个操作都成功两个操作都失败 要么一起成功要么一起失败 但是如果没有事务呢则两个操作逻辑上是分开的
第一个操作成功第二个操作失败则小马直接亏了100
2. Spring中事务的实现
Spring中的事务操作主要分为两类
编程式事务原生方式去写代码操作事务声明式事务利用注解“约定规则”去自动开启和提交事务
2.1 事务针对哪些操作
事务一般针对的是 持久化相关的操作如数据库操作、文件系统操作等 正如刚才的那样两个用户的转账操作 保证数据完整性的操作如消息队列等 通过使用事务可以在消息队列中提供可靠的消息传递机制减少消息丢失或重复处理的可能性同时确保系统在出现故障情况下能够正确恢复 事务的概念适用于需要保证一系列操作的原子性和一致性的任何场景
而其中被持久化的数据被传播的数据…等操作都具有**“持久性影响”**的作用所以要通过事务来控制其影响不要太糟糕而一些操作比如打印都打印到控制台了不会回滚的也没有必要回滚例如查看执行日志… 至于其他的不可见的操作又没有持久化是没有影响力的程序出异常后这些数据也销毁了~
2.2 MySQL 事务使用
--- 开启事务
start transaction;
--- transaction就是事务的意思--- 提交事务
commit;--- 回滚事务
rollback;三个重要的操作
开启事务提交事务回滚事务
2.3 Spring 编程式事务手动挡
与MySQL操作事务类似
开启事务获取一个事务/创建一个事务并获取提交事务回滚事务 SpringBoot 内置了两个对象 DataSourceTransactionManager ⽤来获取事务开启事务、提交或 回滚事务的TransactionDefinition 是事务的属性在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus 实现代码如下 RestController
public class UserController {Resourceprivate UserService userService;// JDBC 事务管理器Resourceprivate DataSourceTransactionManager dataSourceTransactionManager;// ------------定义事务属性------------Resourceprivate TransactionDefinition transactionDefinition;RequestMapping(/sava)public Object save(User user) {// ------------开启事务------------TransactionStatus transactionStatus dataSourceTransactionManager.getTransaction(transactionDefinition);// ------------插⼊数据库------------int result userService.save(user);// ------------提交事务------------dataSourceTransactionManager.commit(transactionStatus);// // ------------回滚事务------------// dataSourceTransactionManager.rollback(transactionStatus);return result;}
}反正就是麻烦难记不简洁容易出错难写
2.4 Spring 声明式事务自动挡
声明式事务的实现很简单
只需要在需要的类或者方法上添加 Transactional 注解 就可以实现了 无需手动开启/提交/回滚事务 进入方法自动开启事务方法执行完会自动提交事务如果中途发生了没有处理的异常自动回滚事务 具体规则/作用范围是
加在类上内部的所有非静态public方法都相当于加了 Transactional 注解加在非静态public方法上这个方法就是一个事务所在的类必须被五大类注解修饰这跟其事务的实现有关 而且有了五大类注解Spring开发才能进行呀~
代码实现
Service
Transactional
public class Test {Autowiredprivate Mapper mapper;public int save(User user) {mapper.save(user);}
}RequestMapping(/save)
Transactional
public Object save(User user) {int result userService.save(user);return result;
}跟往常的注解版和不使用注解版的代码一样 不使用注解版 灵活能实现很多功能但是麻烦使用困难甚至正常人压根没法写例如事务传播机制的代码实现起来就比较复杂使用注解版 使用规则约束实现特定功能但是方便使用简单且足以面对正常开发环境不关心一些极端的不正常开发 对于注解的使用就是遵循约定坐享其成明白逻辑作用合理使用逻辑分析合理 编程式就相当于车的手动挡声明式就相当于车的自动挡那么现实咱们买不起偏贵的自动挡车而我们现在可以无条件舒适地使用自动挡那咋不用嘞 2.5 小疑问Transactional注解原理
2.5.1 Transactional注解原理 这个行为可能你也意识到了其实就是AOP对Transactional注解下的代码进行统一的处理 当然对于不同的事务/复杂事务处理可能不同~ 这个在执行日志中也能看到可以平时观察观察~ Transactional 实现思路图 Transactionl执行思路图 默认就是这么一个事务管理器执行这样的逻辑 而如果配置了多个事务管理器则需要通过参数value/transactionManager去指定 2.5.2 为什么必须被五大类注解修饰
其实就是因为
Transactional注解是基于Spring AOP的而Spring AOP则通过JDK的或者CGLib的动态代理来实现AOP 对于使用Transactional注解来实现事务管理确实是通过动态代理来实现的 当你在一个类或方法上添加了Transactional注解时Spring会通过动态代理在运行时为该类或方法创建一个代理对象。这个代理对象会拦截调用并在适当的时机开启、提交或回滚事务 由于动态代理的实现方式确实需要满足一些条件才能使Transactional注解生效 具体来说被注解的类或方法必须是Spring容器中的bean而Spring容器会自动为标注了Service、Controller、Repository、Component和Configuration等注解的类创建bean实例。这也是为什么我之前提到了五大类注解 2.5.3 为什么Transactional不支持static方法
其实就是因为
无论JDK还是CGlib都无法对静态方法提供代理。原因在于静态方法是类级别的调用需要知道类信息而类信息在编译器就已经知道了并**不支持在运行期的动态绑定**
3. 实践
3.1 创建项目
为了方便我就直接使用之前mybatis项目里写过的代码了
因为我们目前侧重学习的点是在事务的实现 model.UserInfo Component
Data
public class UserInfo {private int id;private String username;private String password;private String photo;private LocalDateTime createtime;private LocalDateTime updatetime;private Integer state;public UserInfo(String username, String password, Integer state) {this.username username;this.password password;this.state state;}public UserInfo() {}
}mapper.UserMapper Mapper
//跟五大类注解Repositorysay 拜拜
public interface UserMapper {ListUserInfo getAll(); //获得所有用户信息UserInfo getUserById(Integer id);//通过id查找用户UserInfo getUserByUsername(Param(username) String username);//通过username查找用户ListUserInfo getAll2(Param(option) String option);UserInfo login(Param(username) String username, Param(password) String password);int update(UserInfo userInfo);//删除状态为state的用户int delete(Param(state) Integer state);//增加用户int insert(UserInfo userInfo);ListUserInfo getAllLikeSome(Param(likeString) String likeString);//用户注册提交信息
// int add(UserInfo userInfo);int add(String username, String password, Integer state, Integer id);int add2(UserInfo userInfo);ListUserInfo select1(UserInfo userInfo);int update2(UserInfo userInfo);int deleteByIDs(ListInteger list);int insertByUsers(ListUserInfo list, ListUserInfo list2);}mybatis.UserInfoMapper.xml ?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecom.example.demo.mapper.UserMapperresultMap idBaseMap typecom.example.demo.model.UserInfo/resultMapselect idgetAll resultMapBaseMapselect * from userinfo/select!-- select idgetAll resultTypecom.example.demo.model.UserInfo--
!-- select id, username as name, password, photo,--
!-- createtime, updatetime, state from userinfo--
!-- /select--select idgetUserById resultTypecom.example.demo.model.UserInfoselect * from userinfo where id #{id}/selectselect idgetUserByUsername resultTypecom.example.demo.model.UserInfoselect * from userinfo where username ${username}/selectselect idgetAll2 resultTypecom.example.demo.model.UserInfoselect * from userinfo order by id ${option}/selectselect idlogin resultTypecom.example.demo.model.UserInfoselect * from userinfo where username ${username}and password ${password}/selectupdate idupdateupdate userinfo set state #{state} where username #{username}/updatedelete iddeletedelete from userinfo where state #{state}/deleteinsert idinsert useGeneratedKeystruekeyColumnid keyPropertyid
!-- 自增主键 id 不能为null也没有默认值如果id不设置或者设置为null都会导致自增 --insert into userinfo (username, password) values (#{username}, #{password});/insertselect idgetAllLikeSome resultTypecom.example.demo.model.UserInfoselect * from userinfo where username like concat(%, #{likeString}, %)/selectinsert idaddinsert into userinfo (if testid ! 0id,/ifif testusername ! nullusername,/ifif testpassword ! nullpassword,/ifif teststate ! nullstate/if) values (if testid ! 0#{id},/ifif testusername ! null#{username},/ifif testpassword ! null#{password},/ifif teststate ! null#{state}/if)/insertinsert idadd2insert into userinfotrim prefix( suffix)suffixOverrides,if testid ! 0id,/ifif testusername ! nullusername,/ifif testpassword ! nullpassword,/ifif teststate ! nullstate/if/trimvaluestrim prefix( suffix)suffixOverrides,if testid ! 0#{id},/ifif testusername ! null#{username},/ifif testpassword ! null#{password},/ifif teststate ! null#{state}/if/trim/insertselect idselect1 resultTypecom.example.demo.model.UserInfoselect * from userinfowhereif testid ! 0id #{id}/ifif testusername ! nullor username #{username}/ifif testpassword ! nullor password #{password}/ifif teststate ! nullor state #{state}/if/where
!-- trim prefixwhere prefixOverridesand--
!-- trim prefixOverridesor--
!-- if testid ! 0--
!-- id #{id}--
!-- /if--
!-- if testusername ! null--
!-- or username #{username}--
!-- /if--
!-- if testpassword ! null--
!-- or password #{password}--
!-- /if--
!-- if teststate ! null--
!-- or state #{state}--
!-- /if--
!-- /trim--
!-- /trim--/selectupdate idupdate2update userinfosetif testusername ! nullusername #{username},/ifif testpassword ! nullpassword #{password},/ifif teststate ! nullstate #{state}/if/setwhere id #{id}/updatedelete iddeleteByIDsdelete from userinfo where id inforeach collectionlist open( close) itemx separator,#{x}/foreach/deleteinsert idinsertByUsersinsert into userinfo(username, password, state) valuesforeach collectionlist itemx open( close) separator),(#{x.username}, #{x.password}, #{x.state}/foreach,foreach collectionlist2 itemx open( close) separator),(#{x.username}, #{x.password}, #{x.state}/foreach/insert
/mapperapplication.properties spring.datasource.urljdbc:mysql://127.0.0.1:3306/test_db?characterEncodingutf8
# MyBatis 基于jdbc实现~ 底层用的就是jdbc:mysql协议这个地址是本地数据库的地址test_db就是我们的那个数据库
spring.datasource.usernameroot
# 用户名默认固定是root
spring.datasource.passwordmmsszsd666
# 密码是数据库的密码
spring.datasource.driver-class-namecom.mysql.cj.jdbc.Driver# MyBatis配置信息
mybatis.mapper-locationsclasspath:mybatis/*Mapper.xml# 执行时打印SQL
mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl
#由于其默认情况下的日志类型为Debug重要程度不高所以我们需要设置我们对应的目录下的日志级别
logging.level.com.example.demo.controllerdebug#将数据库中的下换线转换成驼峰比如 user_name - userName
mybatis-plus.configuration.map-underscore-to-camel-casetrue目录结构 3.2 编写代码 同样的controller接受请求service调用方法~ 加Transactional 3.3 测试 访问路由前 delete from userinfo;现在userinfo一条数据都没有了~ 效果 浏览器 控制台 数据库 符合预期还是空的 因为发生了因为Transactional捕获到了异常发生回滚 去这段代码后效果 3.4 注意事项
Transactional 在异常被 try{}catch(){} 捕获的情况下不会进行事务自动回滚这也很好理解因为 try{}catch(){} 后后面的代码可以继续运行这个异常是被我们写的 try{}catch(){} 抢走处理了注解是捕获不到的~ 代码 效果 浏览器 控制台 数据库 说明没有回滚 3.4.1 事务不会自动回滚解决方案1重新抛出 效果 无新增数据代表回滚成功 但是这不太美观“优雅”过于暴力
3.4.2 事务不会自动回滚解决方案2手动回滚
TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务然后设置回滚方法setRollbackOnly就可以实现将当前事务的回滚了
跟切面有关aop 效果 无新增数据代表回滚成功 这种方式就比较“优雅”了~ 文章到此结束谢谢观看 可以叫我 小马我可能写的不好或者有错误但是一起加油鸭 代码事务/src/main · 游离态/马拉圈2023年8月 - 码云 - 开源中国 (gitee.com)