从唯一性说起

写了十几年代码,直到现在,我见过非常多的处理唯一性约束的方法都是放在代码里,而非数据库里。

直到现在我也一直很困惑,这些人为什么不使用数据库的唯一索引呢?不过我并不想知道这个答案。

他们的做法很简单,假如要保证name是唯一的,先使用Java代码执行一个查询语句:

select * from example where name = ?

然后根据返回值来判断,如果是null则表明没有这个name,接着执行插入语句即可:

insert into example(name) values(?)

如果不是null则表明这个name已经存在,那就返回name已存在的提示。

如果系统并发很小或者不是人为故意测试,这种方式完全没有问题。

然而事实证明的是,还是偶尔会遇到问题,会出现name一样的记录。

类似这样的情况还有抽奖问题,那就是判断奖品是否还有剩余。

他们通常的做法也是先查询奖品剩余数量,如下这样:

select remain_count from example where id = ?

然后判断返回值,如果大于0则表明奖品还有,则执行更新语句:

update example set reamin_count = remain_count - 1 where id = ?

如果不大于0则表明奖品没有了,就返回奖品已经抽完的提示。

这种方案在奖品数量趋于0这个临界值时一定会出问题,因为大部分抽奖都是有一定并发性的。

到最后会发现剩余奖品数量不是0而是负的,这些问题我都见过,好歹客户不难缠,只需把多出的奖品钱掏了就行。

我实在想不通写这些代码的人是基于什么考虑的,这样的写法不仅代码写得多,而且也无法百分之百保证。

如果是我年轻的时候,一定会在心里“骂”这样的代码和写代码的人。

不过现在“老”了,很多事情都放得下了,权当“闭一只眼,再闭一只眼”了,况且我又不是项目经理。只要大方向不跑偏就行了。

也许这样的人,人家就是把写代码当作一份糊口的工作而已,人家不爱好这个,不愿意想太多,我们也无可非议。

当然,我不使用这种方法,我一般会在数据库里加上唯一索引,然后尽情的insert吧。

如果没有唯一键冲突,那就一定会插入成功,如果有唯一键冲突,那就一定会抛异常,Spring把这个异常进行了转化。

它就是DuplicateKeyException,我们只需try一下即可:

try {xxxMapper.insertXXX(..);return 1;
} catch (DuplicateKeyException ex) {log.warn(..);return -1;
}

我们不去讨论那种方法好,至少这种做法代码写的少,而且使用数据库的唯一索引,绝对不会出现重复记录。

我以为的我以为

如果有较大量数据需要插入的话,我们都会使用批量插入,如果使用Mybatis的话就是<foreach>标签了。

但是有一个问题,如果插入的数据有重复的话,而且数据库要求不能重复且还建了唯一索引,这时批量插入就没法用了。

因为只要有一个唯一键冲突,这批数据都得完蛋。这其实没有什么非常好的方法,不过可以先拿待插入数据进行检测,把重复的直接排除掉。

但是需要写更多的代码,有些繁琐。实在不行,只要时间上要求不高,还是采用单条插入吧。

我认为,如果有大量数据需要插入而且还要不重复,关键是数据里真有重复的,还是先对数据进行预处理,否则批量插入用不了,单条插入又非常耗时。

我就遇到了这样的遗留问题,有重复的数据,所以不能使用批量插入,好歹数据量不大,那就单条单条的来吧。

按照我们的理解,单条数据唯一键冲突只影响这一条,肯定会抛异常,我们只要try/catch住,不会影响下一条的插入。当然,这是我以为的。

代码当然是这样写的:

int count = 0;
for (XXX xxx : xxxList) {try {xxxMapper.insertXXX(xxx);count++;} catch (DuplicateKeyException ex) {log.warn(..);}
}
return count;

先不要说for里面使用try/catch是不是合理,世界上哪有那么多的合理啊,快速解决问题才是王道,不合理的事情留到以后再说。

如果这样真的可以的话,那也算是一种解决方法。可惜的是,一旦遇到唯一键冲突,异常虽然catch住了,但是事务照样中止了,看来,“我以为的”还真成了我以为的。

我进行了多次其它尝试,如catch更多的其它类型的异常,发现只能延迟事务的中止,但最后还是中止。我又在事务注解上设置不回滚某些类型的异常,发现还是不行。

多次尝试之后,我放弃了,因为这是别人的或系统的遗留问题,没有什么好的解决办法,或者也改为别人的写法,先查询再插入,但是需要写更多的代码,也没有太多时间了。

