Spring Boot-Adding EntityListeners to application with multiple data sources

Dhruv Saksena
2 min readMar 10, 2020

--

It’s one of the edge cases where your Spring Boot application is connected to multiple databases and you want to perform Entity listener operations while performing read/write operations on the database.

To configure multiple datasources. You need to have seperate DAOConfig for both the databases

package com.medium.app.config;import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder.Builder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "db1EntityManagerFactory",
transactionManagerRef ="db1TransactionManager",
basePackages={"com.medium.app.repository.db1"}
)
public class Db1DAOConfig
{
@Bean(name = "db1DataSourceProperties")
@ConfigurationProperties("db1.datasource")
public DataSourceProperties db1DataSourceProperties() {
return new DataSourceProperties();
}
@Bean(name = "db1DataSource")
public DataSource db1DataSource()
{
return db1DataSourceProperties().
initializeDataSourceBuilder().
type(BasicDataSource.class).build();

}


@Bean(name = "db1EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
db1EntityManagerFactory(final EntityManagerFactoryBuilder builder)
{
Builder dataSource = builder.dataSource(db1DataSource());
return dataSource.
packages("com.medium.app.entity.db1").persistenceUnit("db1").build();
}


@Bean(name = "db1TransactionManager")
public PlatformTransactionManager db1TransactionManager(@Qualifier("db1EntityManagerFactory")EntityManagerFactory db1EntityManagerFactory)
{
return new JpaTransactionManager(db1EntityManagerFactory);
}
}

Now let’s configure second database-

package com.medium.app.config;import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.zaxxer.hikari.HikariDataSource;@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "db2EntityManagerFactory",
transactionManagerRef="db2TransactionManager",
basePackages="com.medium.app.repository.db2"
)
public class Db2DAOConfig
{

@Bean(name = "db2DataSourceProperties")
@Primary
@ConfigurationProperties("db2.datasource")
public DataSourceProperties db2DataSourceProperties() {
return new DataSourceProperties();
}

@Primary
@Bean(name = "db2DataSource")
public DataSource db2DataSource()
{
return db2DataSourceProperties().
initializeDataSourceBuilder().
type(HikariDataSource.class).build();

}


@Primary
@Bean(name = "db2EntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
@Qualifier("db2DataSource")DataSource dataSource){

return builder.dataSource(dataSource)
.packages("com.medium.app.entity.db2").
persistenceUnit("db2").build();
}

@Primary
@Bean(name="db2TransactionManager")
public PlatformTransactionManager db2TransactionManager(@Qualifier("db2EntityManagerFactory")EntityManagerFactory db2EntityManagerFactory)
{
return new JpaTransactionManager(db2EntityManagerFactory);
}



}

Here’s the configuration of application.properties-

db2.datasource.driver = com.mysql.cj.jdbc.Driver
db2.datasource.url= jdbc:mysql://localhost:3306/db2
db2.datasource.username=root
db2.datasource.password=root
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=truedb1.datasource.driver=com.mysql.cj.jdbc.Driver
db1.datasource.url=jdbc:mysql://localhost:3306/db1
db1.datasource.username=root
db1.datasource.password=root

Now, let’s say we want to perform audit while save/update operations on a table. For this spring-data provides facility to attach EntityListeners to your entity

@Entity
@Table(name = "commission_details")
@EntityListeners(value=CommissionDetailsEntityListener.class)
public class CommissionDetails
{
@Id
@Column(name = "id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
@Column(name = "domain_id")
private Long domainId;
@Column(name = "status")
private Long status;
}

The problem with enititylisteners is that they aren’t managed by Spring and are loaded during application start-up so any efforts to inject an @Autowired bean here fails.

So, idea is to get hold of entityManagerFactory from the config and use it manage the persistence.

Here’s the sample code for the same-

package com.medium.app.entity.db2.listeners;import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.transaction.Transactional;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import static javax.transaction.Transactional.TxType.MANDATORY;
import com.medium.app.entity.db2.CommissionDetails;
import com.medium.app.entity.db2.CommissionDetailsAudit;
public class CommissionDetailsEntityListener
{
@PrePersist
public void prePersist(CommissionDetails commissionDetails)
{
perform(commissionDetails, "CREATE");
}

@PreUpdate
public void preUpdate(CommissionDetails commissionDetails)
{
perform(commissionDetails, "UPDATE");
}

@PreRemove
public void preRemove(CommissionDetails commissionDetails)
{
perform(commissionDetails, "DELETED");
}

@Transactional(MANDATORY)
private void perform(CommissionDetails commissionDetails, String action)
{
CommissionDetailsAudit commissionDetailsAudit = new CommissionDetailsAudit();
commissionDetailsAudit.setAction(action); commissionDetailsAudit.setDomainId(commissionDetails.getDomainId());
commissionDetailsAudit.setStatus(commissionDetails.getStatus());


Object entityManager = BeanUtil.getBean("db2EntityManagerFactory");
System.out.println(entityManager);

if(entityManager instanceof EntityManagerFactory)
{
EntityManagerFactory nativeEntityManagerFactory = (EntityManagerFactory) entityManager;
EntityManager entityManagerObj = nativeEntityManagerFactory.createEntityManager();
entityManagerObj.getTransaction().begin();
entityManagerObj.persist(commissionDetailsAudit);
entityManagerObj.getTransaction().commit();
entityManagerObj.close();
}



}
}

Here BeanUtil is-

@Service
public class BeanUtil implements ApplicationContextAware
{
private static ApplicationContext context;
public static <T> T getBean(Class<T> beanClass)
{
return context.getBean(beanClass);
}
public static Object getBean(String beanName)
{
return context.getBean(beanName);
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
context = applicationContext;
}
}

--

--

No responses yet