🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 一、与主程序事务冲突问题 典型的,如果我们部署一套主程序,一套jobserver或者workserver等,由于两者共享同一个数据库,而两者由于各自事务中,如果一方有某个长时间事务性(比如循环的全表扫描)的锁定某张表的操作,而恰恰对方的业务中需要访问该表,就会抛出 com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction。 解决的方案:将jobserver中,全表扫描的操作,改为短事务,简单的做法,就是循环全表扫描的过程中,每次操作都是一个完整事务的,而不是全部操作放在一个事务里面,更具体来说,就是事务部放到manager层面,而是放在dao层面。 具体的例子: Manager层面: 事务的: ![](https://img.kancloud.cn/c6/82/c6829f2191315214f3a36fa7bc67e5f9_454x128.png) ![](https://img.kancloud.cn/95/fe/95fea39633fec3e9c0f642a336c380d3_469x203.png) 非事务的: ![](https://img.kancloud.cn/8e/84/8e8420b5cd1d149d41720afeb2cba020_487x69.png) ![](https://img.kancloud.cn/ed/ef/edef2573bdb71e7b9fa9f4067a9901cf_485x93.png) 注意,这里面的dao必须是事务的了,否则,无法修改、删除、新增实体对象。 Job层面: 事务的: ![](https://img.kancloud.cn/bf/2c/bf2c0374795c4f66061faaa0f8a294c9_495x165.png) 非事务的: ![](https://img.kancloud.cn/64/00/64004eb4def6063b735d633730f4a4e1_465x95.png) 非事务中,对实体的操作: ![](https://img.kancloud.cn/e4/69/e46947c84b3ab9d6cc209c2f28d0a408_444x49.png) 但是,这里还是有一个问题,就是由于事务控制在dao层面,会导致每个dao操作都会提交事务,对于每一轮循环操作里面组合了若干个dao操作的,这样会出现事务脏数据的问题。 解决的方案是: 继续抽取api,将循环体里面的所有dao操作组合起来,合并为一个api,将事务控制,由原dao层面,提升到该api的事务操作类层面,并将该事务操作类纳入applicationContext-*.xml里面,通过serviceLocator.getObjectFromContext获取即可。 ![](https://img.kancloud.cn/af/e4/afe43e3942f34d6d7c727974d998a180_554x248.png) 这个方案确保了循环体里面的操作都是完整的事务,循环体之间不是同一个事务,兼顾了事务完整性和避免长事务。 ## 二、事务内死循环问题 死循环的操作,除了上述问题之外,即使是一张独占的表,还会带来事务永远无法提交的问题,所以不能用死循环的方式来处理长时间事务的。 比如: ![](https://img.kancloud.cn/5a/cd/5acdab8fb109193f73fe787c230ac756_554x180.png) ![](https://img.kancloud.cn/5a/cd/5acdab8fb109193f73fe787c230ac756_554x180.png) 这个方案里面,由于doTest永远无法结束,导致内部的事务无法提交,一直处于事务中。故不能采用这种方案,只能是用减小间隔的方式。 ## 三、多job之间事务并发机制 多个job中,如果都循环操作同一个表,会出现脏数据的问题,比如某个job修改后,另外一个job拿到了继续修改,但由于事务未提交,会出现脏数据。 解决办法,用数据库的锁机制解决。平台推荐用乐观锁,增加版本号来解决冲突。具体可以参考《高级指南》中的乐观锁悲观锁章节了解; ## 四、全局单一事务job 有时候,有些业务并非长事务,但需要保障业务事务的完整性,平台提供了一种单一事务job机制。该job里面所有业务逻辑,都在一个事务里面执行,如果执行过程有异常,则会回滚; 具体做法如下: 1、该job与普通job不同,继承自UniframeworkTimerSingleTransactionJob 2、实现自己的事务内操作,实现自己的SingleTransactionJobOperator类 示例如下: ![](https://img.kancloud.cn/4d/f3/4df3b0fb14530182fd69d586508ab473_554x269.png) ## 五、循环独立事务job 对于长时间的事务,整个job的执行跨度非常长(参考4.1),循环遍历业务,且每个循环的业务都各自独立,互补影响,那么,就非常适合用循环独立事务job,job里面的业务,通过循环来实现,每一轮循环体内的业务,在一个事务里面执行,如果执行过程有异常,则仅循环体内事务回滚,不影响整体job的执行; 具体做法如下: 1、job继承自UniframeworkTimerJob 2、定义自己的manager,logic及api; 各种定义配置如下: ![](https://img.kancloud.cn/04/32/04320f2d986176866d9da73af90f542e_554x154.png) 代码实现的时候,特别注意,异常的处理,在logic循环内来处理,否则,因为api层面必须抛出异常,才能实现事务回滚,所以不可以在api里面来处理异常,直接让它抛出即可; 具体的实例明细如下: Job: ![](https://img.kancloud.cn/80/2a/802afb5b672392925a2b5fcc8956dcaa_554x242.png) Manager: ![](https://img.kancloud.cn/f1/01/f1013f6b2bdbdc96d5a0742ca5ab6325_554x318.png) Logic: ![](https://img.kancloud.cn/d1/52/d15230b0181017a11f2e67a3ff5e465f_554x186.png) 这里是关键,这里处理异常,确保不会因为一次失败,终止整个调度,仅终止该次循环的调度逻辑,而且能确保该次逻辑也是完整事务的,出错会回滚;这里是在循环体内,捕捉每次api调用的异常; Api: ![](https://img.kancloud.cn/c6/9d/c69d15eddc54bc0e20c687ef01959ffc_553x128.png) Api里面,不要处理异常,直接抛出即可; ## 六、跨数据源job 在一个job中,跨两个数据库进行操作; 首先、uniframework.properties中配置; ![](https://img.kancloud.cn/a0/5e/a05ee0c9a1e4b219ba5f423c890074f0_553x71.png) server.jobserver.anotherds.enabled必须设定为true,才会加载; 然后、在job中,调用第二数据源的dao,即可; this.serviceLocator.getJobserverAnotherDatasourceDao() 举例如下: 核心业务代码中,这样来调用第二数据源: ![](https://img.kancloud.cn/9a/56/9a56e9304fc0068e1c8581042e2a63fc_554x88.png)