Junit test cases for JPA repositories - spring-boot

I am new to junit, I have a repository as follows:
#Repository
public interface ChartRepository extends JpaRepository<Chart, Integer>{
}
and My Chart Entity class is follows:
#Entity
#Table(name = "Chart")
public class Chart {
#Column(name = "ENT_ID")
private String entID;
#Column(name = "ent_NAME")
private String entName;
#Column(name = "ent_PRODUCER_ID")
private String entProducerId;
#Id
#Column(name = "ent_Rc_ID")
#SequenceGenerator(name = "ent_RC_ID_SEQ", sequenceName="ent_RC_ID_SEQ", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ent_RC_ID_SEQ")
private Integer entReceiveId;
#Column(name = "JOB_ID")
private Integer jobId;
#Column(name = "CREATE_DT")
private Timestamp createdDate;
//getters and Setters
}
Now, Can we able to write test cases for the repository class. If so how can we do that.Can anyone please suggest me with some code samples.

You can create a #DataJpaTest and #Autowire your repository into it. For example:
#RunWith(SpringRunner.class)
#DataJpaTest
public class MyJpaTest {
#Autowired
private ChartRepository chartRepository;
#Test
public void myTest() {
...
}
}
See this for more: https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html#boot-features-testing-spring-boot-applications-testing-autoconfigured-jpa-test

I would strongly recomment to use any in-memory DB to test you JPA repository and dont use mock test framework like Mockito, EasyMock, etc. As in Dao layer, there should not be any business logic to mock. it should be simple read/write operation.
I use h2database for this.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(
classes = {DatabaseConfig.class},
loader = AnnotationConfigContextLoader.class)
public class ChartRepositoryTest {
#Autowired
private ChartRepository cartRepository;
#Test
public void testfind() {
// do find , insert and validate the response
}
}
testCompile('com.h2database:h2:1.4.196')
This is wha database config file looks like
#Configuration
#EnableJpaRepositories(basePackages = "com.mypackage.repository")
#PropertySource("application-test.properties")
#EnableTransactionManagement
public class DatabaseConfig {
#Autowired
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "com.mypackage.v2" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create");
properties.setProperty("hibernate.dialect","org.hibernate.dialect.H2Dialect");
return properties;
}
}

Related

