jSqlBox 2.0.6 釋出,輕鬆解決分庫分表事務
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演化而來,本身就存在著基於容器、過度複雜的問題。
2. 更正了多資料來源及分庫情況下不支援事務的bug:
在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,雖然麻煩一點,但可能通用性和效能會更好一點。
作者主要開源專案 | Other Projects
期望 | Futures
歡迎發issue提出更好的意見或提交PR,幫助完善jSqlBox