使用Hibernate在CQRS读取模型中进行快速开发
在這篇文章中,我將分享一些在CQRS讀取模型中使用Hibernate工具進(jìn)行快速開發(fā)的技巧。
為什么要休眠?
休眠非常流行。 從外觀上看,它也很容易,而從內(nèi)部看,它卻相當(dāng)復(fù)雜。 它可以很容易地開始使用,而無需進(jìn)行深入了解,濫用和發(fā)現(xiàn)問題,如果為時已晚。 由于所有這些原因,這幾天真是臭名昭著。
但是,它仍然是一項(xiàng)堅(jiān)實(shí)而成熟的技術(shù)。 經(jīng)過實(shí)戰(zhàn)測試,功能強(qiáng)大,文檔完善,并且可以解決許多常見問題。 它可以使您*非常*高效。 如果包括工具和庫,則更多。 最后,只要您知道自己在做什么,它就是安全的。
自動模式生成
使SQL模式與Java類定義保持同步相當(dāng)麻煩。 在最佳情況下,這是非常繁瑣且耗時的活動。 錯誤的機(jī)會很多。
Hibernate帶有模式生成器(hbm2ddl),但其“本機(jī)”形式在生產(chǎn)中使用有限。 創(chuàng)建SessionFactory時,它只能驗(yàn)證架構(gòu),嘗試更新或?qū)С黾軜?gòu)。 幸運(yùn)的是,該實(shí)用程序可用于自定義編程用途。
我們進(jìn)一步走了一步,并將其與CQRS預(yù)測集成在一起。 運(yùn)作方式如下:
- 當(dāng)投影過程線程啟動時,請驗(yàn)證數(shù)據(jù)庫模式是否與Java類定義匹配。
- 如果不是,請刪除該架構(gòu)并重新導(dǎo)出(使用hbm2ddl)。 重新啟動投影,從一開始就重新處理事件存儲。 使投影從一開始就開始。
- 如果匹配,則繼續(xù)從當(dāng)前狀態(tài)更新模型。
由于這個原因,在很多時候,您幾乎不必手動輸入帶有表定義的SQL。 它使開發(fā)速度大大加快。 這類似于使用hbm2ddl.auto = create-drop 。 但是, 在視圖模型中使用它意味著它實(shí)際上不會丟失數(shù)據(jù) (這在事件存儲中是安全的)。 而且,它足夠聰明,僅在實(shí)際更改架構(gòu)時才重新創(chuàng)建架構(gòu)-與創(chuàng)建-放置策略不同。
保留數(shù)據(jù)并避免不必要的重新啟動不僅會縮短開發(fā)周期。 它還可能使其在生產(chǎn)中可用。 至少在某些條件下,請參見下文。
有一個警告:并非所有架構(gòu)更改都會使Hibernate驗(yàn)證失敗。 一個示例是更改字段長度–只要是varchar或文本,驗(yàn)證就可以通過而不受限制。 另一個未發(fā)現(xiàn)的變化是可空性。
這些問題可以通過手動重新啟動投影來解決(請參見下文)。 另一種可能性是擁有一個不存儲數(shù)據(jù)的偽實(shí)體,但對其進(jìn)行了修改以觸發(fā)自動重啟。 它可能只有一個名為schemaVersion字段,每次架構(gòu)更改時, @Column(name = "v_4") schemaVersion @Column(name = "v_4")批注(由開發(fā)人員)都會更新。
實(shí)作
實(shí)施方法如下:
public class HibernateSchemaExporter {private final EntityManager entityManager;public HibernateSchemaExporter(EntityManager entityManager) {this.entityManager = entityManager;}public void validateAndExportIfNeeded(List<Class> entityClasses) {Configuration config = getConfiguration(entityClasses);if (!isSchemaValid(config)) {export(config);}}private Configuration getConfiguration(List<Class> entityClasses) {SessionFactoryImplementor sessionFactory = (SessionFactoryImplementor) getSessionFactory();Configuration cfg = new Configuration();cfg.setProperty("hibernate.dialect", sessionFactory.getDialect().toString());// Do this when using a custom naming strategy, e.g. with Spring Boot:Object namingStrategy = sessionFactory.getProperties().get("hibernate.ejb.naming_strategy");if (namingStrategy instanceof NamingStrategy) {cfg.setNamingStrategy((NamingStrategy) namingStrategy);} else if (namingStrategy instanceof String) {try {log.debug("Instantiating naming strategy: " + namingStrategy);cfg.setNamingStrategy((NamingStrategy) Class.forName((String) namingStrategy).newInstance());} catch (ReflectiveOperationException ex) {log.warn("Problem setting naming strategy", ex);}} else {log.warn("Using default naming strategy");}entityClasses.forEach(cfg::addAnnotatedClass);return cfg;}private boolean isSchemaValid(Configuration cfg) {try {new SchemaValidator(getServiceRegistry(), cfg).validate();return true;} catch (HibernateException e) {// Yay, exception-driven flow!return false;}}private void export(Configuration cfg) {new SchemaExport(getServiceRegistry(), cfg).create(false, true);clearCaches(cfg);}private ServiceRegistry getServiceRegistry() {return getSessionFactory().getSessionFactoryOptions().getServiceRegistry();}private void clearCaches(Configuration cfg) {SessionFactory sf = entityManager.unwrap(Session.class).getSessionFactory();Cache cache = sf.getCache();stream(cfg.getClassMappings()).forEach(pc -> {if (pc instanceof RootClass) {cache.evictEntityRegion(((RootClass) pc).getCacheRegionName());}});stream(cfg.getCollectionMappings()).forEach(coll -> {cache.evictCollectionRegion(((Collection) coll).getCacheRegionName());});}private SessionFactory getSessionFactory() {return entityManager.unwrap(Session.class).getSessionFactory();} }該API看起來過時且繁瑣。 似乎沒有辦法從現(xiàn)有的SessionFactory提取Configuration 。 這只是用來創(chuàng)建工廠并扔掉的東西。 我們必須從頭開始重新創(chuàng)建它。 以上是我們需要的所有內(nèi)容,以使其與Spring Boot和L2緩存一起正常工作。
重新開始投影
我們還實(shí)現(xiàn)了一種手動執(zhí)行此類重新初始化的方法,在管理控制臺中以按鈕形式顯示。 當(dāng)有關(guān)投影的某些內(nèi)容發(fā)生更改但不涉及修改架構(gòu)時,它會派上用場。 例如,如果值的計(jì)算/格式不同,但仍是文本字段,則可以使用此機(jī)制來手動重新處理歷史記錄。 另一個用例是修復(fù)錯誤。
生產(chǎn)用途?
在開發(fā)過程中,我們一直在成功使用這種機(jī)制。 它使我們可以通過僅更改Java類而不用擔(dān)心表定義來自由地修改模式。 由于與CQRS結(jié)合使用,我們甚至可以維護(hù)長期運(yùn)行的演示或試點(diǎn)客戶實(shí)例。 數(shù)據(jù)始終在事件存儲區(qū)中是安全的。 我們可以逐步開發(fā)讀取模型架構(gòu),并將更改自動部署到正在運(yùn)行的實(shí)例中,而不會丟失數(shù)據(jù)或手動編寫SQL遷移腳本。
顯然,這種方法有其局限性。 僅在很小的情況下或事件可以足夠快速地處理時,才可以在隨機(jī)的時間點(diǎn)重新處理整個事件存儲。
否則,可以使用SQL遷移腳本解決遷移問題,但是它有其局限性。 這通常是冒險且困難的。 可能會很慢。 最重要的是,如果更改較大并且涉及以前未包含在讀取模型中(但事件中可用)的數(shù)據(jù),則根本不選擇使用SQL腳本。
更好的解決方案是將投影(帶有新代碼)指向新數(shù)據(jù)庫。 讓它重新處理事件日志。 當(dāng)它趕上來時,請測試視圖模型,重定向流量并丟棄舊實(shí)例。 提出的解決方案也與此方法完美配合。
翻譯自: https://www.javacodegeeks.com/2015/10/rapid-development-with-hibernate-in-cqrs-read-models.html
總結(jié)
以上是生活随笔為你收集整理的使用Hibernate在CQRS读取模型中进行快速开发的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Apache Spark中实现的MapR
- 下一篇: 亚型多态性应用于元组的危险