触发器
Jimmer支持触发器,用户可以监听数据库的变化。
触发器不仅可以告知对象的变更,也可以告知关联的变更。
触发器类型
触发器分类
-
BinLog触发器
这是默认的触发器类型,不会影响Jimmer本身生成的SQL,具有较高性能,在事务提交后被触发,能监听任何原因导致的数据库变化,包括非Jimmer API引起的数据和变化;但需要底层数据库支持binlog。
-
Transaction触发器
该触发器不无需底层数据库,在事务提交前被触发;工作机制和Alibaba Seata的AT模式类似,会在修改过程中生成额外的查询语句,对修改性能有一定影响,也只能监听因Jimmer API导致的数据库的变化。
两种触发器的区别如下
| BinLog触发器 | Transaction触发器 | |
|---|---|---|
| 触发时机 | 事务提交后 | 事务提交前 |
| 性能 | 高 | 低 |
| 可监听的数据库变化 | 任何原因导致的数据库变化 | 仅因当前应用调用Jimmer API导致的数据库变化 |
| 数据库要求 | 支持且启用binlog | 无任何要求 |
| 工作原理 | 利用第三方技术将数据库binlog变更推送到消息队列,Jimmer应用监听消息队列 | Jimmer的任何修改操作API均自 动植入额外的SQL查询以找寻数据变更,和Alibaba Seata的AT模式类似 |
除了这个表格中的区别外,两种触发器为用户提供的通知数据完全相同。
推荐用法
-
BinLog触发器
BinLog触发器在事务提交后触发,面对无法更改的既定事实。
即,BinLog触发器对原事务毫无影响,被允许做耗时操作。所以适合在其处理逻辑中执行多个任务,尤其是这些任务
- 缓存一致性维护
- 异构数据源同步
- 以异步方式向其它微服务发送消息
-
Transaction触发器
Transaction触发器在事务提交前触发,其处理逻辑会直接植入当前事务。
如果其事件处理逻辑异常,会导致当前业务修改失败;如果其处理逻辑不能很快完成,会导致当前事务长时间不释放资源。
因此,Transaction触发器适合在当前事务中追加更多的修改行为,不会破坏原子性。
适合在数据库发生变化时通过添加额外的修改来实现通用性很强的部分业务逻辑。
设置触发器类型
概念
在讨论设置触发器类型之前,我们先看开发人员如何使用触发器
-
sqlClient.getTriggers()或sqlClient.getTriggers(false): 优先返回BinLog触发器,如果没有,则返回Transaction触发器。 -
sqlClient.getTriggers(true): 明确返回Transaction触发器,如果没有,抛出异常。
为了影响后续用户通过sqlClient.getTriggers能获取的触发器类型,需要在构建SqlClient时指定TriggerType。
TriggerType有三个取值
-
BINLOG_ONLY:
仅支持BinLog触发器,这是默认配置。
sqlClient.getTriggers()和sqlClient.getTriggers(false)返回BinLog触发器对象sqlClient.getTriggers(true)会异常,无法返回Transaction触发器对象
-
TRANSACTION_ONLY:
仅支持Transaction触发器。 无论
sqlClient.getTriggers的参数为何,都会返回同一个Transaction触发器对象 -
BOTH:
同时支持BinLog触发器何Transaction触发器。
sqlClient.getTriggers()和sqlClient.getTriggers(false)返回BinLog触发器对象sqlClient.getTriggers(true)返回Transaction触发器对象
这里,用一张表格来对比三种情况
| 触发器类型 | getTriggers(false) | getTriggers(true) |
|---|---|---|
| BINLOG_ONLY | BinLog Triggers专用对象 | 抛出异常 |
| TRANSACTION_ONLY | ||
| BOTH | BinLog Triggers专用对象 | Transaction Triggers专用对象 |
Q & A
-
Q: 为什么默认
BINLOG_ONLY模式?A: Transaction触发器会在所有保存行为中植入额外的查询用于模拟触发器,对性能有影响,默认不支持。
-
Q: 为什么
TRANSACTION_ONLY模式下,两种触发器API共享同一个对象?A: Jimmer内置的缓存一致性策略,一定使用
sqlClient.getTriggers(false),开发人员无法改变。这样做的目的是,让缓存一致性维护工作不影响修改事务,在事务提交后才开始执行。因此,原始事务不会被拉长,从而快速结束释放锁资源。
但是,并非所有数据库产品都支持binlog。这时,
getTriggers(false)返回Transaction触发器对象,冒充BinLog触发器对象,接手原本应该由BinLog触发器负责的缓存一致性维护工作。也就是说,
TRANSACTION_ONLY是为不支持binlog的数据库设计的,这是采用该模式的唯一理由。 -
Q:
BOTH模式下,存在两个不同的触发器API对象,是不是表示任何修改都有两次响应机会?A: 是的,而且这是一个重要的功能。
和Jimmer内置的缓存一致性机制必须由
sqlClient.getTriggers(false)驱动不同;用户的业务代码没有这个限制,开发人员可以自由决定某个处理逻辑究竟注册到sqlClient.getTriggers(false)还是sqlClient.getTriggers(true),甚至同时向二者注册。-
如果开发人员的事件响应逻辑包含一些数据联动修改,必须参与当前事务的原子性作用域,就应该选择将处理逻辑注册到
sqlClient.getTriggers(true) -
如果开发人员的事件响应逻辑没必要参与当前事务,就应该选择将处理逻辑注册到
sqlClient.getTriggers(false),让当前事务尽快结束,尽快释放锁资源 -
如果开发人员的事件响应逻辑同时包含以上两种情况,就应该将处理逻辑一分为二,然后分别注册到两种触发器上。
警告如果开发人员为两种触发器注册了同一个事件响应回调,那么每次事件通知时这个回调的确会被执行两次。
这时,区分两次调用就非常重要了。回调方法的参数是一个对象,可以获取JDBC连接对象,其值是否为null可用作区分二者的标准:
- 非null,第一次回调,由Transaction Trigger引起
- null,第二次回调,由BinLog Trigger引起
-
-
Q: 对于不支持binlog的数据库而言,其缓存一致性清理是不是无法在事务提交后做?
A: 并非如此,如果开发人员愿意优化,可以做到。
诚然,这种数据库无法支持BinLog触发器,采用Transaction触发器在事务生命周期内得到数据变更通知是唯一可行的方法。
然而,不并是收到通知后,必须马上执行缓存清理工作,因为redis这种远程缓存的清理行为有网络通信成本,也有通讯失败的可能,这样做会导致本地事务被拖长甚至失败。
Jimmer的缓存系统支持自定义CacheOperator,通过自定义CacheOperator,用户可以覆盖缓存系统的删除行为,将缓存删除任务记录下来但不马上执行,等事务提交后再真正的缓存清理。
-
不需要可靠性的做法
- 自定义CacheOperator并不立即清理缓存,而是用ThreadLocal记录要删除的缓存的键。
- 在Spring的
After commit事件中,集中清理缓存。
-
需要可靠性的做法
- 自定义CacheOperator并不立即清理缓存,而是用同一个数据库中一个本地事件表记录要删除的缓存的键。
- 在Spring的
After Commit事件中,从本地事件表中取数据,清理缓存,如果成功,删除本地事件表的数据。 - 用一个轮询服务,为第2步的失败兜底。
提示幸运的是,对于触发器的类型为
TRANSACTION_ONLY这种情况,Jimmer的Spring Boot Starter已经这样实现了。请参见缓存一致性/Transaction触发器
-
使用Jimmer Spring Boot Starter
如果使用SpringBoot Starter,设置触发器类型非常简单。
在application.properties或application.yml中添加一个配置即可。其名称为jimmer.trigger-type,其值为BINLOG_ONLY | TRANSACTION_ONLY | BOTH。
不使用Jimmer Spring Boot Starter
- Java
- Kotlin
JSqlClient sqlClient = JSqlClient
.newBuilder()
.setTriggerType(TriggerType.BOTH)
...省略其他配置...
.build();
javax.sql.DataSource dataSource = ...;
var sqlClient = newKSqlClient {
setTriggerType(TriggerType.BOTH)
...省略其他配置...
}
BinLog触发器开发工作
和Transaction触发器不同,BinLog触发器需要采用第三方技术将数据库binlog变更推送到消息队列,并让应用监听消息队列。
因此,仅仅在构建SqlClient对象时把TriggerType指定为BINLOG_ONLY(默认行为)或BOTH是不够的。
消息队列有多有选择,例如Kafka和RabbitMQ;把数据库binlog的增量推送到消息队列的第三方技术也有多种选择,例如MaxWell、Debezium、Canal和DataBus。
Jimmer对这类选择未做任何限制。但为简化问题讨论,本文假设消息队列选用Kafka,推送技术采用Maxwell (MySQL) 和Debezium (Postgres)。
由于Debezium本身是kafka-connector,所以,使用Debezium必然导致消息队列是Kafka
外部环境搭建
在开发之前,先要先安装环境,包括数据库、Kafka、以及Maxwell或Debezium。
-
Maxwell
-
进入jimmer-examples/env-with-cache/maxwell所对应的
git clone后本地目录 -
执行
bash ./install.sh
-