org.springframework.orm.jpa.JpaSystemException: could not initialize proxy [Order#orderdate] - no Session;

My spring boot v2.5.5 application has master slave mysql db configured.
Primary Data Source Config
#EnableJpaRepositories(
basePackages = "com.dummy",
excludeFilters = #ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager"
)
#Configuration
public class PrimaryDataSourceConfig {
#Autowired
private Environment env;
#Value("${master.jdbc.url}")
private String jdbcURL;
#Bean
#Primary
public DataSource primaryDataSource() throws Exception {
return DataSourceBuilder.create()
.driverClassName("com.mysql.jdbc.Driver")
.url(jdbcURL)
.username("root")
.password("root").build();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory() throws Exception {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource());
em.setPackagesToScan("com.dummy");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
#Primary
public PlatformTransactionManager primaryTransactionManager() throws Exception {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primaryEntityManagerFactory().getObject());
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("primary.hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("primary.hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty( "primary.hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "false");
return hibernateProperties;
}
}
Slave Datasource Config
#Configuration
#EnableJpaRepositories(
basePackages = "com.dummy.submodule",
includeFilters = #ComponentScan.Filter(ReadOnlyRepository.class),
entityManagerFactoryRef = "readOnlyEntityManagerFactory",
transactionManagerRef = "readOnlyTransactionManager"
)
public class ReadOnlyDataSourceConfig {
#Autowired
private Environment env;
#Value("${slave.jdbc.url}")
private String jdbcURL;
#Bean
public DataSource readOnlyDataSource() throws Exception {
return DataSourceBuilder.create()
.driverClassName("com.mysql.jdbc.Driver")
.url(jdbcURL)
.username("root")
.password("root").build();
}
#Bean
public LocalContainerEntityManagerFactoryBean readOnlyEntityManagerFactory() throws Exception {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(readOnlyDataSource());
em.setPackagesToScan("com.dummy.submodule");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public PlatformTransactionManager readOnlyTransactionManager() throws Exception {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(readOnlyEntityManagerFactory().getObject());
return transactionManager;
}
final Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", env.getProperty("readonly.hibernate.hbm2ddl.auto"));
hibernateProperties.setProperty("hibernate.dialect", env.getProperty("readonly.hibernate.dialect"));
hibernateProperties.setProperty("hibernate.show_sql", env.getProperty( "readonly.hibernate.show_sql"));
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", "false");
return hibernateProperties;
}
}
Entity
#Entity
#Getter
#Setter
public class Product implements Serializable {
private static final long serialVersionUID = 8135071385764991866L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(fetch = FetchType.LAZY)
#NotNull
#JoinColumn(name = "status")
private Order order
}
#Getter
#Setter
#Entity
public class Order implements Serializable {
private static final long serialVersionUID = 8135071385764991879L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date orderdate;
}
I am trying to fetch Product from read only(slave) db in my service layer
#Service
#AllArgsConstructor
public class ProductService{
private ProductRepository repo;
public void performOperation(){
Optional<Product> product = repo.findById(1l, Product.class);
if(product.isPresent()){
Order order = product.get().getOrder();
Date orderDate = order.getOrderDate(); // this line gives below exception
}
}
}
Readonly Repository:
#Repository
#ReadOnlyRepository
public interface ProductRepository extends JpaRepository<Product, Long> {
<T> Optional<T> findById(final Long id, Class<T> type);
}
Exception:
2021-10-22 12:35:11,109 ERROR [http-nio-8080-exec-8] com.dummy.logging.LoggingClass:41 Exception_Occurred::{}
org.springframework.orm.jpa.JpaSystemException: could not initialize proxy [com.dummy.submodule.entities.Order#orderdate] - no Session; nested exception is org.hibernate.LazyInitializationException: could not initialize proxy [com.dummy.submodule.entities.Order#orderdate] - no Session
Note: The LazyInitializationException occurs only when I am fetching Product from slave db. When same operation i am trying to perform using master db I am not getting LazyInitializationException.
This issue for now i have handled by making Order as FetchType.EAGER.
But i am trying to find out reason behind this in case of master slave db and how to achieve it with LAZY fetch type.
Annotate performOperation with #Transactional like below:
#Service
#AllArgsConstructor
public class ProductService{
private ProductRepository repo;
#Transactional
public void performOperation(){
Optional<Product> product = repo.findById(1l, Product.class);
if(product.isPresent()){
Order order = product.get().getOrder();
Date orderDate = order.getOrderDate(); // this line gives below exception
}
}
}

Spring boot multiple database cause NoSuchBeanDefinitionException by integration testing

By configuring application to use two datasource instances, it cause an issue in one of my integration tests with the following message - "Unsatisfied dependency expressed through method 'entityManagerFactoryBean' parameter 0; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}"
Here is the bean that stores the information for both database instances:
#Component
public class DataSourceRouting extends AbstractRoutingDataSource {
private final DataSourcePrimeConfig dataSourcePrimeConfig;
private final DataSourceSecondConfig dataSourceSecondConfig;
private final DataSourceContextHolder dataSourceContextHolder;
public DataSourceRouting(DataSourceContextHolder dataSourceContextHolder, DataSourcePrimeConfig dataSourcePrimeConfig,
DataSourceSecondConfig dataSourceSecondConfig) {
this.dataSourcePrimeConfig = dataSourcePrimeConfig;
this.dataSourceSecondConfig = dataSourceSecondConfig;
this.dataSourceContextHolder = dataSourceContextHolder;
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put(DataSourceEnum.DATASOURCE_PRIME, dataSourceOneDataSource());
dataSourceMap.put(DataSourceEnum.DATASOURCE_SECOND, dataSourceTwoDataSource());
this.setTargetDataSources(dataSourceMap);
this.setDefaultTargetDataSource(dataSourceOneDataSource());
}
#Override
protected Object determineCurrentLookupKey() {
return this.dataSourceContextHolder.getBranchContext();
}
public DataSource dataSourceOneDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(dataSourcePrimeConfig.getUrl());
dataSource.setUsername(dataSourcePrimeConfig.getUsername());
dataSource.setPassword(dataSourcePrimeConfig.getPassword());
return dataSource;
}
public DataSource dataSourceTwoDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(dataSourceSecondConfig.getUrl());
dataSource.setUsername(dataSourceSecondConfig.getUsername());
dataSource.setPassword(dataSourceSecondConfig.getPassword());
return dataSource;
}
}
Here is the configuration class:
#Configuration
#EnableJpaRepositories(basePackages = "test.project.repository", transactionManagerRef = "transcationManager", entityManagerFactoryRef = "entityManager")
#EnableTransactionManagement
#RequiredArgsConstructor
#DependsOn("dataSourceRouting")
public class DataSourceConfig {
private final DataSourceRouting dataSourceRouting;
#Bean
#Primary
public DataSource dataSource() {
return dataSourceRouting;
}
#Bean(name = "entityManager")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(EntityManagerFactoryBuilder builder) {
return builder.dataSource(dataSource()).packages("test.project").build();
}
#Bean(name = "transcationManager")
public JpaTransactionManager transactionManager(
#Autowired #Qualifier("entityManager") LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return new JpaTransactionManager(entityManagerFactoryBean.getObject());
}
}
And here is the failing test:
#SpringBootTest
#Import(value = DataSourceConfig.class)
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
public class MailBodyProviderServiceTest {
#Autowired
private MailService mailService;
#Autowired
private ReportService reportService;
#MockBean
private JPATransactionDataRepository jpaTransactionDataRepository;
#Autowired
private MailBodyBuilder builder;
#Autowired
private DataSourceRouting dataSourceRouting;
#Autowired
private DataSource dataSource;
#Autowired
private LocalContainerEntityManagerFactoryBean entityManagerFactoryBean;
#Autowired
private JpaTransactionManager jpaTransactionManager;
#Autowired
private EntityManagerFactoryBuilder entityManagerFactoryBuilder;
#Rule
public SmtpServerRule smtpServerRule = new SmtpServerRule(2525);
#Test
public void shouldSendSingleMail() throws MessagingException {
when(this.jpaTransactionDataRepository.mapTransactionData()).thenReturn(generateReportData());
mailService.sendEmail(new ReportEvent(this, "report event", EventType.CREDIT_CARD));
MimeMessage[] receivedMessages = smtpServerRule.getMessages();
assertEquals(3, receivedMessages.length);
MimeMessage current = receivedMessages[0];
String contentType = current.getContentType();
assertTrue(contentType.contains("multipart"));
}
private static Map<Date, List<TransactionDto>> generateReportData(){
Date today = new Date();
List<Object[]> objects = new ArrayList<>();
Object[] object1 = new Object[] { today, AuthStatus.AUTH_CONFIRMED.name(), ProcessingType.DIRECT.name(), BigDecimal.valueOf(10l)};
Object[] object2 = new Object[] { today, AuthStatus.AUTH_REJECTED.name(), ProcessingType.DIRECT.name(), BigDecimal.valueOf(10l)};
Object[] object3 = new Object[] { new Date(today.getTime() + (1000 * 60 * 60 * 24)), AuthStatus.AUTH_CANCELED.name(), ProcessingType.TAN.name(), BigDecimal.valueOf(10l)};
Object[] object4 = new Object[] { new Date(today.getTime() + (1000 * 60 * 60 * 24)), AuthStatus.AUTH_FAILED.name(), ProcessingType.TAN.name(), BigDecimal.valueOf(10l)};
objects.add(object1);
objects.add(object2);
objects.add(object3);
objects.add(object4);
return TransactionMapper.mapFromDatabaseQuery(objects);
}
}
I try with #TestConfiguration, also to exclude DataSourceConfig, but nothing helps. Hope someone to have idea what I do wrong or miss :)