于是就决定不使用事务了,把事务注解去掉。问题得以解决了。后来还发现,这个方法被别的带事务的方法调用了,默认又在事务里了,索性干脆直接使用注解标记为不支持事务。

掐断了事务的传播之后,这下真与事务绝缘了,世界清净了。

所以,在从零开发新系统的时候,一定要多思考,不管是项目经理还是开发人员,一定要知道现在的某种做法会在日后带来什么问题,如果什么都不想,日后必定会有很多奇葩的问题,简直莫名其妙。

最终,我们不得不承认,没有最烂的代码,只有更烂的代码。

重新认知Spring事务

说句心里话,这个事情真的让我很意外,虽然我很少有“意外”,本以为可以的,结果却是不行。于是我就仔细的思考。

Spring的事务给人的印象就是抛出了某些异常可以回滚,抛出了某些异常可以不回滚,而且是可以配置的,默认只回滚运行时异常。

这仿佛是在说明Spring可以catch住指定的异常,然后提交事务,或catch住某些异常,然后回滚事务,再把异常抛出给我们。

照这样理解,那我们自己catch住异常岂不更好,不用劳Spring大驾,事实是不完全行的。由于Spring的事务行为是运行时通过生成子类注入的,所以没有现成的源码可看。

由于这件事,我又想起了我年轻时候的困惑,由于后来就不再想这个困惑了,所以一直没有得到答案。

Spring把事务加在Service层的方法上,但很多时候,这些方法仅仅就是执行一个sql语句而已,无论是insert、update还是delete。

按照通常的理解,只有在涉及多个sql操作的时候才需要事务,这样它们要么全部成功,要么有一个报错就全部回滚,这也正是事务的原子性。

但是只有一个sql操作时,理论上不需要事务,因为它的成功与否并不会对别的sql产生影响,因为只有一个sql操作,默认就是原子的。而且一个sql操作,要么成功要么失败,不会出现一半成功一半失败的情况,这是数据库保证的。

这个逻辑推理本身是没有错的,只是有些狭隘,因为我们把这个事务仅仅看作是数据库的事务,仅仅把它限制在数据库里了。这就是上面的一个疑惑的缘由,为什么只有一个sql操作也开启事务。

Spring把事务加在Service层,其实是扩大了事务的范围,把事务从数据库里拿了出来,放到了Service层的Java代码里了。让我们的业务代码也融入到了事务里。

我们可以先执行若干sql操作,没有抛异常,然后再执行业务代码,如果业务代码抛了异常,Spring可以回滚事务,这样先前的sql操作就撤销了,宏观来看sql操作和业务代码就在一个事务里。

只不过很多时候我们没有业务代码,所以就只剩下一个sql操作了,因此也开着事务,这就解释了前面的疑惑,为什么只有一个sql操作也开着事务。

于是我有一个大胆的猜测,Spring事务里说的“对哪些异常回滚和不回滚”这里的异常应该指的是业务代码里抛出的异常,而不是对数据库执行sql操作时抛出的异常。

因为执行业务代码时抛出的某些异常可能并不影响对数据库的操作,当然这是站在业务的角度来说的,所有Spring照样可以提交事务,让对数据库的sql操作生效。

但是如果在对数据库执行sql操作时抛出了异常,则一定会选择回滚事务,毕竟这个事务是从数据库里引出来然后扩大到整个业务层,而不是倒过来。

我感觉Spring可以通过异常类型来判断是业务代码抛出的还是数据库操作抛出的,如果是业务代码抛出的,我们可以自己catch住或配置为不回滚,则最终照样提交事务。

如果是对数据库执行操作时抛出的,则总是会回滚事务,即使我们自己catch住或配置为不回滚,也照样没有用,最后都会回滚,毕竟数据库操作失败,不应该再有任何幻想。

这样就可以解释本文开头说的情况,虽然catch住了唯一键冲突异常或把该异常配置为不回滚,但是事务照样中止。

注意,这些只是我的猜测,欢迎留言分享自己的看法或想法或猜测。

(END)

特别推荐一个分享架构+算法的优质内容,还没关注的小伙伴,可以长按关注一下:

长按订阅更多精彩▼如有收获,点个在看,诚挚感谢

