Spring Boot - Same repository and same entity for different databases - spring

I have a Spring Boot project with one entity and one repository associated to this entity. In the repository there is one method with a custom query and in the project controller this repository is used to return data from different postgresql databases. These databases have same tables with same columns (so the referred entity is the same), the only difference among these databases is the year (..., DB2015, DB2016, DB2017).
My questions are: How can i return data in the project controller that belong to "different" databases? Is possible to use the same query to select data initially from the first database, then from the second and so on?
In other questions i've read that i need different datasources, is this correct?
This is the entity:
#Entity(name = "REQUEST")
public class Request implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#Column(name="IDREQUEST", nullable=false)
private BigDecimal idrequest;
#Column(name="PAYLOAD")
private String payload;
#Column(name="MITTENTE")
private String mittente;
#Column(name="SERVIZIO")
private String servizio;
#Column(name="DATARICEZIONE")
private BigDecimal dataricezione;
public BigDecimal getIdrequest() {
return idrequest;
}
public void setIdrequest(BigDecimal idrequest) {
this.idrequest = idrequest;
}
public String getPayload() {
return payload;
}
public void setPayload(String payload) {
this.payload = payload;
}
public String getMittente() {
return mittente;
}
public void setMittente(String mittente) {
this.mittente = mittente;
}
public String getServizio() {
return servizio;
}
public void setServizio(String servizio) {
this.servizio = servizio;
}
public BigDecimal getDataricezione() {
return dataricezione;
}
public void setDataricezione(BigDecimal dataricezione) {
this.dataricezione = dataricezione;
}
}
This is the repository:
#Repository
public interface RequestRepository extends PagingAndSortingRepository<Request, BigDecimal> {
#Query(nativeQuery=true, value="SELECT * FROM \"REQUEST\" WHERE strpos(\"PAYLOAD\",\'?1\') > 0")
List<Request> findByCodiceFiscale(String codiceFiscale);
}
This is the controller
#RequestMapping(value="/ricercaadesioni/{codicefiscale}", method=RequestMethod.GET)
public ResponseEntity<List<Request>> ricercaAdesioniByCodiceFIscale(#PathVariable("codicefiscale") String codicefiscale) {
List<Request> listAdesioni = requestRepo.findByCodiceFiscale(codicefiscale);
return new ResponseEntity<List<Request>>(listAdesioni, HttpStatus.OK);
}
This is application.properties (in this case the datasource is referred to one db only):
spring.datasource.url=jdbc:postgresql://localhost:5432/DB2017_test
spring.datasource.username=xxx
spring.datasource.password=xxx
Hope everything is clear

