在这篇文章中,我将分享一些在CQRS读取模型中使用Hibernate工具进行快速开发的技巧。
为什么要休眠?
休眠非常流行。 从外观上看,它也很容易,而从内部看,它却相当复杂。 它可以很容易地开始使用,而无需进行深入了解,滥用和发现问题,如果为时已晚。 由于所有这些原因,这几天真是臭名昭著。
但是,它仍然是一项坚实而成熟的技术。 经过实战测试,功能强大,文档完善,并且可以解决许多常见问题。 它可以使您*非常*高效。 如果包括工具和库,则更多。 最后,只要您知道自己在做什么,它就是安全的。
自动模式生成
使SQL模式与Java类定义保持同步相当麻烦。 在最佳情况下,这是非常繁琐且耗时的活动。 错误的机会很多。
Hibernate带有模式生成器(hbm2ddl),但其“本机”形式在生产中使用有限。 创建SessionFactory
时,它只能验证架构,尝试更新或导出架构。 幸运的是,该实用程序可用于自定义编程用途。
我们进一步走了一步,并将其与CQRS预测集成在一起。 运作方式如下:
- 当投影过程线程启动时,请验证数据库模式是否与Java类定义匹配。
- 如果不是,请删除该架构并重新导出(使用hbm2ddl)。 重新启动投影,从一开始就重新处理事件存储。 使投影从一开始就开始。
- 如果匹配,则继续从当前状态更新模型。
由于这个原因,在很多时候,您几乎不必手动输入带有表定义的SQL。 它使开发速度大大加快。 这类似于使用hbm2ddl.auto = create-drop
。 但是, 在视图模型中使用它意味着它实际上不会丢失数据 (这在事件存储中是安全的)。 而且,它足够聪明,仅在实际更改架构时才重新创建架构-与创建-放置策略不同。
保留数据并避免不必要的重新启动不仅会缩短开发周期。 它还可能使其在生产中可用。 至少在某些条件下,请参见下文。
有一个警告:并非所有架构更改都会使Hibernate验证失败。 一个示例是更改字段长度–只要是varchar或文本,验证就可以通过而不受限制。 另一个未发现的变化是可空性。
这些问题可以通过手动重新启动投影来解决(请参见下文)。 另一种可能性是拥有一个不存储数据的伪实体,但对其进行了修改以触发自动重启。 它可能只有一个名为schemaVersion
字段,每次架构更改时, @Column(name = "v_4")
schemaVersion
@Column(name = "v_4")
批注(由开发人员)都会更新。
实作
实施方法如下:
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看起来过时且繁琐。 似乎没有办法从现有的SessionFactory
提取Configuration
。 这只是用来创建工厂并扔掉的东西。 我们必须从头开始重新创建它。 以上是我们需要的所有内容,以使其与Spring Boot和L2缓存一起正常工作。
重新开始投影
我们还实现了一种手动执行此类重新初始化的方法,在管理控制台中以按钮形式显示。 当有关投影的某些内容发生更改但不涉及修改架构时,它会派上用场。 例如,如果值的计算/格式不同,但仍是文本字段,则可以使用此机制来手动重新处理历史记录。 另一个用例是修复错误。
生产用途?
在开发过程中,我们一直在成功使用这种机制。 它使我们可以通过仅更改Java类而不用担心表定义来自由地修改模式。 由于与CQRS结合使用,我们甚至可以维护长期运行的演示或试点客户实例。 数据始终在事件存储区中是安全的。 我们可以逐步开发读取模型架构,并将更改自动部署到正在运行的实例中,而不会丢失数据或手动编写SQL迁移脚本。
显然,这种方法有其局限性。 仅在很小的情况下或事件可以足够快速地处理时,才可以在随机的时间点重新处理整个事件存储。
否则,可以使用SQL迁移脚本解决迁移问题,但是它有其局限性。 这通常是冒险且困难的。 可能会很慢。 最重要的是,如果更改较大并且涉及以前未包含在读取模型中(但事件中可用)的数据,则根本不选择使用SQL脚本。
更好的解决方案是将投影(带有新代码)指向新数据库。 让它重新处理事件日志。 当它赶上来时,请测试视图模型,重定向流量并丢弃旧实例。 提出的解决方案也与此方法完美配合。
翻译自: https://www.javacodegeeks.com/2015/10/rapid-development-with-hibernate-in-cqrs-read-models.html