jSqlBox 是一个建立在 DbUtils 内核上的 Java 全功能数据库持久层工具,具备跨数据库(80 多种方言)、DD L生成、分页、多种SQL写法、分库分表、声明式事务、分布式事务、主从、实体 CURD、实体关联查询、ActiveRecord、Sql 模板等功能。
Gtx 事务是 jSqlBox3.0.0 新增的分布式事务模块,它的总体思路和 Seata 类似,也是通过生成反向记录来自动回滚,减小对业务的侵入,但 jSqlBox 采用的思路是将分布式事务建立在 ORM 工具之上,不是分析SQL 内容,而是记录实体的插入、删除、修改操作,以生成回滚记录。这样一来,在实现难度上就降低了一个等级,以牺牲 SQL 支持来达到最好的跨数据库兼容性,支持所有数据库。在具体实现上,它通过采用最大保证完成模式结合全局记录锁的方案,架构请参见 Bag 分布式事务对 SAGA 分布式事务的改进 一文。
以下为一个分布式事务的配置和演示:
public class GtxTest { SqlBoxContext[] ctx = new SqlBoxContext[3]; private static DataSource newTestDataSource() { HikariDataSource ds = new HikariDataSource(); ds.setDriverClassName("org.h2.Driver"); ds.setJdbcUrl("jdbc:h2:mem:" + new Random().nextLong() // random h2 ds name + ";MODE=MYSQL;DB_CLOSE_DELAY=-1;TRACE_LEVEL_SYSTEM_OUT=0"); ds.setUsername("sa"); ds.setPassword(""); return ds; } @Before public void init() { SqlBoxContext lock = new SqlBoxContext(newTestDataSource()); lock.setName("lock"); lock.executeDDL(lock.toCreateDDL(GtxId.class)); lock.executeDDL(lock.toCreateDDL(GtxLock.class)); lock.executeDDL(lock.toCreateGtxLogDDL(Usr.class)); GtxConnectionManager lockCM = new GtxConnectionManager(lock); for (int i = 0; i < 3; i++) { ctx[i] = new SqlBoxContext(newTestDataSource()); ctx[i].setName("db"); ctx[i].setDbCode(i); ctx[i].setConnectionManager(lockCM); ctx[i].setMasters(ctx); ctx[i].executeDDL(ctx[i].toCreateDDL(GtxTag.class)); ctx[i].executeDDL(ctx[i].toCreateDDL(Usr.class)); } } public void Div0Test() { ctx[0].startTrans(); try { new Usr().insert(ctx[0]); new Usr().insert(ctx[1]); new Usr().insert(ctx[1]); new Usr().insert(ctx[2]); System.out.println(1 / 0);//强制出错 ctx[0].commitTrans(); } catch (Exception e) { TxResult result=ctx[0].rollbackTrans(); GtxUnlockServ.forceUnlock(ctx[0], result); } Assert.assertEquals(0, ctx[0].eCountAll(Usr.class)); Assert.assertEquals(0, ctx[1].eCountAll(Usr.class)); Assert.assertEquals(0, ctx[2].eCountAll(Usr.class)); } }
上例是最简单的一个分布式事务演示,包含了数据源配置和 DDL 建表。GTX 事务如果在事务提交中出错,无论有无部分事务提交发生,只要数据库没有down机,在任意时刻网线中断,数据的最终一致性都能保证,不象 XA 协议有可能会有数据不一致的情况发生。
上例 GtxUnlockServ.forceUnlock(ctx[0], result) 方法仅用于单元测试,实际项目中应该去掉这一行,而改成用GtxUnlockServ.start(ctx, loopInterval , maxLoopQty);方法开启一个单独的解锁服务,第二个参数为解锁间隔,单位为秒,必须设置成一个远大于数据库事务超时时间的值,第三个参数可以设为 0,表示没有最大解锁次数限制。
jSqlBox 的分布式事务支持分库、分表,以及指定锁服务器的序号,这样如果没有一个高性能(云)锁服务器,可以通过配置一个锁服务器群来提高事务处理性能,当然这种情况下,锁服务器序号的指定通常是与业务相关的,例如红包转账分布式事务,可以红包的 ID 取模作为锁服务器序号。具体示例请详见 wiki 及单元测试目录下的 GtxShardDbTbLockDbTest.java
jSqlBox 分布式事务的构想几乎和 Seata 项目开源时同时提出,但从完成度来看差不多,这并不表示本人水平高,一个人干翻了 Seata,而是因为与 ORM 工具整合成一体的分布式事务,在编写难度上要远远小于Seata 这种分析 SQl 语法、从底层代理数据源的第三方工具,而且 jSqlBox 的分布式事务只支持一组直连的 DataSource,不提供微服务的调用接口。
本次更新还包括以下内容:
1. 在 demo 目录下演示了使用 Beetl 作为 SQL 模板。例如以下是一个使用 Beetl 模板的调用,通过与 Text 类的结合使用,可以方便地利用 IDE 功能快速定位到模板文本:
List<Map<String, Object>> usrs = ctx.tQueryForMapList(SelectUsers1.class, bind("age", 50, "name", null));
2. JAVA8 类中引入 $,a$,c$ 静态简化写法, 例如在 Dao-benchmark 项目中,以下这种 SQL 写法是支持实体字体名重构的:
List<DemoUser> result = gctx().eFindBySample(sample, " or ", $(u::getCode), "=?", param("efg"));
其中的$(u::getCode)静态方法会返回"code"这个实体属性名,也就是数据库的列名(通常约定数据库列名命名与实体属性名一致)
3. JDBPRO 中新增 noNull 方法,用于 like 查询时的非空判断,使用示例:
ctx.iQueryForMapList("select * from a where 1=1",noNull("and name like ?","%",name,"%"));
4. 一些其它改进:
如 TinyTx 类不再使用,改名为 TinyTxAOP,jDialects 模块中 ColumnModel 类中的 length 字段被删除,并修正了 precision 和 scale 在设定浮点数的错误。
5. 另外以下与 jSqlBox 相关的子项目也一并更新到 3.0.0 版,就不再另发更新资讯了,有以下子项目:
jDialects 支持80多种数据库的分页、类型变换、DDL 生成、实体源码生成的数据库方言工具
jTransactions 一个单独的事务工具,含分布式事务功能
jDbPro 这是 jSqlBox 的内核,建立在 DbUtils 基础上,只有 SQL 功能,不包含 ORM 等功能
MyFat 这是一个 Mybatis 插件,它将 jSqlBox 和 MyBatis 捆绑在一起,利用 jSqlBox 来补足 Mybatis 缺少的实体 CRUD 等功能
欢迎发issue提出更好的意见或提交PR,帮助完善jSqlBox
Apache 2.0