Create 2 config files with different datasource and these 2 config files will have different specifications for 2 different jpa repository class.but can have same domain class.
step1>
In your properties file have 2 datasource details.
spring.datasource.url=jdbc:postgresql://localhost:5432/DB2017_test
spring.datasource.username=xxx
spring.datasource.password=xxx
# DB2018 DB - ""
spring.datasource2.url=jdbc:postgresql://localhost:5432/DB2018_test
spring.datasource2.username=xxx
spring.datasource2.password=xxx
step2>Then create config file for first dataSource
package com.package1;
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.package1.repo" }
)
public class DB2017Config {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("com.domain")
.persistenceUnit("foo")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
step3> Similary create another config file for other dataSource,
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.package2.repo" }
And change prefix
#ConfigurationProperties(prefix = "spring.datasource2")
Now you will have 2 similar RequestRepository1 and RequestRepository2 in package1 and package2 respectiverly as mentioned above (basePackages = { "com.package1.repo" }).
step4>All set autowire 2 different repo .
#Autowired
private final RequestRepository1 repo1;
#Autowired
private final RequestRepository2 repo2;
Then use them.
List<Request> listAdesioni = repo1.findByCodiceFiscale(codicefiscale);
List<Request> listAdesioni = repo2.findByCodiceFiscale(codicefiscale);

Related

Spring JPA - Single Repository class with multiple JNDIs

I am using Spring JPA Repository to connect to Oracle.
My Repository package is com.demo.infrastructure.repository;
Repository class is StoreRepo.java
#Repository
public interface StoreRepo extends JpaRepository<StoreAttribute, String> {
#Query("select storeAttributeName from StoreAttribute order by storeAttributeName asc")
List<String> fetchAllStoreAttributeNames();
List<StoreAttribute> findAllByOrderByStoreAttributeNameAsc();
}
Problem:
I am using JNDI config to configure data source. Currently it has only one JNDI entry. Now I want to use two user names for the same database, one with admin(read-write) access and the other with user(read-only) access. Both these users will access the same Repository and same entity.
I tried the solutions already available which uses two different repository packages for each data source. But I want the Repository "StoreRepo" to be the same.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryAdmin",
basePackages = { "com.demo.infrastructure.repository" }
)
public class DataSourceAdminConfig {
#Primary
#Bean(name = "dataSourceAdmin")
public DataSource dataSource() {
return new JndiDataSourceLookup().getDataSource("jdbc/myds_admin");
}
#Primary
#Bean(name = "entityManagerFactoryAdmin")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSourceAdmin") DataSource dataSource
) {
return builder.dataSource(dataSource).
packages("com.demo.domain.model.entities").
persistenceUnit("read-write").
build();
}
#Primary
#Bean(name = "transactionManagerAdmin")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactoryAdmin") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
I should have two classes like this with different package (Refer basePackages). But I dont want this solution instead want to use single repository package and the same repository class.
Solution that worked for me.
1) Created separate config classes one for admin user and app user each
2) Created seprate entity manager references one for admin user and app user each
3) Instantiated the same Repositoy class (without using #Repository annotation) through java code and using respective entity manager
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryAdmin"
)
public class AdminUserConfig {
#Primary
#Bean(name = "dataSourceAdmin")
public DataSource dataSourceAdmin(#Value("${spring.datasource.admin-user.jndi-name}") String key) {
return new JndiDataSourceLookup().getDataSource(key);
}
#Primary
#Bean(name = "entityManagerFactoryAdmin")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryAdmin(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSourceAdmin") DataSource dataSource
) {
return builder.dataSource(dataSource).
packages("com.demo.domain.model.entities").
persistenceUnit("read-write").
build();
}
#Bean(name = "entityManagerAdmin")
public EntityManager entityManagerAdmin(#Qualifier("entityManagerFactoryAdmin") EntityManagerFactory
entityManagerFactory) {
return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
}
#Bean(name = "adminRepository")
public StoreRepo readWriteDimStoreRepository(#Qualifier("jpaRepositoryFactoryAdmin")
JpaRepositoryFactory repositoryFactory) {
return repositoryFactory.getRepository(StoreRepo.class);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryApp"
)
public class AppUserConfig {
#Primary
#Bean(name = "dataSourceApp")
public DataSource dataSourceApp(#Value("${spring.datasource.App-user.jndi-name}") String key) {
return new JndiDataSourceLookup().getDataSource(key);
}
#Primary
#Bean(name = "entityManagerFactoryApp")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryApp(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSourceApp") DataSource dataSource
) {
return builder.dataSource(dataSource).
packages("com.demo.domain.model.entities").
persistenceUnit("read-only").
build();
}
#Bean(name = "entityManagerAdmin")
public EntityManager entityManagerApp(#Qualifier("entityManagerFactoryApp") EntityManagerFactory
entityManagerFactory) {
return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
}
#Bean(name = "AppRepository")
public StoreRepo readOnlyStoreRepository(#Qualifier("jpaRepositoryFactoryApp")
JpaRepositoryFactory repositoryFactory) {
return repositoryFactory.getRepository(StoreRepo.class);
}
}
//#Repository
public interface StoreRepo extends JpaRepository<StoreAttribute, String> {
#Query("select storeAttributeName from StoreAttribute order by
storeAttributeName asc")
List<String> fetchAllStoreAttributeNames();
List<StoreAttribute> findAllByOrderByStoreAttributeNameAsc();
}

