Configuring Multiple JPA Entity Managers In Spring Boot

This blog will demonstrate how to setup multiple entity managers in Spring to connect to different data sources. The solution here also supports Spring Data.

Update Maven Pom file

Include Spring Boot dependency for Spring Data:

 <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-data-jpa</artifactId>
 </dependency>

Disable DataSourceAutoConfiguration

Since we are setting up the data sources, disable the auto configuration in Spring Boot

@Configuration
@ComponentScan
@EnableAutoConfiguration (exclude = {  DataSourceAutoConfiguration.class })
public class Application {
   ...

Configure Primary Entity Manager

Below is the Java configuration for the primary entity manager

@Profile("!test")          // 1
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "au.com.myblog.dao", entityManagerFactoryRef = "entityManager", transactionManagerRef = "transactionManager")        // 2
public class PrimaryMysqlDBConfiguration {
     @Bean(name = "dataSource")      // 3
     @Primary
     @ConfigurationProperties(prefix = "primary.datasource.mysql")
     public DataSource mysqlDataSource() {
          return DataSourceBuilder.create().build();
     }
    @PersistenceContext(unitName = "primary")   // 4
     @Primary
     @Bean(name = "entityManager")
     public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
          return builder.dataSource(mysqlDataSource()).persistenceUnit("primary").properties(jpaProperties())
                    .packages("au.com.myblog.domain").build();
      }
     private Map<String, Object> jpaProperties() {
          Map<String, Object> props = new HashMap<>();
          props.put("hibernate.ejb.naming_strategy", new SpringNamingStrategy());
          return props;
      }
}

Note:

  1. @Profile annotation to use this configuration only for non test profile. This allows me to set a different datasource, e.g. H2, when running tests.
  2. @EnableJpaRepositories is used for Spring Data. Note we are using the default transaction manager setup in Spring
  3. Bean for primary datasource. The @ConfigurationProperties annotation specifies the prefix of the properties to use by this datasource. For the example here, the application.properties file should include properties like below:
    # DB Connection
    primary.datasource.mysql.url=jdbc:log4jdbc:mysql://localhost/bpmn_service
    primary.datasource.mysql.username=root
    primary.datasource.mysql.password=root
    primary.datasource.mysql.driver-class-name=net.sf.log4jdbc.DriverSpy
  4. Persistence unit name should be setup in the EntityManager bean as shown here.

Configure Secondary Entity Manager

Configuration of the secondary entity manager is very similar to that of the primary.The only thing is we have to define a new transaction manager. Make sure a different prefix is set in the configuration properties of the data source. Also, a different persistent unit name should be used.

@Profile("!test")
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = "au.com.myblog.dao", entityManagerFactoryRef = "secondaryMySqlEntityManager", transactionManagerRef = "secondaryTransactionManager")
public class SecondaryMysqlDBConfiguration {
      @Bean
      @ConfigurationProperties(prefix = "secondary.datasource.mysql")
       public DataSource mysqlDataSource() {
            return DataSourceBuilder.create().build();
       }
      @PersistenceContext(unitName = "secondary")
      @Bean(name = "secondaryMySqlEntityManager")
      public LocalContainerEntityManagerFactoryBean mySqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
           return  builder.dataSource(mysqlDataSource()).properties(jpaProperties()).persistenceUnit("secondary").packages("au.com.myblog.domain").build();
       }
      @Bean(name = "secondaryTransactionManager")
       public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder) {
             JpaTransactionManager tm = new JpaTransactionManager();
             tm.setEntityManagerFactory(mySqlEntityManagerFactory(builder).getObject());
             tm.setDataSource(mysqlDataSource());
             return tm;
       }
       private Map<String, Object> jpaProperties() {
             Map<String, Object> props = new HashMap<>();
            // naming strategy to put underscores instead of camel case
            // as per auto JPA Configuration
            props.put("hibernate.ejb.naming_strategy", new SpringNamingStrategy());
            props.put("hibernate.hbm2ddl.auto", "update");
            return props;
       }

Note the setup above assumes the primary and secondary data sources are not used together in a single transaction. Hence we can use 2 independent transaction managers.

Specify which Entity Manager to use

Finally, make sure that you specify the name of the secondary transaction in the spring @Transactional annotation:

@Transactional(value = "secondaryTransactionManager")

Also, add the @PersistenceContext annotation with the unit name as defined in the configuration when injecting entity managers;

 @PersistenceContext(unitName = "secondary")
 private EntityManager entityManager;

