I have this java based JPA configuration for my spring project:
#Configuration
#EnableJpaRepositories(basePackageClasses = {PackageMarker.class})
#EnableTransactionManagement(proxyTargetClass = true)
#EnableCaching
public class FooJPAConfig implements CachingConfigurer {
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
return cacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
//...
}
How can I tell spring to use a specific ehcache.xml file?
You need to alter cacheManager in order to integrate EhCache. Your current code does not make EhCache enter the picture.
The configuration would look like
#Configuration
#EnableJpaRepositories(basePackageClasses = {PackageMarker.class})
#EnableTransactionManagement(proxyTargetClass = true)
#EnableCaching
public class FooJPAConfig implements CachingConfigurer {
#Bean
public EhCacheManagerFactoryBean cacheFactoryBean() {
EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean();
ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("whatever-name.xml")); //this is where you set the location of the eh-cache configuration file
return ehCacheManagerFactoryBean;
}
#Bean
#Override
public CacheManager cacheManager() {
EhCacheCacheManager cacheManager = new EhCacheCacheManager();
cacheManager.setCacheManager(cacheFactoryBean().getObject());
return cacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new DefaultKeyGenerator();
}
}
You will also have to have spring-context-support as a dependency on your classpath (applies for Spring 3.2)
Note that the code above activates Spring -EhCache integration, not JPA - EhCache integration. That means that you can use Spring's #Cacheable not EhCache's #Cache on entities.
After all I could solve the problem by adding this code to the configuration class:
protected static final String EHCACHE_CONFIGURATIONRESOURCENAME_PROPERTY = "net.sf.ehcache.configurationResourceName";
#Bean(name = BEAN_ENTITY_MANAGER_FACTORY)
public EntityManagerFactory entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = createLocalContainerEntityManagerFactoryBean();
// ...
processOptionalProperty(EHCACHE_CONFIGURATIONRESOURCENAME_PROPERTY, em);
return em.getObject();
}
protected void processOptionalProperty(String propertyName, LocalContainerEntityManagerFactoryBean em) {
String value = "";// read propertyName from configuration file
setPropertyValue(propertyName, em, value);
}
protected void setPropertyValue(String propertyName, LocalContainerEntityManagerFactoryBean em, Object value) {
if (value != null) {
Map<String, Object> jpaPropertyMap = em.getJpaPropertyMap();
jpaPropertyMap.put(propertyName, value);
em.setJpaPropertyMap(jpaPropertyMap);
}
}
Related
We use Spring and jOOQ and at present read/write from primary DB. We do have replica DB's and are trying to have non critical reads (like from quartz jobs, pure read api's, etc.) to go to read.
I referred to https://vladmihalcea.com/read-write-read-only-transaction-routing-spring/ and understand that we need a transactional routing layer introduced which would point one way or the other depending on
#Transactional (readonly = true)
However, this doesn't seem to be working in our case, wherein all routing seems to be happening to primary db itself.
Here's the code configurations we use for primary
#Bean(name = "primaryTransactionAwareDataSourceProxy")
public TransactionAwareDataSourceProxy primaryTransactionAwareDataSource(
#Qualifier("primaryDataSource") DataSource primaryDataSource) {
return new TransactionAwareDataSourceProxy(primaryDataSource);
}
#Bean(name = "xyz")
public DataSourceConnectionProvider primaryConnectionProvider(
#Qualifier("primaryTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
return new DataSourceConnectionProvider(datasource);
}
#Bean(name = "primaryDsl")
#Primary
public DefaultDSLContext primaryDsl(
#Qualifier("primaryConfiguration") DefaultConfiguration configuration) {
return new DefaultDSLContext(configuration);
}
#Bean(name = "primaryConfiguration")
public DefaultConfiguration primaryConfiguration(
#Qualifier("primaryDatabaseProperties") DatabaseProperties props,
#Qualifier("xyz") DataSourceConnectionProvider provider) {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(provider);
jooqConfiguration.set(SQLDialect.MYSQL);
Settings settings = new Settings()
.withRenderMapping(new RenderMapping()
.withSchemata(new MappedSchema()
.withInput(<name>)
.withOutput(props.getName())));
jooqConfiguration.set(settings);
return jooqConfiguration;
}
and for replica we have along similar lines
#Bean(name = "replicaTransactionAwareDataSourceProxy")
public TransactionAwareDataSourceProxy replicaTransactionAwareDataSource(
#Qualifier("replicaDataSource") DataSource replicaDataSource) {
return new TransactionAwareDataSourceProxy(replicaDataSource);
}
#Bean(name = "xyz")
public DataSourceConnectionProvider replicaConnectionProvider(
#Qualifier("replicaTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
return new DataSourceConnectionProvider(datasource);
}
#Bean(name = "replicaDsl")
public DefaultDSLContext replicaDsl(
#Qualifier("replicaConfiguration") DefaultConfiguration configuration) {
return new DefaultDSLContext(configuration);
}
#Bean(name = "replicaConfiguration")
public DefaultConfiguration replicaConfiguration(
#Qualifier("replicaDatabaseProperties") DatabaseProperties props,
#Qualifier("xyz") DataSourceConnectionProvider provider) {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(provider);
jooqConfiguration.set(SQLDialect.MYSQL);
Settings settings = new Settings()
.withRenderMapping(new RenderMapping()
.withSchemata(new MappedSchema()
.withInput(<name>)
.withOutput(props.getName())));
jooqConfiguration.set(settings);
return jooqConfiguration;
}
The data sources are configured as
#Bean(name = "primaryDatabaseProperties")
#ConfigurationProperties(prefix = "database")
public DatabaseProperties primaryDatabaseProperties() {
return new DatabaseProperties ();
}
#Bean(name = "replicaDatabaseProperties")
#ConfigurationProperties(prefix = "database.replica")
public DatabaseProperties replicaDatabaseProperties() {
return new DatabaseProperties ();
}
#Bean(name = "primaryDataSource")
public DataSource primaryDataSource(
#Qualifier("primaryDatabaseProperties") DatabaseProperties props) {
return dataSource(props);
}
#Bean(name = "replicaDataSource")
public DataSource replicaDataSource(
#Qualifier("replicaDatabaseProperties") DatabaseProperties props) {
return dataSource(props);
}
And transactional routing data source defined as
#Bean
public TransactionRoutingDataSource actualDataSource(
#Qualifier("primaryTransactionAwareDataSourceProxy") DataSource primaryDataSource,
#Qualifier("replicaTransactionAwareDataSourceProxy") DataSource replicaDataSource) {
TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>() {{
put(DataSourceType.READ_WRITE, primaryDataSource);
put(DataSourceType.READ_ONLY, replicaDataSource);
}};
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
I have an implementation of AbstractRoutingDataSource defined to do the conditional check of if the current transaction is ready only and route to correct datasource accordingly
public class TransactionRoutingDataSource extends AbstractRoutingDataSource {
#Nullable
#Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ?
DataSourceType.READ_ONLY : DataSourceType.READ_WRITE;
}
}
And I use annotations to indicate the necessary routing in the service layer
#Override
#Transactional(readOnly = true)
public <return_type> <method_name>() {}
Any pointers as to what I am missing to have jOOQ route correctly to read db?
I have a spring boot project and I have one internal database with the configuration on the application.properties. In this database I have a Company table which contains the connection informations to external databases (all the external databases have same structure).
I created a class which create datasource when we need :
public class PgDataSource {
private static Map<Long, DataSource> dataSourceMap = new HashMap<>();
private static void createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://"+company.getUrl()+"/"+company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
dataSourceMap.put(company.getId(), new HikariDataSource(hikariConfig));
}
public static DataSource getDataSource(Company company) {
if (!dataSourceMap.containsKey(company.getId()))
createDataSource(company);
return dataSourceMap.get(company.getId());
}
}
Could you tell me if this solution is the best and if I can use JPA with this solution ?
Thanks
The difficulty in your setup is not multiple datasources, but the fact that they are dynamic, i.e. determined at run time.
In addition to a DataSource JPA uses EntityManagerFactory and TransactionManager, which are determined statically i.e. at compile time. So it's not easy to use JPA with dynamic data sources.
In Spring Boot 2 you can try the AbstractRoutingDataSource, which allows to route JPA calls to a different datasource based on some (thread-bound) context. Here's an example of how it can be used and a demo application.
Alternatively you can turn your setup into a static setup, then use the regular multiple datasource approach. The downside is that the list of "companies" will be fixed at compile time, and so is probably not what you want.
Thanks it's work fine !
My solution :
I create a first datasource for my local database with #Primary annotation.
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "localEntityManagerFactory",
basePackages = {"fr.axygest.akostaxi.local"}
)
public class LocalConfig {
#Primary
#Bean(name = "dataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "localEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder,
#Qualifier("dataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.local.model")
.persistenceUnit("local")
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager(
#Qualifier("localEntityManagerFactory") EntityManagerFactory
entityManagerFactory
) {
return new JpaTransactionManager(entityManagerFactory);
}
}
Next, for the x external databases save in the company table of the local database, I use AbstractRoutingDataSource
I store a current context as a ThreadLocal :
public class ThreadPostgresqlStorage {
private static ThreadLocal<Long> context = new ThreadLocal<>();
public static void setContext(Long companyId) {
context.set(companyId);
}
public static Long getContext() {
return context.get();
}
}
I defined the RoutingSource to extend the AbstractRoutingDataSource :
public class RoutingSource extends AbstractRoutingDataSource
{
#Override
protected Object determineCurrentLookupKey() {
return ThreadPostgresqlStorage.getContext();
}
}
And the config class create all the databases connection saved in company table :
#Configuration
#EnableJpaRepositories(
basePackages = {"fr.axygest.akostaxi.postgresql"},
entityManagerFactoryRef = "pgEntityManager"
)
#EnableTransactionManagement
public class PgConfig {
private final CompanyRepository companyRepository;
#Autowired
public PgConfig(CompanyRepository companyRepository) {
this.companyRepository = companyRepository;
}
#Bean(name = "pgDataSource")
public DataSource pgDataSource() {
RoutingSource routingSource = new RoutingSource();
List<Company> companies = companyRepository.findAll();
HashMap<Object, Object> map = new HashMap<>(companies.size());
companies.forEach(company -> {
map.put(company.getId(), createDataSource(company));
});
routingSource.setTargetDataSources(map);
routingSource.afterPropertiesSet();
return routingSource;
}
#Bean(name = "pgEntityManager")
public LocalContainerEntityManagerFactoryBean pgEntityManager(
EntityManagerFactoryBuilder builder,
#Qualifier("pgDataSource") DataSource dataSource) {
return builder
.dataSource(dataSource)
.packages("fr.axygest.akostaxi.postgresql.model")
.persistenceUnit("pg")
.properties(jpaProperties())
.build();
}
private DataSource createDataSource(Company company) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setMaximumPoolSize(10);
hikariConfig.setMinimumIdle(1);
hikariConfig.setJdbcUrl("jdbc:postgresql://" + company.getUrl() + "/" + company.getIdClient());
hikariConfig.setUsername(company.getUsername());
hikariConfig.setPassword(company.getPassword());
return new HikariDataSource(hikariConfig);
}
private Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return props;
}
}
I have project spring-boot using datasource routes in three diferents datasources.
This is my configuration:
#Configuration
#EnableCaching
public class CachingConfiguration extends CachingConfigurerSupport {
#Override
public KeyGenerator keyGenerator() {
return new EnvironmentAwareCacheKeyGenerator();
}
}
--
public class DatabaseContextHolder {
private static final ThreadLocal<DatabaseEnvironment> CONTEXT =
new ThreadLocal<>();
public static void set(DatabaseEnvironment databaseEnvironment) {
CONTEXT.set(databaseEnvironment);
}
public static DatabaseEnvironment getEnvironment() {
return CONTEXT.get();
}
public static void clear() {
CONTEXT.remove();
}
}
--
#Configuration
#EnableJpaRepositories(basePackageClasses = UsuarioRepository.class,
entityManagerFactoryRef = "customerEntityManager",
transactionManagerRef = "customerTransactionManager")
#EnableTransactionManagement
public class DatasourceConfiguration {
#Bean
#ConfigurationProperties(prefix = "spring.ciclocairu.datasource")
public DataSource ciclocairuDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.palmas.datasource")
public DataSource palmasDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.megabike.datasource")
public DataSource megabikeDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#Primary
public DataSource customerDataSource() {
DataSourceRouter router = new DataSourceRouter();
final HashMap<Object, Object> map = new HashMap<>(3);
map.put(DatabaseEnvironment.CICLOCAIRU, ciclocairuDataSource());
map.put(DatabaseEnvironment.PALMAS, palmasDataSource());
map.put(DatabaseEnvironment.MEGABIKE, megabikeDataSource());
router.setTargetDataSources(map);
return router;
}
#Autowired(required = false)
private PersistenceUnitManager persistenceUnitManager;
#Bean
#Primary
#ConfigurationProperties("spring.jpa")
public JpaProperties customerJpaProperties() {
return new JpaProperties();
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean customerEntityManager(
final JpaProperties customerJpaProperties) {
EntityManagerFactoryBuilder builder =
createEntityManagerFactoryBuilder(customerJpaProperties);
return builder.dataSource(customerDataSource()).packages(Users.class)
.persistenceUnit("customerEntityManager").build();
}
#Bean
#Primary
public JpaTransactionManager customerTransactionManager(
#Qualifier("customerEntityManager") final EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
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;
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(
JpaProperties customerJpaProperties) {
JpaVendorAdapter jpaVendorAdapter =
createJpaVendorAdapter(customerJpaProperties);
return new EntityManagerFactoryBuilder(jpaVendorAdapter,
customerJpaProperties.getProperties(), this.persistenceUnitManager);
}
}
--
public class DataSourceRouter extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
if(DatabaseContextHolder.getEnvironment() == null)
DatabaseContextHolder.set(DatabaseEnvironment.CICLOCAIRU);
return DatabaseContextHolder.getEnvironment();
}
}
--
public class EnvironmentAwareCacheKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
String key = DatabaseContextHolder.getEnvironment().name() + "-" + (
method == null ? "" : method.getName() + "-") + StringUtils
.collectionToDelimitedString(Arrays.asList(params), "-");
return key;
}
}
I set datasource using
DatabaseContextHolder.set(DatabaseEnvironment.CICLOCAIRU);
Go to problem:
For example, two users in diferents datasources: 1 and 2
if one user using datasource 1, and send a request,
The other user that using datasource 2,
yours next request , instead of datasource 2, this get datasource 1. I think that this ThreadLocal<DatabaseEnvironment> CONTEXT =
new ThreadLocal<>(); was exclusive for request, But this does not seem to be so.
Iam sorry if this not be clear.
In realy, i need that DataSurceRouter were exclusive for each request, and an request not intefer in another.
I wrong about i think of DatasourceRouter or my code is bad ?
The issue probably occurs because of server thread pool: you have a given number of threads, and each request is served rolling among them.
When the server recycles a thread, the thread local variable has that value already set from the previous cycle, so you need to flush that value after each request, leaving the thread in a clean state.
I am working on spring boot multi tenancy application. I have configured multi datasources as shown below :
application.properties
spring.multitenancy.datasource1.url=jdbc:mysql://localhost:3306/db1
spring.multitenancy.datasource1.username=root
spring.multitenancy.datasource1.password=****
spring.multitenancy.datasource1.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.multitenancy.datasource2.username=root
spring.multitenancy.datasource2.password=****
spring.multitenancy.datasource2.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.multitenancy.datasource3.url=jdbc:mysql://localhost:3306/db3
spring.multitenancy.datasource3.username=root
spring.multitenancy.datasource3.password=****
spring.multitenancy.datasource3.driver-class-name=com.mysql.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
DataSourceBasedMultiTenantConnectionProviderImpl.java
#Component
public class DataSourceBasedMultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 8168907057647334460L;
private static final String DEFAULT_TENANT_ID = "tenant_1";
#Autowired
private DataSource dataSource1;
#Autowired
private DataSource dataSource2;
#Autowired
private DataSource dataSource3;
private Map<String, DataSource> map;
#PostConstruct
public void load() {
map = new HashMap<>();
map.put("tenant_1", dataSource1);
map.put("tenant_2", dataSource2);
map.put("tenant_3", dataSource3);
}
#Override
protected DataSource selectAnyDataSource() {
return map.get(DEFAULT_TENANT_ID);
}
#Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier);
}
}
MultitenancyProperties.java
#ConfigurationProperties("spring.multitenancy")
public class MultitenancyProperties {
#NestedConfigurationProperty
private DataSourceProperties datasource1;
#NestedConfigurationProperty
private DataSourceProperties datasource2;
#NestedConfigurationProperty
private DataSourceProperties datasource3;
public DataSourceProperties getDatasource1() {
return datasource1;
}
public void setDatasource1(DataSourceProperties datasource1) {
this.datasource1 = datasource1;
}
public DataSourceProperties getDatasource2() {
return datasource2;
}
public void setDatasource2(DataSourceProperties datasource2) {
this.datasource2 = datasource2;
}
public DataSourceProperties getDatasource3() {
return datasource3;
}
public void setDatasource3(DataSourceProperties datasource3) {
this.datasource3 = datasource3;
}
}
MultiTenancyJpaConfiguration.java
#Configuration
#EnableConfigurationProperties(JpaProperties.class)
public class MultiTenancyJpaConfiguration {
#Autowired
private DataSource dataSource;
#Autowired
private JpaProperties jpaProperties;
#Autowired
private MultiTenantConnectionProvider multiTenantConnectionProvider;
#Autowired
private CurrentTenantIdentifierResolver currentTenantIdentifierResolver;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.putAll(jpaProperties.getHibernateProperties(dataSource));
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.MySQLDialect");
return builder.dataSource(dataSource).packages(HotelEntity.class.getPackage().getName()).properties(hibernateProps).jta(false).build();
}
}
Application launcher
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
#EnableConfigurationProperties(MultitenancyProperties.class)
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
}
}
When I run the boot application, all tables are created in only first data source.
1) How can I create tables in all data sources on application startup?
2) How to see connections opened/closed for each of the data sources?
3) Is there a better way of configuring multi tenancy application using spring boot for better performance?
As #Alex said, you need differnt EntityManagers, TransactionManager and Datasources. Here is how I would do it
#Configuration
#EnableJpaRepositories(
entityManagerFactoryRef = "dataSource1EntityManagerFactory",
transactionManagerRef = "dataSource1TransactionManager",
basePackageClasses = dataSource1Repository.class)
public class DataSource1Config extends SqlConfig{// Put all common code in base class SqlConfig. If not remove it
#Bean
#Primary
public DataSource dataSource1() {
//create dataSource using MultitenancyProperties::getDataSource1
}
#Primary
#Bean(name = "dataSource1TransactionManager")
PlatformTransactionManager dataSource1TransactionManager(EntityManagerFactory dataSource1EntityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(dataSource1EntityManagerFactory);
return txManager;
}
#Primary
#Bean(name = "dataSource1EntityManagerFactory")
LocalContainerEntityManagerFactoryBean dataSource1EntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource1());
em.setPackagesToScan(dataSource1Repository.class.getPackage().getName(), dataSource1ModelClass.class.getPackage().getName());
em.setPersistenceUnitName("dataSource1Db");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
}
You can create two other classes like this. Remember to use #Primary on only one instace of datasource, transactionmanger and entitymanager(doesn't matter which one). Another word of caution, make sure Repository classes are in different packages for all three databases.
Try adding the following properties:
spring.jpa.generate-ddl=true
You need 2 different persistence factories, not one, each should produce different EntityManagers for different datasources. Also each entity mapping should be marked to been used only with one entity manager.
See full solution here:
http://www.baeldung.com/spring-data-jpa-multiple-databases
I'm using spring-jpa. I have 2 tests.
#Test
#Transactional
public void testFindAll() {
List<Engine> eList = engineService.findAll();
Engine e = eList.get(0); //this engine id=3
List<Translation> tList = e.getTranslations();
for(Translation t : tList) {
...
}
}
This method fails with this exception:
org.hibernate.LazyInitializationException: failed to lazily initialize
a collection of role: xxx.Engine.translations, could not initialize
proxy - no Session
However, this method works just fine:
#Test
#Transactional
public void testFindOne() {
Engine e = engineService.findOne(3);
List<Translation> tList = e.getTranslations();
for(Translation t : tList) {
...
}
}
Why translation list is successfully loaded in one case, but not in another?
EDIT: service/repo code:
public interface EngineRepository extends JpaRepository<Engine, Integer>
{
}
.
#Service
#Transactional
public class EngineService
{
#Autowired
private EngineRepository engineRepository;
public List<Engine> findAll()
{
return engineRepository.findAll();
}
public Engine findOne(Integer engId)
{
return engineRepository.findOne(engId);
}
}
.
public class Engine implements Serializable {
...
#OneToMany
#JoinColumn(name="ID", referencedColumnName="TRAN_ID", insertable=false, updatable=false, nullable=true)
#LazyCollection(LazyCollectionOption.EXTRA)
private List<Translation> translations;
...
}
Config:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"xxx.dao"})
#ComponentScan(basePackages = {"xxx.dao", "xxx.service", "xxx.bean"})
#PropertySource("classpath:application.properties")
public class SpringDataConfig {
#Autowired
private Environment environment;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setUrl(environment.getProperty("db.url"));
dataSource.setDriverClassName(environment.getProperty("db.driverClass"));
dataSource.setUsername(environment.getProperty("db.username"));
dataSource.setPassword(environment.getProperty("db.password"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws NamingException {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setDatabase(Database.POSTGRESQL);
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getProperty("hibernate.showSQL"));
properties.put("hibernate.format_sql", environment.getProperty("hibernate.formatSQL"));
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource());
entityManagerFactoryBean.setPackagesToScan("xxx.model");
entityManagerFactoryBean.setJpaVendorAdapter(hibernateJpaVendorAdapter);
entityManagerFactoryBean.setJpaProperties(properties);
return entityManagerFactoryBean;
}
#Bean
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
I think the problem here is that the session gets closed after the first line in the first case. You should check out the JpaRepository implementation of findAll().
Integration Testing with Spring
It seems you're failing to provide a Spring Context within your TestCase, what that means? The #Transactional is being ignored. Therefore you end up with the closed session exception, because there is no transaction.
Take a look how to configure a TestCase with a Spring Context here
#RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
#ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
#Autowired
EngineService engineService;
#Test
#Transactional
public void testFindOne() {}
#Test
#Transactional
public void testFindAll() {}
}