DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Schema Change Management Tools: A Practical Overview
  • Architectural Miscalculation and Hibernate Problem "Type UUID but Expression Is of Type Bytea"
  • Common Mistakes to Avoid When Writing SQL Code
  • Data-Based Decision-Making: Predicting the Future Using In-Database Machine Learning

Trending

  • Decoding Business Source Licensing: A New Software Licensing Model
  • Chronicle Services: Low Latency Java Microservices Without Pain
  • Hugging Face Is the New GitHub for LLMs
  • Log Analysis Using grep
  1. DZone
  2. Data Engineering
  3. Databases
  4. Rapid Development with Hibernate in CQRS Read Models

Rapid Development with Hibernate in CQRS Read Models

Problems keeping relational schema and Java classes in sync? or just migrating to a new schema? Hibernate with CQRS can help. Here's how.

Konrad Garus user avatar by
Konrad Garus
·
Oct. 07, 15 · Tutorial
Like (4)
Save
Tweet
Share
7.49K Views

Join the DZone community and get the full member experience.

Join For Free

in this post i’m going to share a few tricks for using hibernate tooling in cqrs read models for rapid development.

why hibernate?

hibernate is extremely popular. it’s also deceptively easy on the outside and fairly complex on the inside. it makes it very easy get started without in-depth understanding, misuse, and discover problems when it’s already too late. for all these reasons these days it’s rather infamous.

however, it still is a piece of solid and mature technology. battle-tested, robust, well-documented and having solutions to many common problems in the box. it can make you *very* productive. even more so if you include tooling and libraries around it. finally, it is safe as long as you know what you’re doing.

automatic schema generation

keeping sql schema in sync with java class definitions is rather expensive a bit of a struggle. in the best case it’s very tedious and time-consuming activity. there are numerous opportunities for mistakes.

hibernate comes with a schema generator (hbm2ddl), but in its “native” form is of limited use in production. it can only validate the schema, attempt an update or export it, when the sessionfactory is created. fortunately, the same utility is available for custom programmatic use.

we went one step further and integrated it with cqrs projections. here’s how it works:

  • when the projection process thread starts, validate whether db schema matches the java class definitions.
  • if it does not, drop the schema and re-export it (using hbm2ddl). restart the projection, reprocessing the event store from the very beginning. make the projection start from the very beginning.
  • if it does match, just continue updating the model from the current state.

thanks to this, we almost never type sql with table definitions by hand. it makes development a lot faster. it’s similar to working with hbm2ddl.auto = create-drop . however, using this in a view model means it does not actually lose data (which is safe in the event store). also, it’s smart enough to only recreate the schema if it’s actually changed – unlike the create-drop strategy.

preserving data and avoiding needless restarts does not only improve the development cycle. it also may make it usable in production. at least under certain conditions, see below.

there is one caveat: not all changes in the schema make the hibernate validation fail. one example is changing field length – as long as it’s varchar or text, validation passes regardless of limit. another undetected change is nullability.

these issues can be solved by restarting the projection by hand (see below). another possibility is having a dummy entity that doesn’t store data, but is modified to trigger the automatic restart. it could have a single field called schemaversion , with @column(name = "v_4") annotation updated (by developer) every time the schema changes.

implementation

here’s how it can be implemented:


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();
    }
}


the api looks pretty dated and cumbersome. there does not seem to be a way to extract configuration from the existing sessionfactory . it’s only something that’s used to create the factory and thrown away. we have to recreate it from scratch. the above is all we needed to make it work well with spring boot and l2 cache.

restarting projections

we’ve also implemented a way to perform such a reinitialization manually, exposed as a button in the admin console. it comes in handy when something about the projection changes but does not involve modifying the schema. for example, if a value is calculated/formatted differently, but it’s still a text field, this mechanism can be used to manually have the history reprocessed. another use case is fixing a bug.

production use?

we’ve been using this mechanism with great success during development. it let us freely modify the schema by only changing the java classes and never worrying about table definitions. thanks to combination with cqrs, we could even maintain long-running demo or pilot customer instances. data has always been safe in the event store. we could develop the read model schema incrementally and have the changes automatically deployed to a running instance, without data loss or manually writing sql migration scripts.

obviously this approach has its limits. reprocessing the entire event store at random point in time is only feasible on very small instances or if the events can be processed fast enough.

otherwise the migration might be solved using an sql migration script, but it has its limits. it’s often risky and difficult. it may be slow. most importantly, if the changes are bigger and involve data that was not previously included in the read model (but is available in the events), using an sql script simply is not an option.

a much better solution is to point the projection (with new code) to a new database. let it reprocess the event log. when it catches up, test the view model, redirect traffic and discard the old instance. the presented solution works perfectly with this approach as well.

this post also appeared on the oasis digital blog .

Database Hibernate Schema IT

Published at DZone with permission of Konrad Garus, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Schema Change Management Tools: A Practical Overview
  • Architectural Miscalculation and Hibernate Problem "Type UUID but Expression Is of Type Bytea"
  • Common Mistakes to Avoid When Writing SQL Code
  • Data-Based Decision-Making: Predicting the Future Using In-Database Machine Learning

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: