I want to manage multiple DataSource using your Application Servers built-in features and access it using JNDI. I am using Spring boot with Spring JPA data.
I am able to configure the application.properties for single datasource:
spring.datasource.jndi-name=jdbc/customers
And my configuration in context.xml file as below:
<Resource name="jdbc/customer" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/customer"/>
Everything works fine.
But when I am unable to configure for two datasource.
I am sure on the configuration in context.xml file:
<Resource name="jdbc/customer" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/customer"/>
<Resource name="jdbc/employee" auth="Container" type="javax.sql.DataSource"
maxTotal="100" maxIdle="30" maxWaitMillis="10000"
username="root" password="root" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/employee"/>
I am in doubt about the application.properties file configuration.
I tried the below options with no success:
spring.datasource.jndi-name=jdbc/customers,jdbc/employee
Please let me know any details on Spring boot with JNDI for multiple data source. I was looking for this configuration for days now.
Second Trial As per Spring Boot Documentation
spring.datasource.primary.jndi-name=jdbc/customer
spring.datasource.secondary.jndi-name=jdbc/project
Configuration class.
#Bean
#Primary
#ConfigurationProperties(prefix="datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConfigurationProperties(prefix="datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
The application does not get started. Though the tomcat server is getting started. No errors are printed in the log.
Third Trial: With JndiObjectFactoryBean
I have the below application.properties
spring.datasource.primary.expected-type=javax.sql.DataSource
spring.datasource.primary.jndi-name=jdbc/customer
spring.datasource.primary.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.primary.jpa.show-sql=false
spring.datasource.primary.jpa.hibernate.ddl-auto=validate
spring.datasource.secondary.jndi-name=jdbc/employee
spring.datasource.secondary.expected-type=javax.sql.DataSource
spring.datasource.secondary.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.secondary.jpa.show-sql=false
spring.datasource.secondary.jpa.hibernate.ddl-auto=validate
And the below java configuration:
#Bean(destroyMethod="")
#Primary
#ConfigurationProperties(prefix="spring.datasource.primary")
public FactoryBean primaryDataSource() {
return new JndiObjectFactoryBean();
}
#Bean(destroyMethod="")
#ConfigurationProperties(prefix="spring.datasource.secondary")
public FactoryBean secondaryDataSource() {
return new JndiObjectFactoryBean();
}
But still getting error:
Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'primaryDataSource' defined in class path resource [com/web/initializer/MvcConfig.class]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [jdbc/customer] is not bound in this Context. Unable to find [jdbc].
Related cause: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'secondaryDataSource' defined in class path resource [com/web/initializer/MvcConfig.class]: Invocation of init method failed; nested exception is javax.naming.NameNotFoundException: Name [jdbc/employee] is not bound in this Context. Unable to find [jdbc].
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.onRefresh(EmbeddedWebApplicationContext.java:133)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:474)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:118)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:320)
at org.springframework.boot.context.web.SpringBootServletInitializer.run(SpringBootServletInitializer.java:117)
at org.springframework.boot.context.web.SpringBootServletInitializer.createRootApplicationContext(SpringBootServletInitializer.java:108)
at org.springframework.boot.context.web.SpringBootServletInitializer.onStartup(SpringBootServletInitializer.java:68)
at org.springframework.web.SpringServletContainerInitializer.onStartup(SpringServletContainerInitializer.java:175)
Update:
Trial using the below properties file:
spring.datasource.primary.expected-type=javax.sql.DataSource
spring.datasource.primary.jndi-name=java:comp/env/jdbc/customer
spring.datasource.secondary.jndi-name=java:comp/env/jdbc/employee
spring.datasource.secondary.expected-type=javax.sql.DataSource
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=validate
It creates all the tables in customer schema, but fails trying to find the other tables also.(from the second schema)
This is the solution for your third trial a little bit modified.
Consider this solution (Spring Boot 1.3.2):
application.properties file:
spring.datasource.primary.jndi-name=java:/comp/env/jdbc/SecurityDS
spring.datasource.primary.driver-class-name=org.postgresql.Driver
spring.datasource.secondary.jndi-name=java:/comp/env/jdbc/TmsDS
spring.datasource.secondary.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
spring.jpa.show-sql=false
Configuration:
#Configuration# EnableConfigurationProperties
public class AppConfig {
#Bean# ConfigurationProperties(prefix = "spring.datasource.primary")
public JndiPropertyHolder primary() {
return new JndiPropertyHolder();
}
#Bean# Primary
public DataSource primaryDataSource() {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource(primary().getJndiName());
return dataSource;
}
#Bean# ConfigurationProperties(prefix = "spring.datasource.secondary")
public JndiPropertyHolder secondary() {
return new JndiPropertyHolder();
}
#Bean
public DataSource secondaryDataSource() {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource(secondary().getJndiName());
return dataSource;
}
private static class JndiPropertyHolder {
private String jndiName;
public String getJndiName() {
return jndiName;
}
public void setJndiName(String jndiName) {
this.jndiName = jndiName;
}
}
}
And then you can follow guide http://docs.spring.io/spring-data/jpa/docs/1.3.0.RELEASE/reference/html/jpa.repositories.html to use your datasources with jpa repositories.
You could use a plain JndiObjectFactoryBean for this. Simply replace the DataSourceBuilder with a JndiObjectFactoryBean should do the trick.
Java configuration
#Bean(destroyMethod="")
#Primary
#ConfigurationProperties(prefix="datasource.primary")
public FactoryBean primaryDataSource() {
return new JndiObjectFactoryBean();
}
#Bean(destroyMethod="")
#ConfigurationProperties(prefix="datasource.secondary")
public FactoryBean secondaryDataSource() {
return new JndiObjectFactoryBean();
}
Properties
datasource.primary.jndi-name=jdbc/customer
datasource.primary.expected-type=javax.sql.DataSource
datasource.secondary.jndi-name=jdbc/project
datasource.secondary.expected-type=javax.sql.DataSource
You can set every property of the JndiObjectFactoryBean using the #ConfigurationProperties annotation. (See the expected-type I added, but you could also set cache or lookup-on-startup etc.).
Note: when doing a JNDI lookup set the destroyMethod to an "" else you might get the situation that when the application is shutdown your JNDI resource is getting closed/shutdown as well. This is not something you want in a shared environment.
It works for me and contains less code
#Configuration
public class Config {
#Value("${spring.datasource.primary.jndi-name}")
private String primaryJndiName;
#Value("${spring.datasource.secondary.jndi-name}")
private String secondaryJndiName;
private JndiDataSourceLookup lookup = new JndiDataSourceLookup();
#Primary
#Bean(destroyMethod = "") // destroy method is disabled for Weblogic update app ability
public DataSource primaryDs() {
return lookup.getDataSource(primaryJndiName);
}
#Bean(destroyMethod = "") // destroy method is disabled for Weblogic update app ability
public DataSource secondaryDs() {
return lookup.getDataSource(secondaryJndiName);
}
}
In my case, when i start my application using Spring Boot App, the database configurations are read on application-dev.properties, when I publish on tomcat, using datasources, was necessary add a validation to check if my profile is prod, in this case, i do a jndi lookup
#Bean(name = "dsName")
#ConfigurationProperties("ds.datasource.configuration")
public DataSource dataSource(#Qualifier("dsProperties") DataSourceProperties db1DataSourceProperties)
{
if(Arrays.asList(environment.getActiveProfiles()).contains("prod"))
{
final JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource("java:comp/env/jdbc/DS1");
}
else
{
return db1DataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
}
The concise way I get success and explore more
set up many jndi resources in external tomcat , that you can start/stop in eclipse.Note- double click the tomcat in eclipse and select use workspace metedata , means dont deploy the app to tomcat webapp folder. Add jndi resources in respective eclipse server files( context.xml - ResourceLink, server.xml - Resource , web.xml - resource-ref).
no need to set spring.datasource.* in application.properties. since jndi-contest which is a datasource type( i.e. type="javax.sql.DataSource") is exported to external server.
in SpringBootApplication annotated class , create the datasource beans from all the jndi resources(those setup as per #1) through jndi lookup
#Bean(name = "abcDataSource")
public DataSource getAbcDataSource() {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
DataSource dataSource = dataSourceLookup.getDataSource("java:comp/env/jdbc/abcDataSource");
return dataSource;
}
if spring jdbc used in your project then provide the above datasource to create a jdbcTemplate bean
#Bean(name = "jdbcAbcTemplate")
public JdbcTemplate abcJdbcTemplate(#Lazy #Qualifier("abcDataSource")
DataSource refDS)
{
return new JdbcTemplate(refDS);
}
just autowire a property of DataSource type and get systemout its details to explore more.
While the above answers are good I am going to add one more to illustrate a deadly gotcha if mixing jndi and full data connection configuration. In a typical development environment you may fully qualify the database connection in your local dev environment then use jndi as you push to qa, etc.. Your
application .properties looks like so:
spring.datasource.url=jdbc:sqlserver://server.domain.org:1433;databaseName=dbxx
spring.datasource.username=userxxyyzz
spring.datasource.password=passxxyyzz
spring.datasource.platform=mssql
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
and application-qa.properties like so:
spring.datasource.jndi-name=java:jboss/datasources/dbxx
The problem arises when you have to define your own beans to have multiple datasources. If you use the default Spring managed datasource then it automatically detects jndi vs fully qualified connection and returns a datasource with no change needed in the application code. If you define your own datasource it no longer does this. If you have application.properties like so:
spring.custom.datasource.url=jdbc:sqlserver://server.domain.org:1433;databaseName=dbxx
spring.custom.datasource.username=userxxyyzz
spring.custom.datasource.password=passxxyyzz
spring.custom.datasource.platform=mssql
spring.custom.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
and application-qa.properties like so:
spring.custom.datasource.jndi-name=java:jboss/datasources/dbxx
with a datasource bean like so, as suggested in Spring docs https://docs.spring.io/spring-boot/docs/2.1.11.RELEASE/reference/html/howto-data-access.html
#Primary
#Bean(name="customDataSourcePropertiesBean")
#ConfigurationProperties("spring.custom.datasource")
public DataSourceProperties customDataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name="customDataSourceBean")
#ConfigurationProperties("spring.custom.datasource")
public HiakriDataSource customDataSource(#Qualifier("customDataSourcePropertiesBean") DataSourceProperties properties) {
return properties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
This datasource builder does not attempt to read the jndi config in application-qa.properties and silently fails back to application.properties returning the WRONG database connection. Resolution is fairly simple - test which environment you are in and customize the type of database connection created. Debugging this was a bear, as the symptom was that the app appeared to be ignoring application-qa.properties. I share to spare others the pain. Add spring.profiles.active=qa etc. to your properties files to know which environment you are in then:
#Value("${spring.profiles.active}")
String profile;
#Value("${spring.custom.jndi-name}")
String jndi;
#Primary
#Bean(name="customDataSourcePropertiesBean")
#ConfigurationProperties("spring.custom.datasource")
public DataSourceProperties customDataSourceProperties() {
return new DataSourceProperties();
}
#Primary
#Bean(name="customDataSourceBean")
#ConfigurationProperties("spring.custom.datasource")
public DataSource customDataSource(#Qualifier("customDataSourcePropertiesBean") DataSourceProperties properties) {
if(profile.equals("localhost")) {
return DataSourceBuilder
.create()
.username(properties.getDataUsername())
.password(properties.getPassword())
.url(properties.getUrl())
.driverClassName(properties.getDriverClassName())
.build();
}else {
JndiDataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
return dataSourceLookup.getDataSource(jndi);
}
}
Related
Running under a Tomcat 9 and JDK 1.8, using Spring 5, I am trying to configure a JNDI connection to get a DataSource.
If I configure Spring, through XML, I get my DataSource and everything seems to work fine. I configured the DataSource in my applicationContext.xml, in this way:
<jee: jndi-lookup id = "dataSource" jndi-name = "jdbc / yages"
resource-ref = "true" />
When I use the AbstractAnnotationConfigDispatcherServletInitializer class to initialize Spring, my DataSource is created but when I try to catch the connection it gives me the following error:
java.sql.SQLException: Data source is closed
at org.apache.tomcat.dbcp.dbcp2.BasicDataSource.createDataSource (BasicDataSource.java:2049)
I try to create the DataSource with this function:
#Bean (name = "dataSource")
public DataSource dataSource (Environment env) throws NamingException
{
DataSource datasource = null;
try {
JndiDataSourceLookup lookup = new JndiDataSourceLookup ();
datasource = lookup.getDataSource ("jdbc/yages");
datasource.getConnection ();
return datasource;
} catch (SQLException ex) {
ex.printStackTrace ();
}
return datasource;
}
It seems that the DataSource is created correctly, but the connection to the database seems to be closed.
However, if I use the DataSource, configure it through XML, it works well for me, which is why I assume that it is not a problem neither of the connection to the database nor of the configuration of Tomcat.
Any idea why the connection is closed?
Thank you.
I found the solution.
It is to create the Bean with this instruction
#Bean(name = "dataSource", destroyMethod = "")
The problem is that Spring to do an undeploy, destroy the Bean and close the connection. To avoid this, I have to change the default behavior of Spring.
I have gone through the spring data jpa reference documentation
to configure a datasource in spring boot,and with LocalcontainerEntityManagerFactoryBean and transactionManager..etc,but run it
with error
but I want configure a datasource of mysql,single datasource.
this is configuration class code:
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement
public class DataSourceConfig {
#Bean
#ConfigurationProperties(prefix="oneslide.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendor=new HibernateJpaVendorAdapter();
vendor.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory=new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendor);
factory.setPackagesToScan("com.oneslide.multiDataSource.domain");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager=new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
I don't want user DatasourceBuilder.create.url().password().. something chain invocation like that,I just want to congiure my sql connection metadata in
application.properties with oneslide.datasource namespce.And try to use the LocalContainerEntityManagerFactory Bean,not with tutorial's way in which they
use spring.datasource.* property.
but when i run it datasource debug info is null,there it is digest of exception log:
Invocation of init method failed; nested exception is
org.hibernate.service.spi.ServiceException: Unable to create requested
service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
: Access to DialectResolutionInfo cannot be null when
'hibernate.dialect' not set
Help me thanks a lot.
I totally misunderstand spring boot,Maybe.....it autoconfigure all bean,like
LocalContainerEntityManagerfactoryBean!so to use multidatasource,I just need to configure a datasource only,Right???
Your annotation #ConfigurationProperties(prefix="oneslide.datasource") is asking Sprint to get the info from the external configuration and bind it with the bean you are annotating, i.e. the result produced by the method.
BUT the annotated bean MUST have the properties to receive the configuration values. I.e. it has to have fields and inner objects that replicate the structure of the configurations you are passing (and the setters too).
As example if your config contains something like below:
oneslide.datasource.url = some_url
oneslide.datasource.user = usr
oneslide.datasource.password = pw
oneslide.datasource.special.detail = whatever
The bean you build should have fields "url", "user" and "password" AND an object "special" with a field "detail", so that Spring can set the values. Simplifying something along Y = X.getSpecial(); Y.setDetail() (with null recognition and object creation too, I think to remember).
If you do nothing... Spring behind the scene will create a DataSourceProperties bean (that unsurprisingly contains the fields normally used to set up a datasource with the config info under "spring.datasource").
You can get hold of this bean by defining your own bean that gets it as a parameter, like below:
public <whatever> getTheD_S_Properties(DataSourceProperties myDataSourceValesFromConfig) {
...do something with the bean you got,
that contains the values from your config...
}
The most common operation, in this case is to just build the datasource yourself, with some logic beyond just assigning the values form the config.
If you need to do nothing special, then let Spring to build the datasource too.
Just sit back and enjoy ! :)
I defined DataSource as bean:
#Bean(name="dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:~/myDB");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
It works perfectly, however i need to specify schema for db creation and load data on it. Is there any chances how to excute both scripts (schema and data scripts) just like Spring Data has? The Only thing I found is datasource.setSchema() and aswell as i'm concerned i have to specify full path to it. Then how to specify it, if my schema script located in src/main/resources/ path? (I did exactly, how documentation says, but it fails with a message)
There was an unexpected error (type=Internal Server Error, status=500).
org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is org.h2.jdbc.JdbcSQLException: Schema "~/schema-h2.sql" not found [90079-193]
Thank you for suggestions
You can do something like this :
import org.springframework.jdbc.datasource.init.DatabasePopulator;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
#Bean(name="dataSource")
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:~/myDB");
dataSource.setUsername("sa");
dataSource.setPassword("");
// schema init
Resource initSchema = new ClassPathResource("script/schema.sql");
DatabasePopulator databasePopulator = new ResourceDatabasePopulator(initSchema);
DatabasePopulatorUtils.execute(databasePopulator, dataSource);
return dataSource;
}
Solution 1
Update your connection url with these additional options:
DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;INIT=CREATE SCHEMA IF NOT EXISTS
Solution 2
You should have a application.properties file in your src/main/resources which should contain these propertes:
spring.datasource.platform=h2
spring.datasource.initialize=true
Workaround
You could aim for putting INIT params with script location script in your connection url (being one of the options):
jdbc:h2:mem:test;INIT=RUNSCRIPT FROM '~/schema.sql'\;RUNSCRIPT FROM '~/data.sql'"
This functionality is enabled via the INIT property. Note that
multiple commands may be passed to INIT, but the semicolon delimiter
must be escaped, as in the example below.
Here is Spring official document - Creating an Embedded Database Programmatically.
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
#Bean(name = "dataSource")
public static DataSource H2DataSource() {
return new EmbeddedDatabaseBuilder()
.setName("testdb")
.setType(EmbeddedDatabaseType.H2)
.addScripts("Your scripts in /resources")
.build();
}
I have a modular application with one DAO implementation module that allows interacting with a database through JDBC. When I wrote the DAO module, I expected to get my DataSource via JNDI since the module was used until now as part of a web application. However, I need to use this same DAO module in a standalone application and therefore the datasource can't be obtained via JNDI.
Here is how I obtain my datasource:
#Bean(destroyMethod = "close")
public DataSource dataSource() throws Exception {
return new JndiDataSourceLookup().getDataSource("java:comp/env/jdbc/mydatasource");
}
I thought about the possibility to define the data source in the upper modules as follows:
For the web app module, same as previous code snippet.
For the standalong module, as follows:
#Bean(destroyMethod = "close")
public DataSource dataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driverClassName);
dataSource.setJdbcUrl(jdbcUrl);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
I'm looking for a better approach if there is one...
I managed to get camel working with its CDI module, but I'm unable to register datasources in the registry. I basically have a sql route that looks like this:
sql:SELECT * FROM INBOX_DFEP WHERE id > :#lastID?dataSource=jdbc/DFEP
my application server is glassfish 4, and I've registered a jdbc resource with that name, as I usually do with JPA. Anyway camel does not seems to be able to lookup that datasource for me.
org.apache.camel.NoSuchBeanException: No bean could be found in the registry for: jdbc/DFEP of type: javax.sql.DataSource
what am I doing wrong? how do I register a datasource in the camel registry using CDI?
You can try this:
public class DataSourceFactory {
#Resource(lookup = "jdbc/DFEP")
private javax.sql.DataSource datasource;
#Produces
#Named("queryDatasource")
public javax.sql.DataSource getDatasource() {
return datasource;
}
}
After that you only have to change your toute to:
sql:SELECT * FROM INBOX_DFEP WHERE id > :#lastID?dataSource=queryDatasource