Spring + JPA + H2 Strange behaviours of lazy fetch and bidirectional OneToMany

I have been playing for a long time with JPA, in the past through EJBs, now with Spring. I have recently noticed some weird behaviours I can hardly explain.
First the bidirectionnal OneToMany
My bidirectional OneToMany is correctly set with a mappedBy.
#Entity
public class EntityOne {
#Id #GeneratedValue
private int id;
#OneToMany(mappedBy = "one")
private Set<EntityTwo> twos;
...
#Entity
public class EntityTwo {
#Id #GeneratedValue
private int id;
#ManyToOne
private EntityOne one;
...
Then this does not update the database :
#Transactional
public void firstWay(){
EntityOne e1=em.find(EntityOne.class,1);
EntityTwo e2=em.find(EntityTwo.class,1);
e1.getTwos().add(e2);
}
while this does :
#Transactional
public void secondWay(){
EntityOne e1=em.find(EntityOne.class,1);
EntityTwo e2=em.find(EntityTwo.class,1);
e2.setOne(e1);
}
I am quite puzzled...
Then the lazy fetch :
// this is just a tool example...
public void someFindBy() {
EntityOne e1=em.find(EntityOne.class,1);
for (EntityTwo e2:e1.getTwos()) {
System.out.println(e2);
}
}
leads to LazyExceptionError... Shouldn't my "e1" entity remain attached until the end of the method and thus hibernate resolve the fetch (I use the default persistence context ,i.e. Transaction scoped. I did also try to make the method transactional by annotating it with #Transactional but that didn't change anything).
So, well, I could use an Entity Graph or a Join Fetch, but, still, I wonder why it doesn't work as is...
Here is the Spring configuration file :
#Configuration
#ComponentScan(basePackages = {"facade"})
#EnableTransactionManagement
public class ClientWebConfig extends WebMvcConfigurerAdapter {
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder
.setType(EmbeddedDatabaseType.H2)
.build();
return db;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan(new String[] { "model" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "create-drop");
properties.setProperty(
"hibernate.dialect", "org.hibernate.dialect.H2Dialect");
properties.setProperty("hibernate.hbm2ddl.import_files" ,"insert-data.sql");
return properties;
}
#Bean
public PlatformTransactionManager transactionManager(
EntityManagerFactory emf){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(emf);
return transactionManager;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
}
fix this entity.
cascade, fetch must be set to update.
#Entity
public class EntityOne {
#Id #GeneratedValue
private int id;
**#OneToMany(mappedBy = "one", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)**
private Set<EntityTwo> twos;

Spring boot configuration for #Transactional for both JPA and spring-JDBC-templete

in my project, I am using both JPA and spring-jdbc-template & NamedParameterTemplate.
I need to get transaction support for both.
So how to configure #Transactional for both JPA & spring-JDBC ?
Here are the 2 classes I used for configuring those.
Class number 1: PersistenceConfiguration
This is the class that declares transaction-management should be enabled.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.google.product.repository"},
excludeFilters = #ComponentScan.Filter(type = FilterType.REGEX, pattern = "com.google.product.repository.mongoRepository.*.*Repository"))
public class PersistenceConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
private Properties entityManagerProperties;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.google.product.model");
final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(entityManagerProperties);
return em;
}
}
Class number 2: ProdDatabaseConfiguration
This is the class that declares beans releted toJDBC & JPA. for example primaryJdbcTemplate ,secondaryJdbcTemplate & entityManagerProperties.
#Configuration
#PropertySource({"classpath:database-prod.properties"})
#Profile("prod")
public class ProdDatabaseConfiguration {
private static final Logger LOG = LoggerFactory.getLogger(ProdDatabaseConfiguration.class);
#Value("${jdbc.jndiName}")
private String jndiName;
#Value("${hibernate.dialect}")
private String hibernateDialect;
#Value("${hibernate.show_sql}")
private String hibernateShowSql;
#Value("${hibernate.cache.use_second_level_cache}")
private String hibernateSecondLevelCache;
#Value("${hibernate.cache.use_query_cache}")
private String hibernateQueryCache;
#Value("${jadira.usertype.databaseZone}")
private String databaseZone;
#Value("${jadira.usertype.javaZone}")
private String javaZone;
#Value("${mongo.jndiName}")
private String mongoJndiName;
#Bean
public DataSource dataSource() {
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return jndiDataSourceLookup.getDataSource(jndiName);
}
#Bean(name = "entityManagerProperties")
public Properties additionalProperties() {
final Properties hibernateProperties = new Properties();
hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "none");
hibernateProperties.setProperty("hibernate.dialect", hibernateDialect);
hibernateProperties.setProperty("hibernate.show_sql", hibernateShowSql);
hibernateProperties.setProperty("hibernate.cache.use_second_level_cache", hibernateSecondLevelCache);
hibernateProperties.setProperty("hibernate.cache.use_query_cache", hibernateQueryCache);
hibernateProperties.setProperty("jadira.usertype.databaseZone", databaseZone);
hibernateProperties.setProperty("jadira.usertype.javaZone", javaZone);
return hibernateProperties;
}
#Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate() {
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return new JdbcTemplate(jndiDataSourceLookup.getDataSource(jndiName));
}
#Bean(name = "secondaryJdbcTemplate")
public NamedParameterJdbcTemplate secondaryJdbcTemplate() {
JndiDataSourceLookup jndiDataSourceLookup = new JndiDataSourceLookup();
return new NamedParameterJdbcTemplate(jndiDataSourceLookup.getDataSource(jndiName));
}
Because you are only using one datasource you can remove all the configuration and just use the spring.datasource properties.
Transaction will also work out of the box because you will have only this datasource.
Read more about this topic in the official documentation:
https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-sql