Spring Boot - connecting to two schema per tenant

I am creating a Spring boot Multimodule application in which one of the module has unique requirement. The module should be able to connect to one fixed schema say S1 and there are two other schema S2 and S3.
Depending upon the region it will connect to S1 and S2 or S1 and S3.
Will I have to follow the multi tenancy approach here ?
If yes how do I use multi tenancy to connect to two schema for a particular request ?
As per my understanding you want to connect to different DB's based on country. I have similar kind of issue in my project.
My requirement is when there is a json request i need to fetch country code from that and based on that i'll connect to that country specific DB. Below is my code. Hope this helps you.
application.properties
spring.main.web-application-type=none
#spring.main.allow-bean-definition-overriding=true
#au DB connection
spring.datasource.au.url=
spring.datasource.au.username=
spring.datasource.au.password=
#cn DB connection
spring.datasource.cn.url=
spring.datasource.cn.username=
spring.datasource.cn.password=
spring.jpa.show-sql=true
spring.jpa.database=default
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.naming.implicit-strategy=org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyHbmImpl
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
DB configuration
For Au :
Here i'm using #Primary annotation for AU.
#Configuration
#EnableJpaRepositories(entityManagerFactoryRef = "auEntityManagerFactory", transactionManagerRef = "auTransactionManager", basePackages = "com.dbs.multiDb.au.repository")
#EnableTransactionManagement
public class auDsConfig {
Logger logger = LogManager.getLogger(this.getClass());
#Autowired
Environment env;
#Bean(name = "auEntityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean cnEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("au-db") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.dbs.multiDb.domain").persistenceUnit("auDb")
.properties(singletonMap("hibernate.hbm2ddl.auto", "none")).build();
}
#Bean(name = "auTransactionManager")
#Primary
public PlatformTransactionManager auTransactionManager(
#Qualifier("auEntityManagerFactory") EntityManagerFactory cnEntityManagerFactory) {
return new JpaTransactionManager(cnEntityManagerFactory);
}
#SuppressWarnings("unused")
#Bean(name = "au-db")
#Primary
#ConfigurationProperties(prefix = "spring.datasource.au")
public DataSource auDataSource() {
DriverManagerDataSource dbFinal = new DriverManagerDataSource();
dbFinal.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dbFinal.setUrl(env.getProperty("spring.datasource.au.url"));
dbFinal.setUsername(env.getProperty("spring.datasource.au.username"));
dbFinal.setPassword(env.getProperty("spring.datasource.au.password"));
return dbFinal;
}
}
CN DB config :
#Configuration
#EnableJpaRepositories(entityManagerFactoryRef = "cnEntityManagerFactory", transactionManagerRef = "cnTransactionManager", basePackages = "com.dbs.multiDb.cn.repository")
#EnableTransactionManagement
public class cnDsConfig {
#Bean(name = "cnEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean cnEntityManagerFactory(final EntityManagerFactoryBuilder builder,
final #Qualifier("cn-db") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.dbs.multiDb.domain").persistenceUnit("cnDb")
.properties(singletonMap("hibernate.hbm2ddl.auto", "none")).build();
}
#Bean(name = "cnTransactionManager")
public PlatformTransactionManager cnTransactionManager(
#Qualifier("cnEntityManagerFactory") EntityManagerFactory cnEntityManagerFactory) {
return new JpaTransactionManager(cnEntityManagerFactory);
}
#Bean(name = "cn-db")
#ConfigurationProperties(prefix = "spring.datasource.cn")
public DataSource cnDataSource() {
DriverManagerDataSource dbFinal = new DriverManagerDataSource();
dbFinal.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dbFinal.setUrl(env.getProperty("spring.datasource.cn.url"));
dbFinal.setUsername(env.getProperty("spring.datasource.cn.username"));
dbFinal.setPassword(env.getProperty("spring.datasource.cn.password"));
return dbFinal;
}
}
Created REPO's for both countries :
AU Repo :
#Repository
public interface AURepository extends CrudRepository<BeanName,String>{
}
CN Repo :
#Repository
public interface CNRepository extends CrudRepository<BeanName,String>{
}
Service class :
#Service
public class ServiceClass{
private AURepository auTxn;
private CNRepository cnTxn;
#Autowired
public ServiceClass(AURepository auTxn, CNRepository cnTxn) {
super();
this.auTxn = auTxn;
this.cnTxn = cnTxn;
}
public List<Bean> getData(String input, String country) {
List<Bean> data= new ArrayList<Bean>();
//If this is AU then it will connect to AU DB and retrieve data.
if (country.equals("AU")) {
data= auTxn.findall();
}
//If this is CN then it will connect to AU DB and retrieve data.
else if (country.equals("CN")) {
data= cnTxn.findall();
}
return data;
}
}

