使用Hibernate在CQRS读取模型中进行快速开发

在这篇文章中,我将分享一些在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缓存一起正常工作。

重新开始投影

我们还实现了一种手动执行此类重新初始化的方法,在管理控制台中以按钮形式显示。 当有关投影的某些内容发生更改但不涉及修改架构时,它会派上用场。 例如,如果值的计算/格式不同,但仍是文本字段,则可以使用此机制来手动重新处理历史记录。 另一个用例是修复错误。

生产用途?

cqrs_hibernate

在开发过程中,我们一直在成功使用这种机制。 它使我们可以通过仅更改Java类而不用担心表定义来自由地修改模式。 由于与CQRS结合使用,我们甚至可以维护长期运行的演示或试点客户实例。 数据始终在事件存储区中是安全的。 我们可以逐步开发读取模型架构,并将更改自动部署到正在运行的实例中,而不会丢失数据或手动编写SQL迁移脚本。

显然,这种方法有其局限性。 仅在很小的情况下或事件可以足够快速地处理时,才可以在随机的时间点重新处理整个事件存储。

否则,可以使用SQL迁移脚本解决迁移问题,但是它有其局限性。 这通常是冒险且困难的。 可能会很慢。 最重要的是,如果更改较大并且涉及以前未包含在读取模型中(但事件中可用)的数据,则根本不选择使用SQL脚本。

更好的解决方案是将投影(带有新代码)指向新数据库。 让它重新处理事件日志。 当它赶上来时,请测试视图模型,重定向流量并丢弃旧实例。 提出的解决方案也与此方法完美配合。

翻译自: https://www.javacodegeeks.com/2015/10/rapid-development-with-hibernate-in-cqrs-read-models.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值