Spring JPA - Single Repository class with multiple JNDIs - spring

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

Related

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

SpringBoot JPA TransactionRequiredException when saveAndFlush is called

I'm writing a SpringBoot REST server. I have some problems with the data access layer. In my saveReport() service method, if I call save() method of JPA repository, I can't see any records in the db. If I use saveAndFlush() method of JPA repository, I get the following exception:
Caused by: javax.persistence.TransactionRequiredException: no transaction is in progress
Db config class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "qaEntityManagerFactory",
basePackages = { "qa.repository" }
)
public class QADbConfig {
#Bean(name = "qaDataSource")
#ConfigurationProperties(prefix = "spring.qa-datasource")
public HikariDataSource dataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
#Bean(name = "qaEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("qaDataSource") DataSource dataSource
) {
return builder
.dataSource(dataSource)
.packages("qa.model")
.persistenceUnit("qa")
.build();
}
#Bean(name = "qaTransactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("qaEntityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
In my service layer, I added #Transactional
#Service
public class ReportService implements IReportService {
static final ObjectMapper objectMapper = new ObjectMapper();
#Autowired
ReportRepository reportJpaRepository;
#Transactional(propagation=Propagation.REQUIRED)
#Override
public Report saveReport(Report report) {
reportJpaRepository.save(report); // I get no transaction exception if I use saveAndFlush method here
return report;
}
Also in the repository class, I added #Transactional
#Transactional
#Repository
public interface ReportJpaRepository extends JpaRepository<Report, Integer> {
List<Report> findByPrivateReport(Boolean privateReport);
}
Do you see what is wrong with my code?
Thanks

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

problem with writing to 2 databases from Spring boot

I was trying this code from github:https://github.com/kodinor/spring-data-many-dbs
It's an example of how to use multiple db's from a Spring-boot application. it worked fine but I added the Spring-boot-starter-web dependency and now I'm getting an error:
>Method requestMappingHandlerMapping in >org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$En>ableWebMvcConfiguration required a single bean, but 2 were found:
> - productDSEmFactory: defined by method 'productDSEmFactory' in class >path resource [com/kodinor/configuration/ProductDBConfiguration.class]
> - userDSEmFactory: defined by method 'userDSEmFactory' in class path >resource [com/kodinor/configuration/UserDBConfiguration.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<br>
I have two config files:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = UserRepository.class, entityManagerFactoryRef = "userDSEmFactory", transactionManagerRef = "userDSTransactionManager")
public class UserDBConfiguration {
#Primary
#Bean
#ConfigurationProperties("spring.datasource1")
public DataSourceProperties userDSProperties() {
return new DataSourceProperties();
}
#Primary
#Bean
public DataSource userDS(#Qualifier("userDSProperties") DataSourceProperties userDSProperties) {
return userDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
#Primary
#Bean
public PlatformTransactionManager userDSTransactionManager(EntityManagerFactory userDSEmFactory) {
return new JpaTransactionManager(userDSEmFactory);
}
}
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackageClasses = ProductRepository.class, entityManagerFactoryRef = "productDSEmFactory", transactionManagerRef = "productDSTransactionManager")
public class ProductDBConfiguration {
#Bean
#ConfigurationProperties("spring.datasource2")
public DataSourceProperties productDSProperties() {
return new DataSourceProperties();
}
#Bean
public DataSource productDS(#Qualifier("productDSProperties") DataSourceProperties productDSProperties) {
return productDSProperties.initializeDataSourceBuilder().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean productDSEmFactory(#Qualifier("productDS") DataSource productDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(productDS).packages(Product.class).build();
}
#Bean
public PlatformTransactionManager productDSTransactionManager(EntityManagerFactory productDSEmFactory) {
return new JpaTransactionManager(productDSEmFactory);
}
}
Application.properties:
spring.jpa.hibernate.ddl-auto=create
spring.jpa.generate-ddl=true
spring.datasource1.url=jdbc:mysql://localhost:3306/userdb?autoReconnect=true&useSSL=false
spring.datasource1.username=user
spring.datasource1.password=pass
spring.datasource2.url=jdbc:mysql://localhost:3306/productdb?autoReconnect=true&useSSL=false
spring.datasource2.username=user
spring.datasource2.password=pass
And a simple main app that adds some init data:
#SpringBootApplication
public class SpringDataManyDbsApplication {
public static void main(String[] args) {
SpringApplication.run(SpringDataManyDbsApplication.class, args);
}
#Autowired
private UserRepository userRepository;
#Autowired
private ProductRepository productRepository;
#PostConstruct
void init () {
User user = new User("john", "doe");
userRepository.save(user);
Product product = new Product("chair", 5);
productRepository.save(product);
}
}
I tried to add the #Primary annotation to userDSEmFactory. The error goes away but the product data isn't saved anymore. Any ideas how to save this problem? I don't have a lot of experience with Spring-boot and I've read dozens of articles but many seem to do things in a different way. Thanks so much for helping me out!
update
I've added the #Primary annotation like this:
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
No there are no more errors, but only the user is being saved in the primary db and nothing is being saved to the product (second) database.
So if anybody has suggestions what could be the cause of that..
You are missing #Primary annotation on your userDSEmFactory
#Primary
#Bean
public LocalContainerEntityManagerFactoryBean userDSEmFactory(#Qualifier("userDS") DataSource userDS, EntityManagerFactoryBuilder builder) {
return builder.dataSource(userDS).packages(User.class).build();
}
And also it is goot to use once #EnableTransactionManagement at your main class.

SpringBoot - Multimodule projet & datasources - Unable to scan #Entity.class

I have some trouble to configure my multimodule projet with multiple datasources. So, I need your help.
I have allready try :
Springboot multimodule project
SpringBoot ComponentScan issue with multi-module project
SpringBoot scanBasePackages not working in multi module project
And https://mhdevelopment.wordpress.com/2014/09/16/how-to-configure-spring-boot-with-jpa-and-multiple-projects/
But nothing work, i have the same issue.
So, that m'y multi-module classpath:
BusinessModule, that include repositories and Entities:
base package : com.mycompany.business.
In that module, i have an external dependency where we can found #Entity class ( package : com.mycompany.model)
RestModule, that are used to use BusinessModule.
base package : com.mycompany.rest
Business configuration
Configuration for datasources
First data source:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "entityManagerFactory",
basePackages = {
"com.mycompany.business.repository", "com.mycompany.model" })
#PropertySource(value =
"file:${CPROCESS_INSTALL}/server/custom_config/datasources.properties")
public class BusinessDbConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "business.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.mycompany.business.model", "com.mycompany.model")
.persistenceUnit("business").build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("entityManagerFactory") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Second data source:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(entityManagerFactoryRef = "barEntityManagerFactory", transactionManagerRef = "barTransactionManager", basePackages = {
"com.mycompany.bonita.repository" })
#PropertySource(value = "file:${CPROCESS_INSTALL}/server/custom_config/datasources.properties")
public class BonitaDbConfig {
#Bean(name = "barDataSource")
#ConfigurationProperties(prefix = "bonita.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.mycompany.bonita.model")
.persistenceUnit("bar").build();
}
#Bean(name = "barTransactionManager")
public PlatformTransactionManager barTransactionManager(
#Qualifier("barEntityManagerFactory") EntityManagerFactory barEntityManagerFactory) {
return new JpaTransactionManager(barEntityManagerFactory);
}
}
Repository configuration
#RepositoryRestResource
public interface CDocumentRepository extends JpaRepository<CDocument, Long> {
#Query("select dp from CDocument d where d.persistenceid = ?1")
public CDocument findByPersistenceId(Long persistenceId);
}
Entity
#javax.persistence.Entity(name = "CDocument")
#Table(name = "CDOCUMENT")
public class CDocument{ ... }
Rest module configuration
App configuration
#SpringBootApplication(scanBasePackages= {"com.mycompany"})
#EnableJpaRepositories("com.mycompany")
#EntityScan("com.mycompany")
#Import({ BonitaDbConfig.class, BusinessDbConfig.class })
public class RestApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(RestApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(RestApplication.class, args);
}
}
My controller
#RestController
public class CreateSpaceController {
private CDocushareConnectRepository cDocushareConnectRepository;
private CDocumentRepository cDocumentRepository;
private DocumentRepository documentRepository;
private CustomtableRepository customTableRepository;
private CFieldsMappingRepository cFieldsMappingRepository;
public CreateSpaceController(CDocumentRepository cDocumentRepository) {
super();
this.cDocumentRepository = cDocumentRepository;
}
...
}
When i try to start test or app, i have this error:
java.lang.IllegalArgumentException: Not a managed type: class com.mycompany.model.CDocument
I don't know why... Any help ?

Resources