I have a Springboot project organized in different modules.
This is the main pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.cmw</groupId>
<artifactId>bo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<name>bo</name>
<url>https://www.test.com</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<java.version>11</java.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
<org.lombok.version>1.18.18</org.lombok.version>
</properties>
<modules>
<module>bo_libs</module>
<module>bo_basis_lib</module>
<module>bo_basis</module>
<module>bo_api_lib</module>
<module>bo_api</module>
<module>bo_ui</module>
</modules>
<dependencies>
...
</dependencies>
</project>
I'm trying to have each module connect to a different database.
For the bo_basis module I have created this configuration class
package com.cmw.bo.basis.config;
import com.cmw.bo.basis.repositories.SampleDataPopulator;
import com.cmw.bo.common.config.DataAccessConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.hibernate.boot.SchemaAutoTooling;
import org.hibernate.dialect.MySQL8Dialect;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#Configuration
#Profile("development")
#EnableJpaAuditing
#EnableJpaRepositories(basePackages = {
"com.cmw.bo.basis.repositories"
})
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
public class DevelopmentDataAccessConfig extends DataAccessConfig {
#Value(value = "${jdbc.url}")
private String jdbcUrl;
#Bean
public DataSource dataSource() {
final HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(String.format("%s/bo_basis", jdbcUrl));
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setMaximumPoolSize(10);
dataSource.addDataSourceProperty("cachePrepStmts", true);
dataSource.addDataSourceProperty("prepStmtCacheSize", 250);
dataSource.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
dataSource.addDataSourceProperty("useServerPrepStmts", true);
dataSource.addDataSourceProperty("useLocalSessionState", true);
dataSource.addDataSourceProperty("useLocalTransactionState", true);
dataSource.addDataSourceProperty("rewriteBatchedStatements", true);
dataSource.addDataSourceProperty("cacheResultSetMetadata", true);
dataSource.addDataSourceProperty("cacheServerConfiguration", true);
dataSource.addDataSourceProperty("elideSetAutoCommits", true);
dataSource.addDataSourceProperty("maintainTimeStats", false);
dataSource.addDataSourceProperty("useSSL", false);
dataSource.addDataSourceProperty("characterEncoding", "utf8");
dataSource.addDataSourceProperty("useUnicode", "true");
dataSource.addDataSourceProperty("serverTimezone", "UTC");
return dataSource;
}
#Bean
public SampleDataPopulator sampleDataPopulator() {
return new SampleDataPopulator();
}
#Override
protected boolean getShowSql() {
return false;
}
#Override
protected SchemaAutoTooling getSchemaAutoTooling() {
return SchemaAutoTooling.VALIDATE;
}
#Override
protected String[] getPackagesToScan() {
return new String[]{
"com.cmw.bo.basis.entities",
"com.cmw.bo.common.models.entities"
};
}
#Override
protected String getDatabasePlatform() {
return MySQL8Dialect.class.getCanonicalName();
}
#Override
protected String getName() {
return "basisJpaManager";
}
}
The DataAccessConfig class:
#Configuration
public abstract class DataAccessConfig {
#Bean
#DependsOn({"flyway"})
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(false);
vendorAdapter.setShowSql(getShowSql());
vendorAdapter.setDatabasePlatform(getDatabasePlatform());
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setValidationMode(ValidationMode.CALLBACK);
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan(getPackagesToScan());
factory.setDataSource(dataSource());
Map<String, Object> properties = new HashMap<>();
properties.put(AvailableSettings.HBM2DDL_AUTO, getSchemaAutoTooling().name().toLowerCase());
// To avoid: Entity manager factory name (default) is already registered.
// If entity manager will be clustered or passivated, specify a unique value for property 'hibernate.ejb.entitymanager_factory_name'
properties.put("hibernate.ejb.entitymanager_factory_name", getName());
factory.setJpaPropertyMap(properties);
factory.afterPropertiesSet();
return factory.getObject();
}
#Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory());
return txManager;
}
#Bean
#Description("Database migration")
public Flyway flyway() {
Flyway flyway = Flyway.configure()
.dataSource(dataSource())
.validateOnMigrate(true)
.baselineOnMigrate(true)
.outOfOrder(false).load();
flyway.migrate();
return flyway;
}
protected abstract boolean getShowSql();
protected abstract SchemaAutoTooling getSchemaAutoTooling();
protected abstract String[] getPackagesToScan();
protected abstract String getDatabasePlatform();
protected abstract String getName();
public abstract DataSource dataSource();
}
For the bo_ui module the configuration class is the same (apart from the jdbc connection url of course)
The problem is that (obviously) if I keep the configuration classes the same name I get a ConflictingBeanDefinitionException. If I rename the classes differently, one of the two connections is ignored.
How would you solve this problem that was not taken into consideration in the design phase?
Thank you
You can apply as a way to dynamically determine the actual DataSource based on the current context.
I think is similar to what you ask: Spring's AbstractRoutingDatasource
good coding! ¯_(ツ)_/¯
Related
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;
}
I'm using the Jaxb2Marshaller from org.springframework.oxm.jaxb.Jaxb2Marshaller in my Spring Batch application to marshall XML with annotated classes. The implementation of the Marshaller is:
#Bean
public Jaxb2Marshaller productMarshaller() {
Map<String, Object> props = new HashMap<String, Object>();
props.put("com.sun.xml.bind.marshaller.CharacterEscapeHandler", new XmlCharacterEscapeHandler());
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(new Class[] {Product.class, TechSpecs.class});
marshaller.setMarshallerProperties(props);
return marshaller;
}
The Marshaller is used inside a StaxEventItemWriter that is implemented as following:
#Bean(name = "writer")
#StepScope
public StaxEventItemWriter<Product> writer (
#Value("#{jobParameters['path']}") String path,
#Value("#{stepExecutionContext['currentFile']}") String fileName
) {
Map<String, String> rootElementAttributes = new HashMap<String, String>();
rootElementAttributes.put("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
FileSystemResource file = new FileSystemResource(path + fileName);
return new StaxEventItemWriterBuilder<Product>()
.name("writer")
.version("1.0")
.encoding("UTF-8")
.standalone(false)
.rootTagName("Products")
.rootElementAttributes(rootElementAttributes)
.headerCallback(headerCallback(null, null))
.footerCallback(footerCallback())
.marshaller(productMarshaller())
.resource(file)
.build();
}
Now the problem is that when I run the code, I get an IndexOutOfBoundsException. I found out that the exception is thrown because my Product object has a String attribute that may contain a &. The & is not allowed in XML and has to be escaped.
Why is the Jaxb2Marshaller not auto escaping the & character? As far as I understand the Marshaller should take care of escaping characters.
I tried to escape the character my self in the item processor with the StringEscapeUtils, e.g. product.setFullName(StringEscapeUtils.escapeXml10(dbExport.getFullName()));, but this didn't help. Also the String will be changed from & to &, which also contains a &.
I also tried to use my own implementation of a CharacterEscapeHandler, but the marshaller.setMarshallerProperties() does not have any visible effect on the Marshaller. Do I have to set the properties for the Marshaller differently?
public class XmlCharacterEscapeHandler implements CharacterEscapeHandler {
#Override
public void escape(char[] ch, int start, int length, boolean isAttVal, Writer out) throws IOException {
StringWriter buffer = new StringWriter();
for(int i = start; i < start + length; i++) {
buffer.write(ch[i]);
}
String escapedString = StringEscapeUtils.escapeXml10(buffer.toString());
out.write(escapedString);
}
}
EDIT
Unfortunately I could not resolve my issue until now. Therefore, I switched from Jaxb2Marshaller to XStreamMarshaller. Here I get a similar issue. As far as I can tell the underlying XStream should use a PrettyPrintWriter that will auto convert & to & as described here: https://stackoverflow.com/a/48141964/4191735 This is not happening. For me there is always an problem with &. Why does the escaping not work? Also escaping the String itself and force converting it to UTF-8 does not help.
Minimal Complete Example
Main:
package com.mwe;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan("com.mwe")
public class Main {
public static void main(String [] args) {
System.exit(SpringApplication.exit(SpringApplication.run(Main.class, args)));
}
}
BatchConfig:
package com.mwe;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.file.FlatFileItemReader;
import org.springframework.batch.item.file.mapping.DefaultLineMapper;
import org.springframework.batch.item.file.transform.DelimitedLineTokenizer;
import org.springframework.batch.item.xml.StaxEventItemWriter;
import org.springframework.batch.item.xml.builder.StaxEventItemWriterBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.FileSystemResource;
import org.springframework.oxm.xstream.XStreamMarshaller;
#Configuration
#EnableBatchProcessing
public class BatchConfig {
#Autowired
public JobBuilderFactory jobBuilderFactory;
#Autowired
public StepBuilderFactory stepBuilderFactory;
#Bean
#StepScope
public FlatFileItemReader<Product> reader() {
FlatFileItemReader<Product> reader = new FlatFileItemReader<Product>();
reader.setResource(new FileSystemResource("test.csv"));
DefaultLineMapper<Product> lineMapper = new DefaultLineMapper<>();
lineMapper.setFieldSetMapper(new CustomFieldMapper());
DelimitedLineTokenizer tokenizer = new DelimitedLineTokenizer();
tokenizer.setDelimiter("|");
tokenizer.setNames(new String[] {"ID", "NAME"});
lineMapper.setLineTokenizer(tokenizer);
reader.setLineMapper(lineMapper);
reader.setLinesToSkip(1);
return reader;
}
#Bean
public ItemProcessor<Product, Xml> processor() {
return new Processor();
}
#Bean
#StepScope
public StaxEventItemWriter<Xml> writer () {
return new StaxEventItemWriterBuilder<Xml>()
.name("writer")
.version("1.0")
.encoding("UTF-8")
.standalone(false)
.rootTagName("products")
.marshaller(getMarshaller())
.resource(new FileSystemResource("test.xml"))
.build();
}
#Bean
public Job job() {
return this.jobBuilderFactory.get("job")
.start(step1())
.build();
}
#Bean
public Step step1() {
return (stepBuilderFactory.get("step1")
.<Product, Xml>chunk(2)
.reader(reader())
.processor(processor())
.writer(writer())
.build());
}
#Bean
public XStreamMarshaller getMarshaller() {
XStreamMarshaller marshaller = new XStreamMarshaller();
marshaller.setEncoding("UTF-8");
return marshaller;
}
}
CustomFieldMapper
package com.mwe;
import org.springframework.batch.item.file.mapping.FieldSetMapper;
import org.springframework.batch.item.file.transform.FieldSet;
public class CustomFieldMapper implements FieldSetMapper<Product> {
public Product mapFieldSet(FieldSet fs) {
Product product = new Product();
product.setId(fs.readString("ID"));
product.setName(fs.readString("NAME"));
return product;
}
}
ItemProcessor:
package com.mwe;
import org.springframework.batch.item.ItemProcessor;
public class Processor implements ItemProcessor<Product, Xml> {
#Override
public Xml process(final Product product) {
Xml xml = new Xml();
xml.setId(Integer.parseInt(product.getId()));
xml.setName(product.getName());
return xml;
}
}
Product:
package com.mwe;
public class Product {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Xml:
package com.mwe;
public class Xml {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Application Properties:
# Spring config
spring.main.allow-bean-definition-overriding=true
spring.main.banner-mode=off
spring.batch.initialize-schema=never
# Logging data source
spring.datasource.logging.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.logging.maximum-pool-size=10
spring.datasource.logging.hikar.minimum-idle=1
spring.datasource.logging.hikari.data-source-properties.useUnicode=true
spring.datasource.logging.hikari.data-source-properties.characterEncoding=UTF-8
spring.datasource.logging.hibernate.dialect=org.hibernate.dialect.MariaDBDialect
spring.datasource.logging.hibernate.ddl-auto=none
spring.datasource.url=jdbc:mariadb://localhost:3306/logging?UseUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
Pom:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.mwe</groupId>
<artifactId>mwe</artifactId>
<version>1</version>
<name>mwe</name>
<description>Minimal working example</description>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-batch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.15</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
test.csv:
"ID"|"NAME"
1|"Product 1"
2|"Product 1 & Addition"
You are hitting this issue: https://github.com/spring-projects/spring-batch/issues/3745.
This issue will be fixed in v4.3.2/v4.2.6 which are planned to be released on March 18, 2021. Please check the milestone page on GitHub in case the release date changes.
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 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 am new to spring, hibernate, c3p0 & ehcache. I am developing an application completely using Java Configuration except web.xml. I have to use second level cache in my app. So I added following code
import net.sf.ehcache.config.CacheConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
#EnableCaching
public class CachingConfig implements CachingConfigurer{
#Bean(destroyMethod="shutdown")
public net.sf.ehcache.CacheManager ehCacheManager() {
CacheConfiguration cacheConfiguration = new CacheConfiguration();
cacheConfiguration.setName("myCacheName");
cacheConfiguration.setMemoryStoreEvictionPolicy("LRU");
cacheConfiguration.setMaxEntriesLocalHeap(1000);
net.sf.ehcache.config.Configuration config = new net.sf.ehcache.config.Configuration();
config.addCache(cacheConfiguration);
return net.sf.ehcache.CacheManager.newInstance(config);
}
#Bean
#Override
public CacheManager cacheManager() {
return new EhCacheCacheManager(ehCacheManager());
}
#Override
public CacheResolver cacheResolver() {
return null;
}
#Override
public CacheErrorHandler errorHandler() {
return null;
}
#Override
public KeyGenerator keyGenerator() {
return new SimpleKeyGenerator();
}
}
In web.xml I added it as
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
ApplicationContextConfiguration
CachingConfig
</param-value>
</context-param>
My ApplicationContextConfiguration is
import java.beans.PropertyVetoException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.mchange.v2.c3p0.ComboPooledDataSource;
#Configuration
#EnableTransactionManagement
public class ApplicationContextConfiguration
{
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() throws IOException {
LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
entityManagerFactory.setDataSource(pooledDataSource());
entityManagerFactory.setPackagesToScan(new String[] { "*" });
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
entityManagerFactory.setJpaVendorAdapter(vendorAdapter);
entityManagerFactory.setJpaProperties(additionalProperties());
return entityManagerFactory;
}
#Bean
public ComboPooledDataSource pooledDataSource() throws IOException{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
Properties properties = new Properties();
BufferedReader input = new BufferedReader (new InputStreamReader (getClass().getResourceAsStream("/config/hibernate.properties")));
properties.load(input);
try {
dataSource.setDriverClass(properties.getProperty("hibernate.connection.driver_class"));
} catch (PropertyVetoException e) {
e.printStackTrace();
}
dataSource.setJdbcUrl(properties.getProperty("hibernate.connection.url"));
dataSource.setUser(properties.getProperty("hibernate.connection.username"));
dataSource.setPassword(properties.getProperty("hibernate.connection.password"));
dataSource.setMaxPoolSize(Integer.parseInt(properties.getProperty("hibernate.c3p0.max_size")));
dataSource.setMinPoolSize(Integer.parseInt(properties.getProperty("hibernate.c3p0.min_size")));
dataSource.setCheckoutTimeout(Integer.parseInt(properties.getProperty("hibernate.c3p0.timeout")));
dataSource.setMaxStatements(Integer.parseInt(properties.getProperty("hibernate.c3p0.max_statements")));
dataSource.setIdleConnectionTestPeriod(Integer.parseInt(properties.getProperty("hibernate.c3p0.idle_test_period")));
dataSource.setAcquireIncrement(Integer.parseInt(properties.getProperty("hibernate.c3p0.acquire_increment")));
return dataSource;
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() throws IOException {
Properties properties = new Properties();
Properties hibernateProperties = new Properties();
BufferedReader input = new BufferedReader (new InputStreamReader (getClass().getResourceAsStream("/config/hibernate.properties")));
hibernateProperties.load(input);
properties.setProperty("hibernate.dialect", hibernateProperties.getProperty("hibernate.dialect"));
return properties;
}
}
When I try to run I am getting following error
org.hibernate.cache.NoCacheRegionFactoryAvailableException: Second-level cache is used in the application, but property hibernate.cache.region.factory_class is not given, please either disable second level cache or set correct region factory class name to property hibernate.cache.region.factory_class (and make sure the second level cache provider, hibernate-infinispan, for example, is available in the classpath).
I understood that I have to add
<property name="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</property>
but not sure where and how to add it via Java configuration. Can someone help here please.
In addition, do I need to add the following?
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<property name="hibernate.cache.use_second_level_cache">true</property>
<property name="hibernate.cache.use_query_cache">true</property>
Assuming that you are able to retrieve the entities and you just want to enable second level cache:
Add following properties in your existing additionalProperties() method.
properties.setProperty("hibernate.cache.region.factory_class", "org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory");
properties.setProperty("hibernate.cache.use_second_level_cache", "true");
properties.setProperty("hibernate.cache.use_query_cache", "true");
and on the entity you want to enable second level cache place #Cache annotation on that entity ex: #Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
You can remove CachingConfig class as that has nothing to do with enabling second level cache for Hibernate.