That’s it!

Advertisements

Handling empty string foreign key in database in Spring/Hibernate ORM

I recently came across an issue when using Hibernate to map entity classes to an existing database. The database does not define foreign key constrain and use empty string (“”) in the FK column instead of NULL when a data row has no relation to the second table. When I mapped the relationship in my entity class, Hibernate attempts to find the associated entity using “” as the key. For example, the following code will throw an ObjectNotFoundException

@Entity

@Table(…)

@Repository(“myLoadListener”)

public class MyEntity {

@OneToOne

@JoinColumn(name=”fkCol”)

private MyAssocEntity assocEntity;

}

To work around the above problem, I implement a custom hibernate load event listener by extending the standard DefaultLoadEventListener:

@Component
public class MyLoadEventListener extends DefaultLoadEventListener {

@Override
protected Object doLoad(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadEventListener.LoadType options) {

Object entity = handleEmptyStringFK(keyToLoad);
return entity == null ? super.doLoad(event, persister, keyToLoad,   options) : entity;
}

@Override
protected Object proxyOrLoad(
final LoadEvent event,
final EntityPersister persister,
final EntityKey keyToLoad,
final LoadEventListener.LoadType options) {
Object entity = handleEmptyStringFK(keyToLoad);
return entity == null ? super.proxyOrLoad(event, persister, keyToLoad, options) : entity;
}

I override both doLoad() and proxyOrLoad() methods by adding a new method handleEmptyStringFK() to check for empty string in the identifier property in the input argument ketToLoad and instantiate an empty object to bypass the exception to be thrown by the parent’s methods. proxyOrLoad() is required here as it is called when using lazy loading.

Now, the listener has to be registered to the session factory. I use Spring and have to add the following in the xml configuration file:

<eventListeners refid=”myloadListener”></eventListeners>

Admittedly, this is a database issue but I cannot modify the data or database schema and the above solution works fine for me.

Setting up Spring JPA support

A basic setup to use JPA with Spring

Step 1 – Enable standard bean post processor for annotations

In spring config xml file, add the following:

<context:annotation-config/>

Step 2 – Setup entity manager factory and JPA transaction manager beans

In spring config xml file, add the followings:

<bean class=“org.springframework.orm.jpa.JpaTransactionManager” id=“transactionManager”>

 <property name=“entityManagerFactory” ref=“entityManagerFactory”/>

 </bean>

 <tx:annotation-driven mode=“aspectj” transaction-manager=“transactionManager”/>

  <bean class=“org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean” id=“entityManagerFactory”>

        <property name=“dataSource” ref=“dataSource”/>

 </bean>

Step 3 – add persistence.xml file to classpath:/META-INF folder

For example, to use Hibernate as provider for MySQL:

<?xml version=“1.0” encoding=“UTF-8” standalone=“no”?>

<persistence xmlns=http://java.sun.com/xml/ns/persistence&#8221; xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance&#8221; version=“2.0” xsi:schemaLocation=http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd&#8221;>

<persistence-unit name=“persistenceUnit” transaction-type=“RESOURCE_LOCAL”>

        <provider>org.hibernate.ejb.HibernatePersistence</provider>

        <properties>

            <property name=“hibernate.dialect” value=“org.hibernate.dialect.MySQL5InnoDBDialect”/>

            <!– value=”create” to build a new database on each run; value=”update” to modify an existing database; value=”create-drop” means the same as “create” but also drops tables when Hibernate closes; value=”validate” makes no changes to the database –>

            <!–  property name=”hibernate.hbm2ddl.auto” value=”validate”/ –>

            <property name=“hibernate.ejb.naming_strategy” value=“org.hibernate.cfg.ImprovedNamingStrategy”/>

            <property name=“hibernate.connection.charSet” value=“UTF-8”/>

            <property name=“hibernate.show_sql” value=“true”/>

            <!– Uncomment the following two properties for JBoss only –>

            <!– property name=”hibernate.validator.apply_to_ddl” value=”false” /–>

            <!– property name=”hibernate.validator.autoregister_listeners” value=”false” /–>

        </properties>

    </persistence-unit>

</persistence>

Conclusion

That’s it. Now you should be able to have the entity manager injected by using annotation @PersistenceContext. For example:

@Repository
public class MyJPARepository {

@PersistenceContext
private EntityManager entityManager;

More information from Spring documentation here