Hikaricp configuration for multiple datasources

I have a multi database application. Users can select the database on the login page.
Then the database is routing selected database thanks for AbstractRoutingDataSource from Spring.
I want to use HikariCP, but it needs dataSourceUrl. But my Datasource URL changes dynamically. How can I configure Hikaricp for multiple databases?
File application.properties:
#database1 properties
app.database1.connection.url = url1
app.database1.connection.username = sameusername
app.database1.connection.password = samepassword
#database2 properties
app.database2.connection.url = url2
app.database2.connection.username = sameusername
app.database2.connection.password = samepassword
My Datasource configuration class example:
public class DataSourceConfiguration {
#Autowired(required = false)
private PersistenceUnitManager persistenceUnitManager;
#Bean
#ConfigurationProperties(prefix = "app.database1.connection")
public DataSource database1DataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "app.database2.connection")
public DataSource database2DataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public DataSource appDataSource() {
DataSourceRouter router = new DataSourceRouter();
final HashMap<Object, Object> map = new HashMap<>(3);
map.put(DatabaseEnvironment.DATABASE1, database1DataSource());
map.put(DatabaseEnvironment.DATABASE2, database2DataSource());
router.setTargetDataSources(map);
return router;
}
#Bean
#Primary
#ConfigurationProperties("app.connection.jpa")
public JpaProperties appJpaProperties() {
return new JpaProperties();
}
private JpaVendorAdapter createJpaVendorAdapter(JpaProperties jpaProperties) {
AbstractJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();
adapter.setShowSql(jpaProperties.isShowSql());
adapter.setDatabase(jpaProperties.getDatabase());
adapter.setDatabasePlatform(jpaProperties.getDatabasePlatform());
adapter.setGenerateDdl(jpaProperties.isGenerateDdl());
return adapter;
}
My session scoped class instead of context holder:
#Component
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PreferredDatabaseSession implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private DatabaseEnvironment preferredDb;
public DatabaseEnvironment getPreferredDb() {
return preferredDb;
}
public void setPreferredDb(DatabaseEnvironment preferredDb) {
this.preferredDb = preferredDb;
}
}
If I understand your requirement correctly, you intend to define two data sources and for a given request you want to route your queries to a particular data source based on some condition.
The solution is:
File application.properties
#database1 properties
app.database1.connection.url = url1
app.database1.connection.username = username1
app.database1.connection.password = password1
#database2 properties
app.database2.connection.url = url2
app.database2.connection.username = username2
app.database2.connection.password = password2
#default
default.datasource.key=dataSource1
File CommonRoutingDataSource.java
public class CommonRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceName();
}
public void initDataSources(final DataSource dataSource1, final DataSource dataSource2,
final String defaultDataSourceKey) {
final Map<Object, Object> dataSourceMap = new HashMap<Object, Object>();
dataSourceMap.put("dataSource1", dataSource1);
dataSourceMap.put("dataSource2", dataSource2);
this.setDefaultTargetDataSource(dataSourceMap.get(defaultDataSourceKey));
this.setTargetDataSources(dataSourceMap);
}
}
File DataSourceContextHolder.java
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
private DataSourceContextHolder() {
// Private no-op constructor
}
public static final void setDataSourceName(final String dataSourceName) {
Assert.notNull(dataSourceName, "dataSourceName cannot be null");
contextHolder.set(dataSourceName);
}
public static final String getDataSourceName() {
return contextHolder.get();
}
public static final void clearDataSourceName() {
contextHolder.remove();
}
}
File DataSourceConfig.java
public class DataSourceConfig {
#Autowired
private Environment env;
#Autowired
#Bean(name = "dataSource")
public DataSource getDataSource(final DataSource dataSource1, final DataSource dataSource2) {
final CommonRoutingDataSource dataSource = new CommonRoutingDataSource();
dataSource.initDataSources(dataSource1, dataSource2, env.getProperty("default.datasource.key"));
return dataSource;
}
#Bean(name = "dataSource1")
public DataSource getDataSource1() throws SQLException {
// The exact DataSource class imported shall be as per your requirement - HikariCP, or Tomcat etc.
final DataSource dataSource = new DataSource();
dataSource.setDriverClassName();
dataSource.setUrl(env.getProperty("app.database1.connection.url"));
// Set all data source attributes from the application.properties file
return dataSource;
}
#Bean(name = "dataSource2")
public DataSource getDataSource2() throws SQLException {
// The exact DataSource class imported shall be as per your requirement - HikariCP, or Tomcat etc.
final DataSource dataSource = new DataSource();
dataSource.setDriverClassName();
dataSource.setUrl(env.getProperty("app.database2.connection.url"));
// set all data source attributes from the application.properties file
return dataSource;
}
}
Now, somewhere in your code (either an Aspect or Controller), you need to dynamically set the data source conditionally:
DataSourceContextHolder.setDataSourceName("dataSource1");
Note: It's better to declare the data source names as enums rather than strings "dataSource1", "dataSource2", etc.
The below snippet works for me
first.datasource.jdbc-url=jdbc-url
first.datasource.username=username
first.datasource.password=password
.
.
.
.
=================== In Java Configuration File ==================
#Primary
#Bean(name = "firstDataSource")
#ConfigurationProperties(prefix = "first.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "firstEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("firstDataSource") DataSource dataSource) {
Map<String, String> props = new HashMap<String, String>();
props.put("spring.jpa.database-platform", "org.hibernate.dialect.Oracle12cDialect");
.
.
.
return builder.dataSource(dataSource).packages("com.first.entity").persistenceUnit("firstDB")
.properties(props)
.build();
}
#Primary
#Bean(name = "firstTransactionManager")
public PlatformTransactionManager firstTransactionManager(
#Qualifier("firstEntityManagerFactory") EntityManagerFactory firstEntityManagerFactory) {
return new JpaTransactionManager(firstEntityManagerFactory);
}
second.datasource.jdbc-url=jdbc-url
second.datasource.username=username
second.datasource.password=password
.
.
.
.
=================== In Java Configuration File ==================
#Bean(name = "secondDataSource")
#ConfigurationProperties(prefix = "second.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("secondDataSource") DataSource dataSource) {
Map<String, String> props = new HashMap<String, String>();
props.put("spring.jpa.database-platform", "org.hibernate.dialect.Oracle12cDialect");
.
.
.
return builder.dataSource(dataSource).packages("com.second.entity").persistenceUnit("secondDB")
.properties(props)
.build();
}
#Bean(name = "secondTransactionManager")
public PlatformTransactionManager secondTransactionManager(
#Qualifier("secondEntityManagerFactory") EntityManagerFactory secondEntityManagerFactory) {
return new JpaTransactionManager(secondEntityManagerFactory);
}

Junit test cases for JPA repositories

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;
}
}

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