I want to implement a read-only routing for db, I have researched lots of ways to achieve this and came across the following repository.
I couldn't understand the function of the following two methods so I simplified and changed it in my repository in the following way.
transactionManager() & entityManagerFactory(EntityManagerFactoryBuilder builder)
If I keep having these methods I am getting the error , how could I overcome this error?
Thanks.
org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL "create index contact_tag_ix_uuid on contact_tag (uuid)" via JDBC Statement
at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlString(AbstractSchemaMigrator.java:581) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applySqlStrings(AbstractSchemaMigrator.java:526) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.applyIndexes(AbstractSchemaMigrator.java:348) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.GroupedSchemaMigratorImpl.performTablesMigration(GroupedSchemaMigratorImpl.java:88) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.performMigration(AbstractSchemaMigrator.java:220) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaMigrator.doMigration(AbstractSchemaMigrator.java:123) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:196) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:85) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:335) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:471) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1498) ~[hibernate-core-5.6.14.Final.jar:5.6.14.Final]
package ru.project.master.slave.datasources;
import java.util.Map;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.google.common.collect.Maps;
import ru.project.master.slave.properties.Properties;
import ru.project.master.slave.routing.RoutingDataSource;
#Configuration
#ComponentScan
#EnableJpaRepositories("ru.project.master.slave.dao")
#EnableTransactionManagement
public class DataSourcesConfiguration {
#Autowired
private Properties props;
#Bean(name = "masterDataSource")
#Primary
public DataSource masterDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(props.getProperty("spring.datasource.master.driver-class-name"));
dataSource.setDefaultAutoCommit(false);
dataSource.setUsername(props.getProperty("spring.datasource.master.username"));
dataSource.setPassword(props.getProperty("spring.datasource.master.password"));
dataSource.setUrl(props.getProperty("spring.datasource.master.url"));
return dataSource;
}
#Bean
public RoutingDataSource routingDataSource() {
RoutingDataSource dataSource = new RoutingDataSource();
dataSource.setDefaultTargetDataSource(masterDataSource());
Map<Object, Object> targets = Maps.newHashMap();
targets.put(DataSourceType.MASTER, masterDataSource());
targets.put(DataSourceType.SLAVE, slaveDataSource());
dataSource.setTargetDataSources(targets);
dataSource.afterPropertiesSet();
return dataSource;
}
#Bean(name = "slaveDataSource")
public DataSource slaveDataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName(props.getProperty("spring.datasource.slave.driver-class-name"));
dataSource.setDefaultAutoCommit(false);
dataSource.setUsername(props.getProperty("spring.datasource.slave.username"));
dataSource.setPassword(props.getProperty("spring.datasource.slave.password"));
dataSource.setUrl(props.getProperty("spring.datasource.slave.url"));
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(masterDataSource());
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(Boolean.parseBoolean(props.getProperty("spring.jpa.generate-ddl")));
Map<String, String> additionalProperties = Maps.newHashMap();
additionalProperties.put("hibernate.dialect", props.getProperty("spring.jpa.dialect"));
LocalContainerEntityManagerFactoryBean factory = builder.dataSource(routingDataSource())
.packages("ru.project.master.slave.orm").persistenceUnit("routingEMF").properties(additionalProperties)
.build();
factory.setJpaVendorAdapter(vendorAdapter);
factory.afterPropertiesSet();
return factory;
}
}
My solution:
But somehow If feels missing because I think I need to implement as have been mentioned here
https://stackoverflow.com/a/75223437/19751568
I need some configuration to provide some hibernate functionality.
Not only that the hibernate.connection.provider_disables_autocommit allows you to make better use of database connections, but it’s the only way we can make this example work since, without this configuration, the connection is acquired prior to calling the determineCurrentLookupKey method TransactionRoutingDataSource.
#Configuration
#ComponentScan
#("io.api.persistence.repository")
#EnableTransactionManagement
public class DataSourcesConfiguration {
#Value("${spring.datasource.master.url}")
private String mstUrl;
#Value("${spring.datasource.master.username}")
private String mstUsername;
#Value("${spring.datasource.master.password}")
private String mstPassword;
#Value("${spring.datasource.slave.url}")
private String slaveUrl;
#Value("${spring.datasource.slave.username}")
private String slaveUsername;
#Value("${spring.datasource.slave.password}")
private String slavePassword;
#Bean
public DataSource dataSource() {
RoutingDataSource masterSlaveRoutingDataSource = new RoutingDataSource();
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource());
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource());
masterSlaveRoutingDataSource.setTargetDataSources(targetDataSources);
// Set as all transaction point to master
masterSlaveRoutingDataSource.setDefaultTargetDataSource(masterDataSource());
return masterSlaveRoutingDataSource;
}
public DataSource slaveDataSource() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(slaveUrl);
hikariDataSource.setUsername(slaveUsername);
hikariDataSource.setPassword(slavePassword);
return hikariDataSource;
}
#Primary
public DataSource masterDataSource() {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(mstUrl);
hikariDataSource.setUsername(mstUsername);
hikariDataSource.setPassword(mstPassword);
return hikariDataSource;
}
Related
I am getting the error below when I try configure multiple datasources in Spring boot:
Property: xyz.integration.multidatasources.connections.datasource1.username
Value: safuser
Origin: class path resource [application.properties]:14:67
Reason: org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType
When I try to load multiple datasource configurations. The properties are as below:
xyz.integration.multidatasources.connections.datasource1.url=jdbc:postgresql://localhost:5432/safdb
xyz.integration.multidatasources.connections.datasource1.driver-class-name=org.postgresql.Driver
xyz.integration.multidatasources.connections.datasource1.username=auser
xyz.integration.multidatasources.connections.datasource1.password=apassword
The java class is as below:
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "xyz.integration.multidatasources")
public class MultipleDatasourcesProperties {
private Map<String, DataSourceProperties> connections = new HashMap<>();
public Map<String, DataSourceProperties> getConnections() {
return connections;
}
public void setConnections(Map<String, DataSourceProperties> connections) {
this.connections = connections;
}
}
Am I missing anything in my configuration?
Can you try this ?
application.properties
primary.url={primary-database-url}
primary.username={primary-database-username}
primary.password={primary-database-password}
primary.driver-class-name=com.mysql.jdbc.Driver
primary.test-on-borrow=true
primary.validation-query=SELECT 1
secondary.url={secondary-database-url}
secondary.username={secondary-database-username}
secondary.password={secondary-database-password}
secondary.driver-class-name=com.mysql.jdbc.Driver
secondary.test-on-borrow=true
secondary.validation-query=SELECT 1
secondary.validation-interval=25200000
Create the configuration beans
#Bean(name = "primaryDataSource")
#ConfigurationProperties("primary")
#Primary
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean(name = "secondaryDataSource")
#ConfigurationProperties("secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
Next, create the JdbcTemplate beans that we are going to use for accessing the data sources in our data access layer.
#Bean(name = "primaryJdbcTemplate")
public JdbcTemplate primaryJdbcTemplate(#Qualifier("primary") DataSource primaryDs) {
return new JdbcTemplate(writeDs);
}
#Bean(name = “secondaryJdbcTemplate")
public JdbcTemplate secondaryJdbcTemplate(#Qualifier("secondary") DataSource secondaryDs) {
return new JdbcTemplate(secondaryDs);
}
Try accessing properties like this:
#Resource(name = "primaryJdbcTemplate")
private JdbcTemplate primaryJdbcTemplate;
#Resource(name = "secondaryJdbcTemplate")
private JdbcTemplate secondaryJdbcTemplate;
primaryJdbcTemplate.query(“any-query-to-apply-on-primary-data-source”);
I get this exception with this Configuration class
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Configuration
#EnableTransactionManagement//enable tx management
#EnableWebMvc//enables webmvc
//load properties into spring container
#PropertySource("classpath:app.properties")
//all layered classes common package name
#ComponentScan("in.nit")
public class AppConfig {
#Autowired
private Environment env;
//1) DataSource
#Bean
public DataSource ds() {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName(env.getProperty("db.driver"));
ds.setUrl(env.getProperty("db.url"));
ds.setUsername(env.getProperty("db.user"));
ds.setPassword(env.getProperty("db.pwd"));
return ds;
}
//2)SessionFactory
#Bean
public SessionFactory sf() {
LocalSessionFactoryBean sf = new LocalSessionFactoryBean();
sf.setDataSource(ds());
sf.setHibernateProperties(props());
sf.setPackagesToScan("in.nit.model");
return sf.getObject();
}
//3)HibernateProperties
public Properties props() {
Properties p = new Properties();
p.put("hibernate.dialect", env.getProperty("orm.dialect"));
p.put("hibernate.show_sql", env.getProperty("orm.showsql"));
p.put("hibernate.format_sql",env.getProperty("orm.fmtsql"));
p.put("hibernate.hbm2ddl.auto", env.getProperty("orm.hbm2ddl.auto"));
return p;
}
//4)HibernateTemplate
#Bean
public HibernateTemplate ht() {
HibernateTemplate ht = new HibernateTemplate();
ht.setSessionFactory(sf());
return ht;
}
//5)HibernateTx Manager
#Bean
public HibernateTransactionManager htx() {
HibernateTransactionManager htx = new HibernateTransactionManager();
htx.setSessionFactory(sf());
return htx;
}
//6)ViewResolver
#Bean
public InternalResourceViewResolver ivr() {
InternalResourceViewResolver ivr = new InternalResourceViewResolver();
ivr.setPrefix(env.getProperty("mvc.prefix"));
ivr.setSuffix(env.getProperty("mvc.suffix"));
return ivr;
}
}
and I get the exception
Error creating bean with name 'ht' defined in in.nit.config.AppConfig: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sessionFactory' is required
obviously it has something to do with session factory
But when i replace the config with the code below it works fine
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate5.HibernateTemplate;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Configuration
#EnableTransactionManagement //enables HtX
#EnableWebMvc // Spring MVC Activated
//load properties into Spring container
#PropertySource("classpath:app.properties")
//all layered classes common package name
#ComponentScan("in.nit")
public class AppConfig2 {
#Autowired
private Environment env;
//1. DataSource
#Bean
public DataSource ds() {
BasicDataSource d=new BasicDataSource();
d.setDriverClassName(env.getProperty("db.driver"));
d.setUrl(env.getProperty("db.url"));
d.setUsername(env.getProperty("db.user"));
d.setPassword(env.getProperty("db.pwd"));
return d;
}
//2. SessionFactrory
#Bean
public LocalSessionFactoryBean sf() {
LocalSessionFactoryBean s=new LocalSessionFactoryBean();
s.setDataSource(ds());
s.setHibernateProperties(props());
//s.setAnnotatedClasses(ShipmentType.class);
s.setPackagesToScan("in.nit.model");
return s;
}
#Bean
public Properties props() {
Properties p=new Properties();
p.put("hibernate.dialect", env.getProperty("orm.dialect"));
p.put("hibernate.show_sql", env.getProperty("orm.showsql"));
p.put("hibernate.format_sql", env.getProperty("orm.fmtsql"));
p.put("hibernate.hbm2ddl.auto", env.getProperty("orm.hbm2ddl.auto"));
return p;
}
//3. HT
#Bean
public HibernateTemplate ht() {
HibernateTemplate h=new HibernateTemplate();
h.setSessionFactory(sf().getObject());
return h;
}
//4. HtxM
#Bean
public HibernateTransactionManager htx() {
HibernateTransactionManager htm=new HibernateTransactionManager();
htm.setSessionFactory(sf().getObject());
return htm;
}
//5. ViewResovler
#Bean
public InternalResourceViewResolver ivr() {
InternalResourceViewResolver v=new InternalResourceViewResolver();
v.setPrefix(env.getProperty("mvc.prefix"));
v.setSuffix(env.getProperty("mvc.suffix"));
return v;
}
}
Can anyone explain why is the exception is being thrown in the first case ?
And both classes AppConfig and AppCOnfig2 seem to be doing the same thing.
Can anyone point what out specifically where and which lines make a difference?
Definition : LocalSessionFactoryBean is the FactoryBean that creates a Hibernate SessionFactory. It provides the SessionFactory object and itself is not a SessionFactory. In case you are getting exception, you are not passing the sessionfactory object and instead passing factoryBean which causes illegalArgumentException.
Original documentation: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/orm/hibernate5/LocalSessionFactoryBean.html
I am trying to set up a second data source in my Spring application.
Below are the 2 configuration classes for the 2 data sources:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.XYXYale.persistence.XY",
entityManagerFactoryRef = "awEntityManagerFactory",
transactionManagerRef= "awTransactionManager"
)
public class Datasource1DataSourceConfig {
#Value("${spring.first-datasource.url}")
private String url;
#Value("${spring.first-datasource.username}")
private String username;
#Value("${spring.first-datasource.password}")
private String pw;
#Value("${spring.first-datasource.driver-class-name}")
private String driver;
#Bean
#Primary
#ConfigurationProperties("spring.first-datasource")
public DataSourceProperties awDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#Qualifier("awEntityManagerFactory")
public DataSource awDataSource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(pw);
return dataSource;
}
#Primary
#Bean(name = "awEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean awEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(awDataSource());
em.setPersistenceUnitName("XY");
");
em.setPackagesToScan(new String[] { "com.XY.XY.domain.XY" });
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
#Primary
#Bean
public PlatformTransactionManager awTransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
awEntityManagerFactory().getObject());
return transactionManager;
}
}
The second Config class:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = "com.XYXYale.persistence.YX",
entityManagerFactoryRef = "sndEntityManagerFactory",
transactionManagerRef= "sndTransactionManager"
)
public class SndDataSourceConfig {
#Value("${spring.second-datasource.jdbcUrl}")
private String url;
#Value("${spring.second-datasource.username}")
private String username;
#Value("${spring.second-datasource.password}")
private String pw;
#Value("${spring.second-datasource.driver-class-name}")
private String driver;
#Bean
#ConfigurationProperties("spring.second-datasource")
public DataSourceProperties sndDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Qualifier("sndEntityManagerFactory")
public DataSource sndDataSource() {
DriverManagerDataSource dataSource
= new DriverManagerDataSource();
dataSource.setDriverClassName(
driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(pw);
return dataSource;
}
#Bean(name = "sndEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean sndEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(sndDataSource());
em.setPersistenceUnitName("snd");
em.setPackagesToScan(new String[] { "com.XY.XY.domain.YX" });
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabasePlatform("org.hibernate.dialect.MySQLDialect");
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
#Bean
public PlatformTransactionManager sndTransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
sndEntityManagerFactory().getObject());
return transactionManager;
}
}
I have in com.XYXYale.persistence.XY a Spring data JPA Repo defined as follows
DemoRepo
#Repository
public interface DemoRepo extends CrudRepository<Demo, String>, DemoRepoCustom{
}
DemoRepoCustom
#NoRepositoryBean
public interface DemoRepoCustom {
Demo returnDemoContent();
}
DemoRepoImpl
public class DemoRepoImpl extends QuerydslRepositorySupport implements DemoRepoCustom {
public DemoRepoImpl() {
super(Demo.class);
}
public Demo returnDemoContent(){
return something;
}
}
The Repo is used in the following way
#Autowired
DemoRepo demoRepo;
I am getting this exception:
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'demoRepositoryImpl': Unsatisfied dependency expressed through method 'setEntityManager' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:678)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:376)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1411)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:592)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:515)
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:307)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:277)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1255)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:595)
... 55 more
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'javax.persistence.EntityManager' available: expected single matching bean but found 2: org.springframework.orm.jpa.SharedEntityManagerCreator#0,org.springframework.orm.jpa.SharedEntityManagerCreator#1
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:221)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1233)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1175)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:670)
... 70 more
Anyone having a suggestion on how to solve that? I could think of injecting the correct entitymanager to each repo, however I have no idea how to do that.
Thanks in advance. Cant find any solution on here or other sites.
if you will need to connect to more than one data source.
and assuming that you have already your spring app, then use this code 👇:
in your application.properties add this code:
# DEFAULT CONFIG
spring.main.banner-mode=off
server.port=2020
# CUSTOM PREFIX FOR SQL SERVER
sqlserver.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
sqlserver.datasource.jdbcUrl=jdbc:sqlserver://192.168.9.44;databaseName=myAwesomeDB
sqlserver.datasource.username=someUser
sqlserver.datasource.password=somePass
# DEFAULT PREFIX FOR POSTGRES
spring.datasource.platform=postgres
spring.datasource.url=jdbc:postgresql://192.168.6.125:5432/wilmer
spring.datasource.username=admWinter
spring.datasource.password=wilmer43nlp
now create a class called DataSourceDBConfig.java and add this code:
package com.example.demo.config;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;
/**
* #author Wilmer Villca
* #version 0.0.1
*/
#Configuration
public class DataSourceDBConfig {
private final Environment env;
#Autowired
public DataSourceDBConfig(Environment env) {
this.env = env;
}
#Bean
#Primary
#ConfigurationProperties(prefix = "sqlserver.datasource")
public DataSource dataSourceFromSqlServer() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix = "spring.datasource")
public HikariDataSource dataSourceFromPostgres() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName("org.postgresql.Driver");
hikariConfig.setJdbcUrl(env.getProperty("spring.datasource.url"));
hikariConfig.setUsername(env.getProperty("spring.datasource.username"));
hikariConfig.setPassword(env.getProperty("spring.datasource.password"));
return new HikariDataSource(hikariConfig);
}
#Bean
#Qualifier("jdbcTemplateSqlServer")
public JdbcTemplate jdbcTemplateSqlServer(#Qualifier("dataSourceFromSqlServer") DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean
#Qualifier("jdbcTemplatePostgres")
public JdbcTemplate jdbcTemplatePostgres(#Qualifier("dataSourceFromPostgres") DataSource ds) {
return new JdbcTemplate(ds);
}
}
that is all you need to to access multiple databases 😉
NOTE: Do not forget and
remember that this only is an alternative, exist anothers many ways 🎉
i hope you understand, 👀
I will show you a little code and then I will ask a question.
SendEmail.java
package com.goode;
import java.util.Properties;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
#Component
#NoArgsConstructor
#Data
#AllArgsConstructor
public class SendEmail {
#Value("${email.username}")
private String username;
#Value("${email.password}")
private String password;
#Value("${email.fullAddress}")
private String fullAddress;
#Value("${email.host}")
private String host;
#Value("${email.port}")
private String port;
public boolean send(String toEmail, String subject, String message){
Properties props = new Properties();
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.smtp.host", host);
props.put("mail.smtp.port", port);
Session session = Session.getInstance(props,
new javax.mail.Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
try {
Message createMessage = new MimeMessage(session);
createMessage.setFrom(new InternetAddress(fullAddress));
createMessage.setRecipients(Message.RecipientType.TO,
InternetAddress.parse(toEmail));
createMessage.setSubject(subject);
createMessage.setText(message);
Transport.send(createMessage);
return true;
} catch (MessagingException e) {
throw new RuntimeException(e);
}
}
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer()
{
PropertySourcesPlaceholderConfigurer o = new PropertySourcesPlaceholderConfigurer();
o.setLocation(new ClassPathResource("application.properties"));
return o;
}
}
RootConfig.java
package com.goode.config;
import java.util.Properties;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#EnableJpaRepositories( basePackages = {"com.goode.repository"})
#PropertySource(value = { "classpath:application.properties" })
#EnableTransactionManagement
#Import({ SecurityConfig.class })
#ComponentScan(basePackages = {"com.goode.service", "com.goode.repository", "com.goode.controller", "com.goode.business", "com.goode"})
public class RootConfig {
#Autowired
private Environment environment;
#Autowired
private DataSource dataSource;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getRequiredProperty("jdbc.url"));
dataSource.setUsername(environment.getRequiredProperty("jdbc.username"));
dataSource.setPassword(environment.getRequiredProperty("jdbc.password"));
return dataSource;
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.POSTGRESQL);
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.goode.business");
factory.setDataSource(dataSource());
factory.setJpaProperties(jpaProperties());
return factory;
}
private Properties jpaProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory().getObject());
return txManager;
}
}
application.properties
jdbc.driverClassName = org.postgresql.Driver
jdbc.url = jdbc:postgresql://localhost:5432/GoodE
jdbc.username = postgres
jdbc.password = postgres
hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
hibernate.show_sql = true
hibernate.format_sql = true
email.username = xx //I replce real data to xx just to this post
email.password = xx
email.fullAddress = xx#xx.com
email.host = xx
email.port = xx
When I try call a send method from SendEmail console shows error:
java.lang.NullPointerException: null
in line:
props.put("mail.smtp.host", host);
So annotation #Value doesn't inject any value from application.properties to private variable in SendEmail. Why is that?
In RootConfig.java I used #Autowired, Environment and #PropertySource but I read somewhere that you can use #PropertySource only in #Configuration class so I tried find another way -> I found #Value, but I have no idea why data from application.properties are not injecting to variable.
I put PropertySourcesPlaceholderConfigurer in SendEmail because I read it's necessary. I am not sure this is the right place for it but placing this in another class e.g. RootConfig didn't help.
May you have any advice where should I search for bug?
This line:
#PropertySource(value = { "classpath:application.properties" })
is unnecessary because all values from application.properties are picked up by default. That being said, you also don't need PropertySourcesPlaceholderConfigurer for that.
Make sure that when calling your send method, that you are not instantiating your SendEmail class as new SendEmail() because that will not work as #Value works only inside of the spring application context.
You must either do an #Autowired or constructor injection (the latter being recommended).
Also this:
#ComponentScan(basePackages = {"com.goode.service", "com.goode.repository", "com.goode.controller", "com.goode.business", "com.goode"})
Can be replaced by this:
#ComponentScan(basePackages = {"com.goode.*"})
I'm developing an application in spring boot that needs to connect to two different databases. To do that this I had to manually configure the connections.
Initially everything seemed to work fine but later I realized I had problems with the transactions.
In fact, although I used the transactional annotation on the methods, I realized that every record i was writing on the DB was immediately committed and that if an exception was thrown the rollback did not happen.
It seems as if the transactions were not active.
This is my actually configuration for the first DB.
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
#Configuration
#EnableConfigurationProperties
#EnableJpaRepositories(
entityManagerFactoryRef = "fooEntityManager",
basePackages = "xx.yy.zz.repository"
)
#EnableTransactionManagement
public class FooConfig {
#Autowired
private Environment env;
#Value("${spring.datasource.primary.jndi-name}")
private String jndiConnection;
#Primary
#Bean(destroyMethod = "")
public DataSource ds() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiConnection);
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean fooEntityManager(DataSource ds) {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(ds());
em.setPackagesToScan(
new String[] { "xx.yy.zz.entity" });
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.dialect",
env.getProperty("spring.jpa.properties.hibernate.dialect"));
properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
properties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean(name="transactionManager")
public PlatformTransactionManager fooTransactionManager() {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
fooEntityManager(ds()).getObject());
return transactionManager;a
}
}
I also tried to use JTA but on the contrary this time the application has never written on the DB.
This is the jta configuration.
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy;
import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
#Configuration
#EnableConfigurationProperties
#EnableJpaRepositories(
basePackages = "xx.yy.zzzz.repository",
entityManagerFactoryRef = "fooEntityManager"
)
#EnableTransactionManagement
public class FooConfig {
#Value("${spring.datasource.primary.jndi-name}")
private String jndiConnection;
#Autowired
private Environment env;
#Primary
#Bean(destroyMethod = "")
public DataSource ds() {
JndiDataSourceLookup lookup = new JndiDataSourceLookup();
return lookup.getDataSource(jndiConnection);
}
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean fooEntityManager(DataSource ds) {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setJtaDataSource(ds());
em.setPackagesToScan(
new String[] { "xx.yy.zz.entity" });
HibernateJpaVendorAdapter vendorAdapter
= new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "none");
properties.put("hibernate.dialect",
env.getProperty("spring.jpa.properties.hibernate.dialect"));
properties.put("hibernate.transaction.jta.platform",
env.getProperty("org.hibernate.service.jta.platform.internal.JBossAppServerJtaPlatform"));
properties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName());
properties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
em.setJpaPropertyMap(properties);
return em;
}
#Primary
#Bean(name="transactionManager")
public PlatformTransactionManager fooTransactionManager() {
JtaTransactionManager transactionManager = new JtaTransactionManager();
return transactionManager;
}
}
That's all the connection config.
edit1:
I tried to use this configuration in an application with only one connection to one DB. It seems to work fine. I wrote a transactional method that insert 10 element in the DB and if i throw an error in this method, nothing is inserted in the DB.