Using JPA/Hibernate in CustomTaskChange

Hi there,

I’m currently trying to incorporate Liquibase in our project. We use JPA / Hibernate and our domain model is rather complicated. We had to create some initial data in our database, and instead of doing it using SQL I thought of doing it using a CustomTaskChange.

The first problem was that the EntityManagerFactory likes to have a connection specified in a configuration file, but I didn’t want to have that information duplicated, so I solved it using an Hibernate ConnectionProvider built inside CustomTaskChange.execute:

    public class LiquibaseConnectionProvider implements ConnectionProvider {     public static final String LIQUIBASE_CONNECTION = "xxxx.commons.db.liquibase_database";

        protected Database database;

        @Override
        public void close() throws HibernateException {
        }

        @Override
        public void closeConnection(Connection connection) throws SQLException {
        }

        @Override
        public void configure(Properties properties) throws HibernateException {
            this.database = (Database) properties.get(LiquibaseConnectionProvider.LIQUIBASE_CONNECTION);
        }

        @Override
        public Connection getConnection() throws SQLException {
            return this.database.getConnection().getUnderlyingConnection();
        }

        @Override
        public boolean supportsAggressiveRelease() {
            return false;
        }
    }

    public class DataCreation implements CustomTaskChange
      {
      @Override
      public void execute( Database database ) throws CustomChangeException, UnsupportedChangeException
        {
        Map<String, Object> properties = new HashMap<String, Object>( );
        properties.put( Environment.DIALECT, “whatever.your.dialect” );
        properties.put( Environment.CONNECTION_PROVIDER, “com.example.package.LiquibaseConnectionProvider” );
        properties.put( LiquibaseConnectionProvider.LIQUIBASE_CONNECTION, database );

        EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory( “gloxr”, properties );

        EntityManager entityManager = entityManagerFactory.createEntityManager( );
        … // I guess you get the point
        }

This way I’m able to create an entity manager that uses the database connection provided by Liquibase. The problem with this approach is that I have to create the EntityManagerFactory every time a CustomTaskChange is called. This is a costly operation (specially when you have a rich domain model), and it only makes sense to do it once during a Liquibase database update.

I was thinking on creating some kind of singleton that would initialize the EntityManagerFactory on first call, and then would return that factory to any subsequent call, but first of all, I wasn’t sure Liquibase would always use the same connection to do all the operations on the database, and then I thought that what I’d really like is Liquibase to provide this kind of integration: I mean, if used with Hibernate or JPA, Liquibase could initialize the EntityManagerFactory, and pass the EntityManager as a parameter to the execute method on CustomTaskChange (or JPACustomTaskChange).

What do you think? Is there any workaround using the current API, maybe what I mentioned about using a singleton and assuming the Database object will always be the same?

Best regards
Jos�

Yes, the connection object should always be the same. 

I’m not sure it’s something we would want by default since liquibase shouldn’t really know anything about hibernate.  Plus, would we want to pass EntityManager or SessionManager?  It would be something useful to create and add on the http://liquibase.org/extensions for those that need it, though.

One general concern I have with the approach you are looking at, is that hibernate usually expects the database to match with it’s mapped model, and if you are spinning up your entity manager before the database is fully up to date it could make hibernate unhappy.

Nathan

Hi Nathan,

I neither think Liquibase should do this by default, but I think it would be useful to provide this functionality for JPA / Hibernate (or any other ORM tool) based projects, when the user requests it using some startup parameters. I’m involved in a project with a rather complicated domain model (knowledge and operationl levels, heavily based on tree structures…) and in order to create the data to be initially loaded to the database I had to create a program that would create this initial data in the database using Hibernate, run it against my development database, export the data, properly order the generated SQL to take into account foreign keys, and then include the script in Liquibase. The process was error prone, and I couldn’t help to feel that I was doing the same thing twice without need.

Regarding EntityManager vs Session, I would favor the first based on two reasons:

  • EntityManager is part of the JPA standard and provider agnostic, this is, if properly configured, anybody using JPA with whatever provider would benefit from this
  • The Hibernate EntityManager provides automatic scanning of annotated entitites in any jar containing a persistence.xml file, even in a JavaSE environment (JPA only mandates this behavior in JavaEE environments), so the configuration is a lot easier than using a SessionFactory, where you must explicitly specify the mappging files or annotated entities

Hibernate does expect the database to match it’s mapped model, but it only checks it at EntityManagerFactory or SessionFactory creation time if you specify hibernate.hbm2ddl.auto=validate in the Hibernate configuration (Table 3.7. Miscellaneous Properties in Optional configuration properties). I think there would be no problem creating the EntityManagerFactory before the database is updated.

Any pointer to documentation or tips on how to develop sunch an extension?

Thanks, best regards
Jos�

I’m going to be on on vacation until Aug 29th, so I’ll get back to you after then.  Sorry I wasn’t able to give you write more beforehand.  Ended up with too much going on…

Nathan

I have successfully build a custom task change that configures Hibernate via Spring. 


Within the setup for my CustomTaskChange i create a GenericApplicationContext() and load my Hibernate-baed DAO from that context.


 Does that fit your application?



Mark

It seems like you are looking for a way to initialize a new database with some initial data.  If this is the case, then you would only perform this step after all of the other changesets have been applied.  In this case, hibernate will not have any problems because the database is in the exact state that it expects.


Just for the sake of the discussion, let’s say that the version of your application with includes the changeset to create the initial set of data is version 1.0.  Let’s also assume that your initial set of data does not change from 1.0 to 1.1. One more thing, one of the tables that contains the initial set of data has a new required column.


Upgrading to 1.1 is not a problem because liquibase will not attempt to execute changesets that have already been applied.  A new install of 1.1 is more complicated because you must first install version 1.0.  After 1.0 has been installed, you can install 1.1.  The reason for forcing two installs is that your application logic (version 1.1) using hibernate to populate the database will fail against a 1.0 database because it expected that new column to exist.


This problem is not specific to hibernate either.  This problem exist with calling any application logic from liquibase.  If that application logic changes, you now have some versions of your application that can not be skipped during initial installations or upgrades.


A few things to consider…


If you are just creating the initial set of data in version 1.0 and using liquibase tags to manipulate it (and user created application data) in future releases, calling your application logic during the installation is not needed at all.  Since the creation of this initial set of data is a one time event, do it once in your development database.  You can then use liquibase to generate a changelog containing the data.  All that is needed at this point is to include that change log before your foreign keys are created as part of the initial install.


You could also just call your application logic on startup after liquibase has finished…