I have implemented HikariCP which is working fine and I'm now planning to do a graceful shutdown of my application and I would like to make HikariCP close the database connection properly and not just killing the java application.
I was reading on google and I could see the HikariDataSource should have a close method.... but in fact I'm not able to see it available:
private static DataSoure ds;
:
public blabla() {
HikariConfig config = new HikariConfig();
config.setJdbc(jdbcURL);
:
ds = new HikariDataSource(config)
In Eclipse, if I tried ds.close()... Eclipse does not showing "close" as a valid method for HikariDataSource:
Am i doing something wrong? Probabily.... Any idea on how to make HikariCP to close properly the database connection?
Thanks,
Helio
You declare ds as javax.sql.DataSource and assign it with HikariDataSource. This way you will not have access to HikariDataSource native method.
((HikariDataSource)ds).close();
would do the trick.
Related
I need to set SimpleDriverDataSource object in order to create datasource with MongoDB, however I am not able to figure out what I should pass as "Driver".
I searched and found out that this is the JDBC driver for MongoDB "mongodb.jdbc.MongoDriver", but how do I set it up while initializing SimpleDriverDataSource object?
I have tried doing the below, but it shows error mentioned below
#Bean
protected DataSource dataSource() {
SimpleDriverDataSource ds = new SimpleDriverDataSource();
Driver driver = new Driver("mongodb.jdbc.MongoDriver");
ds.setDriverClass(driver);
ds.setUrl("jdbc:mongodb://localhost:27017:spring-security-sampledb");
ds.setUsername("root");
ds.setPassword("secret");
return ds;
}
error
Cannot instantiate the type Driver
I am new to Spring Boot. Can you please help me in implementing this?
java.sql.Driver is an interface, which means you cannot instantiate it like that. You may want to review the basics of Java before starting with Spring Boot.
In any case, you need to use new mongodb.jdbc.MongoDriver() instead of new Driver(), or use setDriverClass and pass it the class reference (mongodb.jdbc.MongoDriver.class) instead of a Driver instance.
Also, please be aware that SimpleDriverDataSource is probably the wrong choice, as - for example - it doesn't provide connection pooling.
I am using DriverManager.getConnection(url, prop) to get the connection. I am trying to inject the jdbc interceptors using properties like below but it is not working.
Properties prop = new Properties();
...
prop.setProperty("jdbcInterceptors", "com.amazonaws.xray.sql.mysql.TracingInterceptor;");
However, when we try to do via datasource it is working.
import org.apache.tomcat.jdbc.pool.DataSource;
DataSource source = new DataSource();
source.setUrl("url");
source.setUsername("user");
source.setPassword("password");
source.setDriverClassName("com.mysql.jdbc.Driver");
source.setJdbcInterceptors("com.amazonaws.xray.sql.mysql.TracingInterceptor;");
Not sure what is wrong with DriverManager properties.
These interceptors are a feature of the Tomcat org.apache.tomcat.jdbc.pool.DataSourceProxy and its subclass org.apache.tomcat.jdbc.pool.DataSource. This is not a feature of JDBC itself, nor a feature of the JDBC driver you're using, so the only way to access it is through a Tomcat data source.
In short, it doesn't work with DriverManager because this feature doesn't exist in DriverManager.
In Java with MySQL we want to add the jdbc ClientInfo to identify the source of each query. Currently we can do something like:
try(Connection connection = dataSource.getConnection()){
connection.setClientInfo("ApplicationName", "MyApp");
}
But I need to add it to every connection created and means checking all the source code for places where a new connection is created. I will like to set it to the DataSource level.
So far what works for me is to extends the DataSource with a custom overriden getConnection method that calls setClientInfo. This is not only a dirty workarround but datasource specific.
I have seen that mysql driver has ClientInfoProviders like the default com.mysql.cj.jdbc.CommentClientInfoProvider. A custom ClientInfoProvider can be configured like:
Properties properties = new Properties();
properties.setProperty(PropertyKey.clientInfoProvider.getKeyName(), "foo.bar.CustomClientInfoProvider");
properties.setProperty(APPLICATION_NAME, "MyApp");
HikariConfig dataSourceConfig = new HikariConfig();
dataSourceConfig.setDataSourceProperties(properties);
...
But it is only called if someone calls the getClientInfo in the connection anyway.
So I will like to know:
Is there support in the MySql driver to set the clientInfo in the DataSource just by setting properties?
If there is a way. How can it be done?
I think you can use AspectJ as a possible solution for it. You can create an aspect which will intercept calls of the DataSource.getConnection method and then call the setClientInfo method with configured parameters when the connection is established.
I am using HikariCP 2.3.3 with Spring and Jetty 9 and am trying to resolve the fact that when I hot deploy a new war file, all of the Hikari database pool connections to MySQL are left open and idle. I am using a JNDI lookup in my spring applicationContext file to retrieve the datasource from a Jetty context file.
Since I cannot specify a destroy-method in the jndi-lookup like I can if I were to define a dataSource bean, I referred to this question: Should I close JNDI-obtained data source?, where it mentions you can attempt to close the datasource in the contextDestroyed() method of a ServletContextListener. In that case they were using tomcat and c3po so I'm not sure how much the example relates.
I have tried the following in my contextDestroyed method:
InitialContext initial;
DataSource ds;
try
{
initial = new InitialContext();
ds = (DataSource) initial.lookup("jdbc/myDB");
if (ds.getConnection() == null)
{
throw new RuntimeException("Failed to find the JNDI Datasource");
}
HikariDataSource hds = (HikariDataSource) ds;
hds.close();
} catch (NamingException | SQLException ex)
{
Logger.getLogger(SettingsInitializer.class.getName()).log(Level.SEVERE, null, ex);
}
But at HikariDataSource hds = (HikariDataSource) ds; I get the following exception: java.lang.ClassCastException: com.zaxxer.hikari.HikariDataSource cannot be cast to com.zaxxer.hikari.HikariDataSource
I have also tried the following after reading this issue on GitHub: Is it essential to call shutdown() on HikariDataSource?:
InitialContext initial;
DataSource ds;
try
{
initial = new InitialContext();
ds = (DataSource) initial.lookup("jdbc/myDB");
ds.unwrap(HikariDataSource.class).close();
} catch (NamingException | SQLException ex)
{
Logger.getLogger(SettingsInitializer.class.getName()).log(Level.SEVERE, null, ex);
}
But I get the following exception: java.sql.SQLException: Wrapped connection is not an instance of class com.zaxxer.hikari.HikariDataSource
at com.zaxxer.hikari.HikariDataSource.unwrap(HikariDataSource.java:177)
I feel like I'm close to a working solution but can't quite get it. What is the proper way to close a JNDI HikariCP data source, whether in contextDestroyed() or elsewhere?
I can't find where the 2.3.3 code lines up with the HikariDataSource.java:177 line number above. One suggestion is upgrading to the latest HikariCP version, 2.3.8.
While your code looks correct, I suspect that you are running into a classloader issue, whereby the HikariDataSource (class) loaded by Jetty/Spring classloader and registered in JNDI is not the same classloader that is loading HikariDataSource in your web app.
One quick way to check is to log/print both class instances like so:
...
ds = (DataSource) initial.lookup("jdbc/myDB");
logger.info("JNDI HikariDataSource : " + System.identityHashCode(ds.getClass()));
logger.info("Local HikariDataSource: " + System.identityHashCode(HikariDataSource.class));
...
If the two class objects have different hashCodes, they are not the same class. In which case you will have to investigate whether the JNDI instance is registered in the "global JNDI context". If it is, that datasource can be shared across web app instances, and it is not appropriate for your web app to unilaterally shut it down.
UPDATE: Sorry I missed it. Re-reading your question, my guess was correct. Your original error:
java.lang.ClassCastException: com.zaxxer.hikari.HikariDataSource cannot be cast to
com.zaxxer.hikari.HikariDataSource
is a clear indication that there are two classloaders that have loaded two separate instances of the HikariDataSource class. The first is the Jetty classloader (JNDI), and the second is your web application classloader.
This indicates that the pool is shared across web applications and you probably should not try to shut it down from your application context.
I need to create a connection pool from a spring application running in a tomcat server.
This application has many catalogs, the main catalog (its is static) called 'db' has just one table with all existing catalog names and a boolean flag for the "active" one.
When the application starts I need to choose from the main catalogs the active one, then I have to select it as default catalog.
How can I accomplish this?
Until now I used a custom class DataSourceSelector extends DriverManagerDataSource but now I need to improve the db connection using a pool, then I thought about a tomcat dbcp pool.
I would suggest the following steps:
Extend BasicDataSourceFactory to produce customized BasicDataSources.
Those customized BasicDataSources would already know which catalog is active and have the defaultCatalog property set accordingly.
Use your extended BasicDataSourceFactory in the Tomcat configuration.
#Configuration
public class DataAccessConfiguration {
#Bean(destroyMethod = "close")
public javax.sql.DataSource dataSource() {
org.apache.tomcat.jdbc.pool.DataSource ds = new org.apache.tomcat.jdbc.pool.DataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost/db");
ds.setUsername("javauser");
ds.setPassword("");
ds.setInitialSize(5);
ds.setMaxActive(10);
ds.setMaxIdle(5);
ds.setMinIdle(2);
ds.get
return ds;
}
}