Connection closes using JNDI in Tomcat - java

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.

Related

Set maxConnectionLimit to Manually created connection using Spring DataSourceBuilder.create()

Using application.properties we can provide the
spring.datasource.hikari.maximum-pool-size=10
as spring using the Hikari connection pooling by default.
But when we are creating datasource manually as a specific requirement we cannot use the application.properties.
Scenario.
User will configure the datasource dynamically and that connection object should be available from thereafter.
For this we are creating DatasourceBuilder.create().build() method to create a datasource connection object and set it into the bean factory.
But while creating a datasource connection object using DataSourceBuilder.create().build() method it is creating connection pooling and 10 connection to the database at the same time.
We wanted to avoid that connection pooing and have only one connection.
How do I do that?
While creating the custom datasource we can create an instance of HikariDataSource instead of DataSource.
Assuming you are using Spring default Connection pooling (Hikari)
public DataSource createCustomConnection(String driverClass, String url, String username, String password) {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driverClass);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(MENTIONED_TOTAL_CONNECTIONS);
// Like this you can configure multiple properties here
HikariDataSource dataSource = new HikariDataSource(config);
return dataSource;
}
DataSource dataSource = createCustomConnection(...) // Pass Parameters here
Here you have created the datasource that creates a single connection at the same time.
for more information https://github.com/brettwooldridge/HikariCP#initialization
Thank you.
You can try setting the minimumIdle property. By default it is set to maxPoolSize, so 10 connections are created initially. You can refer minimumIdle section in this link, https://github.com/brettwooldridge/HikariCP#frequently-used

Spring - Change schema connection for persistent_logins

I am working on a Spring Boot web application and I am implementing the "Remember me" function.
I defined in my Web Security Configuration this:
http.authorizeRequests().and()
.rememberMe().tokenRepository(this.persistentTokenRepository())
.tokenValiditySeconds(1 * 24 * 60 * 60); // 24h
and
#Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl db = new JdbcTokenRepositoryImpl();
db.setDataSource(dataSource);
return db;
}
The problem is that when I flag the option on the html page, Spring try to add a token in the default schema of my database -> "public".
Is there any way to change the default schema for that option? Everything else is linked correctly on the right schema through this property:
spring.jpa.properties.hibernate.default_schema=another_schema_name
I tried to make a personal implement of the class JdbcTokenRepositoryImpl but I can't find a way to change the schema. I looked it up online but I didn't find nothing..
Thank you
Regards,
Mohamad
You may initialize differently your dataSource variable what you use in your PersistentTokenRepository bean. Most data sources support schema setting. For instance Spring's org.springframework.jdbc.datasource.DriverManagerDataSource :
#Bean(name = "dataSource")
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
// ... tipicly set username, password, driver class name, jdbc Url
dataSource.setSchema(schema);
return dataSource;
}
You could control the schema through the mentioned property: (spring.jpa.properties.hibernate.default_schema)
#Value("${spring.jpa.properties.hibernate.default_schema}")
private String schema;

Use Spring boot embedded tomcat connection pool without jdbctemplate, hibernate or JPA

I'm integrating Spring boot to replace existing RESTful API we already had. I want to use the connection pool which comes with it. I'm new to Spring boot. I see in DataSource object that I can get connection but it is returning null.
I also know that it's not a best practice to try to use this connection outside separately.
How can I use a Spring boot JDBC connection with my existing Core java Database Access Object?
Create a bean in configuration class like this:
#Configuration
public class Configuration {
#Bean
public DataSource dataSource() {
ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(connectURI,null);
PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory);
ObjectPool objectPool = new GenericObjectPool(poolableConnectionFactory);
PoolingDataSource dataSource = new PoolingDataSource(objectPool);
return dataSource;
}

Obtaining datasource in both JNDI and non-JNDI environment

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...

Configure Multiple DataSource in Spring Boot with JNDI

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

Categories

Resources