Spring JPA Specify entity's table name on the repository interface

I have a scenario where I want to create multiple repositories for the same entity class.
The first and simplest scenario I want to save some of the instances of my class as rows in a different table than the primary table (which is designated on the entity itself).
other scenarios would be to create remote back ups hence the whole datasource would be different.
Does Spring allow things like that?
I'm not sure you'll want this, but here it is anyway... The best I could find using Spring Data Repositories is the following:
(1a) Manually define 2 repository beans to use 2 datasources:
#Bean
#Qualifier("db1")
public ModelJpaRepository modelJpaRepositoryDb1() {
JpaRepositoryFactoryBean<ModelJpaRepository, Model, String> myFactory = new JpaRepositoryFactoryBean<ModelJpaRepository, Model, String>();
myFactory.setRepositoryInterface(ModelJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory1().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
#Bean
#Qualifier("db2")
public ModelJpaRepository modelJpaRepositoryDb2() {
JpaRepositoryFactoryBean<ModelJpaRepository, Model, String> myFactory = new JpaRepositoryFactoryBean<ModelJpaRepository, Model, String>();
myFactory.setRepositoryInterface(ModelJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory2().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
(1b): Define the 2 datasources (referred to by the previous repository definitions):
#Bean(name = "dataSource1")
public DataSource dataSource1() {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName(...);
bds.setUrl(...);
bds.setUsername(...);
bds.setPassword(...);
return bds;
}
#Bean(name = "dataSource2")
public DataSource dataSource2() {
BasicDataSource bds = new BasicDataSource();
bds.setDriverClassName(...);
bds.setUrl(...);
bds.setUsername(...);
bds.setPassword(...);
return bds;
}
(1c): Define 2 entityManagers - 1 per datasource (referred to by the previous repository definitions):
#Bean
public EntityManagerFactory entityManagerFactory1() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource1());
entityManagerFactory.setPersistenceUnitName("pu1");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getNativeEntityManagerFactory();
}
#Bean
public EntityManagerFactory entityManagerFactory2() {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(dataSource2());
entityManagerFactory.setPersistenceUnitName("pu2");
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter());
entityManagerFactory.setJpaProperties(jpaProperties());
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getNativeEntityManagerFactory();
}
(2) Inject the 2 repositories and use them:
#Autowired
#Qualifier("db1")
private ModelJpaRepository modelJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private ModelJpaRepository modelJpaRepositoryDb2;
...
modelJpaRepositoryDb1.save(model);
modelJpaRepositoryDb2.save(model);
(3) Define the "OtherModel" and include the "Model" as embedded:
#Entity
#Table(name = "OTHER_TABLE")
public class OtherTable {
#Id
#Column(name = "ID", nullable = false)
private long id;
#Embedded
private Model model;
public OtherTable(Model model) {
this.model = model;
this.id = model.getId();
}
}
For info, This answer suggests using #Inheritance(strategy=InheritanceType.TABLE_PER_CLASS), to achieve define an additional model (inheritance vs composition).
(4) Define the 2 repositories for the OtherTable:
#Bean
#Qualifier("db1")
public OtherTableJpaRepository otherTableJpaRepositoryDb1() {
JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String> myFactory = new JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String>();
myFactory.setRepositoryInterface(OtherTableJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory1().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
#Bean
#Qualifier("db2")
public OtherTableJpaRepository otherTableJpaRepositoryDb2() {
JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String> myFactory = new JpaRepositoryFactoryBean<OtherTableJpaRepository, OtherTable, String>();
myFactory.setRepositoryInterface(OtherTableJpaRepository.class);
myFactory.setEntityManager(entityManagerFactory2().createEntityManager());
myFactory.afterPropertiesSet();
return myFactory.getObject();
}
(5) Inject and use them all:
#Autowired
#Qualifier("db1")
private ModelJpaRepository modelJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private ModelJpaRepository modelJpaRepositoryDb2;
#Autowired
#Qualifier("db1")
private OtherTableJpaRepository otherTableJpaRepositoryDb1;
#Autowired
#Qualifier("db2")
private OtherTableJpaRepository otherTableJpaRepositoryDb2;
// ...
modelJpaRepositoryDb1.save(model);
otherTableJpaRepositoryDb1.save(new OtherTable(model));
modelJpaRepositoryDb2.save(model);
otherTableJpaRepositoryDb2.save(new OtherTable(model));

Resources