Profilbasierte Verwendung von Spring-Liquibase

In diesem Post geht es darum, wie man die Liquibase-Bean für Tests benutzt, ohne jedoch das Produktivsystem automatischen Schema-Änderungen zu unterwerfen. Für Tests soll hierbei H2 Embedded benutzt werden und für den Produktivbetrieb eine PostgreSQL-Datenbank.

Die Problematik, die sich hierbei ergibt, ist, Liquibase zwischen der Erzeugung der DataSource und der EntityManagerFactory auszuführen, aber nur im Test-Profil.

Sofern dies gelöst ist, ist es möglich, Tests unabhängig durchzuführen. Die Tests erstellen hierbei die Datenbank per Spring und Liquibase bei jeder Ausführung neu.

Daraus ergibt sich, dass die Tests immer das neuste Schema sehen und keine Datenbank für Tests manuell gepflegt werden muss.

Sofern man mit Liquibase nicht vertraut ist, empfiehlt es sich, kurz Liquibase: Best Practices durchzulesen. Zusätzlich ist Kenntnis der Profile-Funktionalität von Spring erforderlich.

In diesem Post wird die Spring-Konfiguration in XML angegeben. Ein vergleichbarer Ansatz ist auch bei programmatischer Konfiguration möglich. Hierbei muss jedoch die Liquibase-Bean unbedingt per Identifier statt über die Klasse aufgelöst werden.

Die DataSource erstellen wir in diesem Beispiel mit Hibernate (Postgres für “productive” und H2 Embedded für “test”). Zusätzlich wollen wir, dass Hibernate im Test-Profil das SQL loggt.

<beans profile="productive">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="dbConnectionUrl" />
        <property name="username" value="dbUsername" />
        <property name="password" value="dbPassword" />
    </bean>
    <util:properties id="jpaProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
        <prop key="hibernate.hbm2ddl.auto">validate</prop>
    </util:properties>
</beans>
<beans profile="test">
    <jdbc:embedded-database id="dataSource" type="H2" />
    <util:properties id="jpaProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
        <prop key="hibernate.hbm2ddl.auto">validate</prop>
        <prop key="hibernate.format_sql">true</prop>
        <prop key="hibernate.show_sql">true</prop>
    </util:properties>
</beans>

Jetzt kommt der interessante Teil: die Erstellung der EntityManagerFactory. Es werden die DataSource und die jpaProperties, die profilabhängig erzeugt wurden, benutzt. Zusätzlich wird die Bean liquibase als Abhängigkeit angegeben.

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
depends-on="liquibase">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties" ref="jpaProperties" />
</bean>

Jetzt wird die Bean liquibase noch benötigt. Hierbei ist ein kleiner Trick notwendig, da diese nur im Test-Profil benutzt werden darf. Es wird lediglich ein String erzeugt und als liquibase registriert, damit Spring die Abhängigkeit auf liquibase erfüllen kann, ohne die echte Liquibase-Bean auszuführen.

<beans profile="productive">
    <!-- Dummy liquibase for dependencies in non testcontext -->
    <bean id="liquibase" class="java.lang.String" />
</beans>

Zuletzt noch die echte Liquibase-Bean:

<beans profile="test">
    <bean id="liquibase" class="liquibase.integration.spring.SpringLiquibase">
        <property name="dataSource" ref="dataSource" />
        <property name="changeLog" value="classpath:liquibase/myMasterChangelog.xml" />
    </bean>
</beans>

Zusammengesetzt sieht dies dann so aus:

<beans profile="productive">
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.postgresql.Driver" />
        <property name="url" value="dbConnectionUrl" />
        <property name="username" value="dbUsername" />
        <property name="password" value="dbPassword" />
    </bean>
    <util:properties id="jpaProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
        <prop key="hibernate.hbm2ddl.auto">validate</prop>
    </util:properties>
    <bean id="liquibase" class="java.lang.String" />
</beans>
<beans profile="test">
    <jdbc:embedded-database id="dataSource" type="H2" />
    <util:properties id="jpaProperties">
        <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
        <prop key="hibernate.hbm2ddl.auto">validate</prop>
        <prop key="hibernate.format_sql">true</prop>
        <prop key="hibernate.show_sql">true</prop>
    </util:properties>
    <bean id="liquibase" class="liquibase.integration.spring.SpringLiquibase">
        <property name="dataSource" ref="dataSource" />
        <property name="changeLog" value="classpath:liquibase/myMasterChangelog.xml" />
    </bean>
</beans>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"
depends-on="liquibase">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" />
    </property>
    <property name="jpaProperties" ref="jpaProperties" />
</bean>

Ein Vorteil von diesem Vorgehen zeigt sich insbesondere wenn noch mehr verschiedene Ausführungskontexte existieren, da nur der wirklich unterschiedliche Teil dupliziert werden muss und die Definition der EntityManagerFactory nur einmal zentral vorkommt.

Short URL for this post: http://wp.me/p4nxik-2F0
This entry was posted in Java Persistence, Spring Universe and tagged , , , . Bookmark the permalink.

Leave a Reply