jSqlBox是一个Java持久层工层,2.0.6版发布,主要有两个更新:
1.添加了对以下三个JPA注解的支持:
@Version 乐观锁注解
@Enumerated 枚举字段注解
@Convert 自定义字段转换器
具体的用法可参考jSqlBox的[ 用户手册 ],对于以操纵SQL为基础的常见DAO工具来说,jSqlBox可能算是支持标准JPA注解比较多的了,目前它已支持的JPA注解达到以下15个,这些注解既可以使用JPA的,也可以使用jSqlBox自带的同名注解:
```
@GeneratedValue,@GenerationType,@Id,@Index,@SequenceGenerator,@Table,@TableGenerator,@Transient,@UniqueConstraint,@Version,@Column,@Convert,@Entity,@Enumerated,@EnumType
```
至于完整的JPA注解有上百个,不可能也没必要完全去支持,因为作者本人认为JPA从Hibernate演化而来,本身就存在着基于容器、过度复杂的问题。
在jSqlBox2.0.5及之前版本,存在一个bug:当SqlBoxContext实体被作为参数传递时,例如:
ctx1.iExecute(ctx2,'insert into users (userid,name) value(...)'
它将会从ctx1切换到ctx2上工作,也就是数据源被切换了,但问题是,如果上述语句是在一个@TX之类的自定义声明式事务注解控制下,它的事务怎么处理? 2.0.5版及以前对这个问题考虑不周,这种情况下ctx2依然工作在自动提交模式,不受事务控制,显然这是一个严重的Bug。
从2.0.6版起,jSqlBox在jTransaction模块(子项目)增加了一个GroupTx类,这个类的作用是实现多数据源事务,解决了SqlBoxContext被作为参数传递时不受事务控制的问题。这个GroupTx事务不是分布式事务,只保证一致回滚,但不保证一致提交,也就是说,当有一组SqlBoxContext同时在GroupTx控制的方法里进行数据存取时,是不安全的,但是如果只有其中任意一个(具体那一个不知道)数据源进行提交,则是100%数据一致性安全的。 GroupTx最适用的场合是Sharding,因为在Sharding场景下,任一个SQL都会可能在一群SqlBoxContext中根据实体的Sharding键(jSqlBox要求是主键)切换,但是因为业务优化设计的原因,通常所有相关的业务数据都会保存在同一个数据库中,也就是说会收敛到单个数据源上进行操作,也就充分发挥了GroupTx的特点,实现了安全性。 Sharding和分布式事务都是用来解决大数据量的手段, 如果业务采用了Sharding方案,往往可以在支持大数据量的前题下,不制造出分布式事务问题,当然这对业务设计、持久层工具的要求比较高,所有业务、实体和SQL存取都要考虑分库分表情况,和开发单机应用不太一样。如果采用微服务架构,把所有订单放在一个库里,所有用户放在另一个库里,则编程虽然简单,但却制造出了微服务之间的分布式事务问题。
GroupTx的使用示例如下:(完整源码见jSqlBox/core/目录下的GroupTxTest.java)
public class GroupTxTest { GroupTxConnectionManager gm; HikariDataSource ds1; HikariDataSource ds2; SqlBoxContext ctx1; SqlBoxContext ctx2; @Before public void init() { ds1 = new HikariDataSource(); (略去部分内容) gm = new GroupTxConnectionManager(ds1, ds2); ctx1 = new SqlBoxContext(ds1); ctx1.setConnectionManager(gm);//设定GroupTxConnectionManager ctx2 = new SqlBoxContext(ds2); ctx2.setConnectionManager(gm);//设定GroupTxConnectionManager } @Test public void groupRollbackTest() { // test group roll back for (int i = 0; i < 100; i++) { gm.startGroupTransaction(); try { Assert.assertEquals(100, ctx1.eCountAll(Usr.class)); new Usr().putField("firstName", "Foo").insert(ctx1); Assert.assertEquals(101, ctx1.eCountAll(Tail.class, tail("users"))); Assert.assertEquals(100, ctx2.eCountAll(Usr.class)); new Usr().putField("firstName", "Foo").insert(ctx2); Assert.assertEquals(101, ctx2.eCountAll(Tail.class, tail("users"))); System.out.println(1 / 0); // 除0,事务回滚! gm.commitGroupTx(); } catch (Exception e) { gm.rollbackGroupTx(); } Assert.assertEquals(100, ctx1.eCountAll(Tail.class, tail("users"))); Assert.assertEquals(100, ctx2.eCountAll(Tail.class, tail("users"))); } } @Test public void groupCommitTest() { // test group commit for (int i = 0; i < 100; i++) { gm.startGroupTransaction(); try { new Usr().putField("firstName", "Foo").insert(ctx1); ctx1.eInsert(new Usr().setFirstName("Foo"), ctx2); new Usr().putField("firstName", "Bar").insert(ctx2); gm.commitGroupTx(); //两个都提交,但不可信任,因为。。。 } catch (Exception e) { gm.rollbackGroupTx(); } } } @Test public void groupPartialCommitTest() { // simulate partial commit test Assert.assertEquals(100, ctx1.eCountAll(Tail.class, tail("users"))); gm.startGroupTransaction(); try { new Usr().putField("firstName", "Foo").insert(ctx1); new Usr().putField("firstName", "Foo").insert(ctx2); ds2.close();//看到没有,ds2关闭了,但是ctx1还是提交成功了 gm.commitGroupTx(); } catch (Exception e) { gm.rollbackGroupTx(); } Assert.assertEquals(101, ctx1.eCountAll(Tail.class, tail("users"))); } }
以上示例演示了手工进行GroupTx事务的开启、提交和回滚,示例说明了如果有两个ctx同时进行提交,在调用commitGroupTx时,如果运行期异常发生,会回步回滚,这个没有问题,但是如果一个数据提交出错,另一个没有,就可能造成部分提交的情况,这就是GroupTx的局限,所以只能用在多打一的情况下,也就是说通常最适合它的场景是Sharding事务,最终生效的实际数据源会收敛到单个数据源上操作。
以上是手工进行GroupTx事务的配置和解说,关于声明式方式的配置使用和解说因篇幅问题请详见jSqlBox的wiki用户手册。
jSqlBox下一步计划是加入分布式TCC事务支持,补上这项一直缺少的功能,初步打算是加入Fescar的支持,这是阿里最近开源出来的项目,还没成熟,它的特点是对业务入侵小,通过分析SQL来自动创建回滚SQL。 不过个人感觉Fescar可能会存在性能问题和兼容性问题(非常类似于jdbc-sharding所面对的问题,它也是通过分析SQL语法来支持Sharding),如果由持久层工具来创建回滚SQL,虽然麻烦一点,但可能通用性和性能会更好一点。
基于DbUtils内核的持久层工具 jSqlBox
数据库方言工具 jDialects
独立的声明式事务工具 jTransactions
微型IOC/AOP工具 jBeanBox
服务端布局工具 jWebBox
欢迎发issue提出更好的意见或提交PR,帮助完善jSqlBox
Apache 2.0