Spring,你为何中止我的事务?相关推荐

  1. spring boot项目 中止运行 最常用的几种方法

    spring boot项目 中止运行 最常用的几种方法: 1. 调用接口,停止应用上下文 @RestController public class ShutdownController impleme ...

  2. Spring整合JMS(四)——事务管理

    2019独角兽企业重金招聘Python工程师标准>>> Spring整合JMS(四)--事务管理 博客分类: Spring Jms SpringJMS事务sessionTransac ...

  3. spring.net nhibernate 分布布式事务(下)

    spring.net nhibernate 分布布式事务(下) 摘自: http://www.cnblogs.com/GoodHelper/archive/2010/07/30/SpringNetDi ...

  4. 【Spring】Spring第三天 - 声明式事务、常用注解、Ajax 复习

    一.自动注入 1.在Spring 配置文件中对象名和ref="id" . id 名相同,使用自动注入,可以不配置<property/> 2.两种配置办法 2.1 在&l ...

  5. Spring+Mybatis+MySql+Maven 简单的事务管理案例

    利用Maven来管理项目中的JAR包,同时使用Spring在业务处理层进行事务管理.数据库使用MySq,数据处理层使用Spring和Mybatis结合. 本案例代码主要结构如图: 1.数据库脚本 -- ...

  6. Spring在tomcat下使用JTA事务

    Spring在tomcat下使用JTA事务 涉及到多个库的事务问题时,常常要使用到JTA事务,tomcat本身不支持JTA事务,需要借助于Atomikos来使用JTA事务.以下内容结合mybatis, ...

  7. Spring中两种编程式事务管理

    Spring中两种编程式事务管理 在代码中显示调用beginTransaction,commit,rollback等与事务处理相关的方法,这就是编程式事务管理,当只有少数事务操作时,编程式事务管理才比 ...

  8. spring框架学习 - Data Access之 事务管理 - 声明式事务管理

    接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122085016?spm=1001.2014.3001.5502 4.声明式事务管理 ...

  9. 在Spring中使用JOTM实现JTA事务管理

    Spring 通过AOP技术可以让我们在脱离EJB的情况下享受声明式事务的丰盛大餐,脱离Java EE应用服务器使用声明式事务的道路已经畅通无阻.但是很大部分人都还认为脱离Java EE应用服务器就无 ...

  10. Spring Cloud Alibaba系列之分布式事务Seata

    Spring Cloud Alibaba系列之分布式事务Seata 1.分布式事务 分布式事务不是在现在微服务分布式架构上才产生的问题,在单体应用同样存在分布式事务问题,典型的场景就是单体应用使用了多 ...

最新文章

  1. java显示本地磁盘所有盘符,显示桌面路径
  2. js中event对象属性和方法
  3. python字典有什么用_在Python中使用范围作为字典键,我有什么选...
  4. linux 基本指令
  5. 『 Luogu P3205 』 HNOI2010 合唱队
  6. php工场模式讲解,PHP设计模式之工厂模式详解
  7. html带取消的谈窗框,HTML参考
  8. Linux服务器运行环境搭建(四)——Tomcat安装
  9. 【ABAP系列】SAP ABAP 从FTP服务器读取文件到本地
  10. 配置一个强大的FireFox
  11. Atitit. 二进制数据ascii表示法,与base64编码解码api 设计标准化总结java php c#.net...
  12. Windows Mobile 6 模拟器绿色中文版
  13. java软件开发必读15本书籍
  14. Stata:面板分位数回归
  15. 魔兽争霸——《冰封王座》2007魔兽比赛背景音乐下载
  16. android知乎多图片选择,知乎开源Matisse图片选择器使用
  17. 【LUTs调色】50个好莱坞电影级别调色预设模板 mLUT Film 3
  18. 深大uooc学术道德与学术规范教育第十二章
  19. AR虚拟互动系统创造身临其境的多元互动体验
  20. android 文字大小设计,为什么设计稿的文字大小和开发的不一致

热门文章

  1. php网站首页点击更多时获取数据,jQuery+PHP实现点击按钮加载更多,不刷新页面加载更多数据!附:可用源码+demo...
  2. php 求 相似 比,php计算title标题相似比
  3. php memcache 封装类,PHP 自定义session储存 MEMCACHE 方式类
  4. 八大排序算法合集 (归并排序、交换排序、插入排序、选择排序......)
  5. 计算机硬件系统公开课课件,计算机硬件系统的组成(公开课).ppt.ppt
  6. C - Multiplication Table CodeForces - 448D
  7. 华为策略路由加等价路由_两个ISP接入路由,双路由接入华为S5700交换机,实施策略路由...
  8. 求自定类型元素序列的中位数
  9. poj2112(floyd+二分+二分图多重匹配)
  10. java多态简单例子6_Java_6、面向对象——继承和多态