Not getting value from property inside attributeconverter when using multiple datasource configuration for spring boot application - spring-boot

I have created spring boot application with multiple datasources by referring link below:
https://medium.com/#joeclever/using-multiple-datasources-with-spring-boot-and-spring-data-6430b00c02e7
And added encryption at entity level by referring link below:
https://github.com/sunitk/generic-jpa-converter-encrypt-decrypt
With multiple datasources configuration I am not able to get the property value in Attribute Converter. Its coming as null.
But with single datasource (Default properties) I am able to get the property value.
Please let me know how can I get property value with multiple datasource configuration instead of default single datasource properties ?

Using a #Convert(converter = Xxx.class) has nothing to do with a specific dataSource. You should be able to use the converter in every #Entity.
Here is a working demo: multi-datasource-converter
It uses your referenced converter on multiple datasources.
Snippets
application.properties
foo.datasource.jdbcUrl=jdbc:h2:mem:foo
foo.datasource.username=sa
foo.datasource.password=
foo.datasource.driver-class-name=org.h2.Driver
bar.datasource.jdbcUrl=jdbc:h2:mem:bar
bar.datasource.username=sa
bar.datasource.password=
bar.datasource.driver-class-name=org.h2.Driver
Configuration of the datasources, select one as #Primary and point both EntityManagerFactoryBuilders to the correct packages
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "fooEntityManagerFactory",
transactionManagerRef = "fooTransactionManager",
basePackageClasses = FooRepository.class)
public class FooJpaConfiguration {
#Primary
#Bean(name = "fooDataSource")
#ConfigurationProperties(prefix = "foo.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "fooEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder, #Qualifier("fooDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.demo.foo").persistenceUnit("foo")....
....
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "barEntityManagerFactory",
transactionManagerRef = "barTransactionManager",
basePackageClasses = BarRepository.class )
public class BarJpaConfiguration {
#Bean(name = "barDataSource")
#ConfigurationProperties(prefix = "bar.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "barEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean barEntityManagerFactory(
EntityManagerFactoryBuilder builder, #Qualifier("barDataSource") DataSource dataSource) {
return builder.dataSource(dataSource).packages("com.example.demo.bar").persistenceUnit("bar")
And use the converter in your Entities
package com.example.demo.foo;
...
#Entity
public class Foo {
#Id
private Long id;
private String name;
#Convert(converter = StringEncryptDecryptConverter.class)
private String secret;
....
package com.example.demo.bar;
...
#Entity
public class Bar {
#Id
private Long id;
private String name;
#Convert(converter = StringEncryptDecryptConverter.class)
private String secret;

Related

Spring Boot, required a single bean, but 2 were found when creating multiple datasources

I have an application in Spring Boot 1.4 which I'm trying to add additional datasources to.
First I setup a primary datasource and ran the application to check it still worked, and it did. Then I went ahead and added a second datasource, but when I did that I got the following error;
Description:
Field userRepo in com.nationallocums.config.CustomUserDetailsService required a single bean, but 2 were found:
- nlDoctorsEntityManager: defined by method 'nlDoctorsEntityManager' in class path resource [com/nationallocums/config/NLDoctorsDataSourceConfiguration.class]
- primaryEntityManager: defined by method 'primaryEntityManager' in class path resource [com/nationallocums/config/PrimaryDataSourceConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I don't understand why I'm seeing this error, as I've clearly marked one of the datasources with #Primary, but it seems Spring Boot isn't picking that up.
Here's my two datasource configurations;
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "primaryEntityManager",
transactionManagerRef = "primaryTransactionManager",
basePackages = { "com.nationallocums.repository" })
#EnableTransactionManagement
public class PrimaryDataSourceConfiguration {
#Bean(name = "primaryDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
#Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "primaryEntityManager")
#Primary
public LocalContainerEntityManagerFactoryBean primaryEntityManager(final EntityManagerFactoryBuilder builder, #Qualifier("primaryDataSource") final DataSource dataSource) {
final Map<String, String> properties = new HashMap<>();
return builder
.dataSource(dataSource)
.properties(properties)
.packages("com.nationallocums.model")
.persistenceUnit("primary")
.build();
}
#Bean(name = "primaryTransactionManager")
#Primary
public PlatformTransactionManager nlDoctorsTransactionManager(#Qualifier("primaryEntityManager") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
and...
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "nlDoctorsEntityManager",
transactionManagerRef = "nlDoctorsTransactionManager",
basePackages = { "com.nationallocums.eclipse.nldoctorsrepository" })
#EnableTransactionManagement
public class NLDoctorsDataSourceConfiguration {
#Bean(name = "nlDoctorsDataSource")
#ConfigurationProperties(prefix = "spring.nldoctors-datasource")
public DataSource nlDoctorsDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "nlDoctorsEntityManager")
public LocalContainerEntityManagerFactoryBean nlDoctorsEntityManager(final EntityManagerFactoryBuilder builder, #Qualifier("nlDoctorsDataSource") final DataSource dataSource) {
final Map<String, String> properties = new HashMap<>();
return builder
.dataSource(dataSource)
.properties(properties)
.packages("com.nationallocums.eclipse.model")
.persistenceUnit("nlDoctors")
.build();
}
#Bean(name = "nlDoctorsTransactionManager")
public PlatformTransactionManager nlDoctorsTransactionManager(#Qualifier("nlDoctorsEntityManager") final EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Can anyone spot what I've done wrong?
I managed to fix this by changing my repository from...
#PersistenceContext
private EntityManager entityManager;
to...
#Autowired
#Qualifier("primaryDataSource")
private EntityManager entityManager;

Spring-boot TransactionRequiredException: Executing an update/delete query

My service method is annotated with Transnational (org.springframework.transaction.annotation)
But still a "TransactionRequiredException" occurs when making an update on table Persons.
A select on the same table works fine.
#Transactional
public String myMethod(String contractNo){
myRepository.resetValues(contractNo);
}
#Repository
public interface MyRepository extends JpaRepository<Persons, Long> {
#Modifying
#Query(value = "UPDATE PERSONS SET status = 0 WHERE LOGIN_NAME like :contractNo", nativeQuery = true)
void resetValues(#Param("contractNo") String contractNo);
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager",
basePackages = { "com.mypackage.repositories" }
)
public class EBankingDBConfig {
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.eba-datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder.dataSource(dataSource)
.packages("com.mypackage.model")
.persistenceUnit("myPersistenceUnit")
.build();
}
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Because there are multiple TransactionManagers beans defined in the project, you have to annotate transactions of non-primary TransactionaManagers with their names. For example, in my config above, the TransactionManager name is defined as "transactionManager".
So, the Transactional annotation on methods should look line this : #Transactional("transactionManager")

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

Spring Boot - Same repository and same entity for different databases

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

Resources