In the application we are working on, the user can connect to external RDBMSs by entering an arbitrary JDBC connection URL into a text field. One of our customers reported that our application server freezes (indefinitly) at 0% CPU when he accidentally tried to connect to a Microsoft SQL Server with a MySQL JDBC URL.
The following Java snippet illustrates the situation:
public static void main(String[] args){
// note: the application running on localhost:1433 is ACTUALLY
// an MS SQL Server instance!
String jdbcUrl = "jdbc:mysql://localhost:1433/my-db";
// enable JDBC Driver Manager logging
DriverManager.setLogWriter(new PrintWriter(System.err));
// set a timeout of 5 seconds for connecting (which is blissfully ignored!)
DriverManager.setLoginTimeout(5);
// open the connection (which should fail, but freezes instead)
try (Connection c = DriverManager.getConnection(jdbcUrl)){
System.out.println("This should never be reached due to wrong JDBC URL.");
}catch(Exception e){
System.out.println("This is expected (but never printed).");
}
System.out.println("This is never printed either.");
}
To run the snippet:
have a SQL Server instance running on localhost:1433 (content does not matter)
have the MariaDB JDBC driver version 2.2.5. (latest) on your classpath.
Questions:
1) Could this be a bug in the MariaDB JDBC driver? A google search revealed nothing in this regard.
2) How should I work around this issue? I don't want my server to freeze when the user accidentally inserts an invalid JDBC URL.
I tried several other JDBC drivers (MySQL, DB2, Oracle...) and they all handle this issue gracefully, only the MariaDB JDBC driver freezes the JVM.
Here's what I did to resolve the issue. The trick is to add a socketTimeout to the connection. To fix the program in the question, it is enough to modify the JDBC URL to be:
jdbc:mysql://localhost:1433/my-db?socketTimeout=2000
This answer to a related question was the hint I needed.
Answer 1: Yes, it is a bug. They missed to use the login timeout in the implementation of mariadb jdbc driver.
Answer 2: I worked around by using a task that that wraps the getConnection method. This task is stopped after a defined login time if it hasn't finished. Here is my implementation.
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import org.mariadb.jdbc.util.DefaultOptions;
public class ConnectionTest {
private static final String CONNECTION_STRING = "jdbc:mariadb://localhost:3306/test";
private static final String USER = "root";
private static final String PW = "";
private static final int LOGIN_TIMEOUT_SEC = 2;
public static void main(String[] args) throws Exception {
var test = new ConnectionTest();
Connection connection = test.getConnection();
if(connection != null && connection.isValid(LOGIN_TIMEOUT_SEC)) {
System.out.println("Connected!");
}
}
private Connection getConnection() throws Exception {
ConnEstablishSync sync = new ConnEstablishSync();
Properties conProps = new Properties();
conProps.setProperty(DefaultOptions.USER.getOptionName(), USER);
conProps.setProperty(DefaultOptions.PASSWORD.getOptionName(), PW);
FutureTask<Connection> task = new FutureTask<>(() -> {
Connection c = DriverManager.getConnection(CONNECTION_STRING, conProps);
if(sync.canceled && c != null) {
c.close();
c = null;
}
return c;
});
Connection connection = null;
ExecutorService executor = Executors.newSingleThreadExecutor();
try {
executor.submit(task);
connection = task.get(LOGIN_TIMEOUT_SEC, TimeUnit.SECONDS);
} finally {
sync.canceled = true;
task.cancel(true);
executor.shutdown();
}
return connection;
}
private static class ConnEstablishSync {
private volatile boolean canceled = false;
}
}
Related
I got to use MariaDB for my University Project.
it's my first time doing it, so I dont't know well how to use and code JDBC Driver and mariaDB.
Now I'm implementing the code in many places while looking at examples.
As I see, All the examples seems to creating Statement and making connection by using "DriverManager.getConnection"
Now I have a question.
I'm going to create a DBmanager Class that can connect, create tables, execute queries, and execute the code that updates data on tables in a single line.
I thought all the examples would run alone in one method and came from different places, so I could only try a new connection and create a code that would not close. But I have a gut feeling that this will be a problem.
Is there any way I can leave a connection connected at a single connection to send a command, and disconnect it to DB.disconnect()? And I'd appreciate it if you could tell me whether what I'm thinking is right or wrong.
The code below is the code I've written so far.
I am sorry if you find my English difficult to read or understand. I am Using translator, So, my English could not be display as I intended.
import java.sql.*;
import java.util.Properties;
public class DBManager {
/*********INNITIAL DEFINES********/
final static private String HOST="sumewhere.azure.com";//Azure DB URL
final static private String USER="id#somewhere";//root ID
final static private String PW="*****";//Server Password
final static private String DRIVER="org.mariadb.jdbc.Driver";//DB Driver info
private String database="user";
/***************API***************/
void setDB(String databaseinfo){
database=databaseinfo;
}
private void checkDriver() throws Exception
{
try
{
Class.forName("org.mariadb.jdbc.Driver");
}
catch (ClassNotFoundException e)
{
throw new ClassNotFoundException("MariaDB JDBC driver NOT detected in library path.", e);
}
System.out.println("MariaDB JDBC driver detected in library path.");
}
public void checkOnline(String databaseinfo) throws Exception
{
setDB(databaseinfo);
this.checkDriver();
Connection connection = null;
try
{
String url = String.format("jdbc:mariadb://%s/%s", HOST, database);
// Set connection properties.
Properties properties = new Properties();
properties.setProperty("user", USER);
properties.setProperty("password", PW);
properties.setProperty("useSSL", "true");
properties.setProperty("verifyServerCertificate", "true");
properties.setProperty("requireSSL", "false");
// get connection
connection = DriverManager.getConnection(url, properties);
}
catch (SQLException e)
{
throw new SQLException("Failed to create connection to database.", e);
}
if (connection != null)
{
System.out.println("Successfully created connection to database.");
}
else {
System.out.println("Failed to create connection to database.");
}
System.out.println("Execution finished.");
}
void makeCcnnection() throws ClassNotFoundException
{
// Check DB driver Exists
try
{
Class.forName("org.mariadb.jdbc");
}
catch (ClassNotFoundException e)
{
throw new ClassNotFoundException("MariaDB JDBC driver NOT detected in library path.", e);
}
System.out.println("MariaDB JDBC driver detected in library path.");
Connection connection = null;
}
public void updateTable(){}
public static void main(String[] args) throws Exception {
DBManager DB = new DBManager();
DB.checkOnline("DB");
}
}
For a studying project it's okay to give a connection from your DB Manager to client code and close it there automatically using try-with-resources construction.
Maybe you will find it possible to check Connection Pool tools and apply it further in your project or use as example (like HikariCP, here is a good introduction).
Read about Java try with resources. I think that this link could be usefull for your problem.
JDBC with try with resources
I just can't connect to mySQL using Eclipse and can't figure out why.
Here is my connection class :
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import com.mysql.jdbc.Connection;
import com.mysql.jdbc.Statement;
public class ConnexionBDD {
static String url = "jdbc:mysql://localhost:8888/Peoples?autoReconnect=true&useSSL=false";
static String login = "root";
static String password = "";
static Connection connection = null;
static Statement statement = null;
static ResultSet result = null;
static String request = "";
public static void main (String[] args) {
System.out.println("will load driver");
loadingDrive();
System.out.println("will connect");
connection();
}
public static void loadingDrive() {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch ( ClassNotFoundException e ) {
e.getMessage();
}
}
public static void connection() {
try
{
connection = (Connection) DriverManager.getConnection(url, login, password);
System.out.println("Connected");
statement = (Statement) connection.createStatement();
result = statement.executeQuery(request);
}
catch ( SQLException e )
{
System.out.println(e.getMessage());
} finally {
if ( result != null ) {
try {
result.close();
} catch ( SQLException ignore ) {
}
}
if ( statement != null ) {
try {
statement.close();
} catch ( SQLException ignore ) {
}
}
if ( connection != null ) {
try {
connection.close();
} catch ( SQLException ignore ) {
}
}
}
}
}
Here is the result in the console :
will load driver
will connect
Could not create connection to database server. Attempted reconnect 3 times. Giving up.
I have the connector (mysql-connector-java-5.1.41-bin.jar) in the file WebContent/WEB-INF/lib.
I installed mySQL on my mac.
I installed MAMP, I can reach phpmyadmin and add a new database, but it's kind of weird though, phpmyadmin is already logged as default and clicking the "Exit" button does not disconnect me I don't know why..
The url of phpmyadmin is :
http://localhost:8888/phpmyadmin/index.php
Why can't I connect to mySQL ?
EDIT :
I changed my url with the right port (8889) after checking out the mySQL port on MAMP, but nothing changed in the output of the console.
MySQL and phpMyAdmin cannot be on the same port.
Are you sure that your MySQL is not running on the default port? 3306
UPDATE:
If you are not already using Maven, please do so. It will aid you in managing your packages. That way you can avoid your method: loadingDrive()
I just ran you code locally with a test database. It runs fine for me.
I did the following changes to your code:
I removed the loadingDrive()
Altered request to static String request = "SELECT 1";. This query string is good for testing if the connection to the database is working properly.
I printed the request to the console:
result = statement.executeQuery(request);
System.out.println(result.next());
I added the mysql jdbc connector to my pom.xml file:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
After this I ran the code and this is the console output:
will connect
Connected
true
I still think your MySQL port is wrong. Do you run MySQL locally? Can you connect to it with phpMyAdmin?
I tried to change the url to contain the wrong port. I then received:
Could not create connection to database server. Attempted reconnect 3 times. Giving up.
So I strongly believe that your port is wrong. Can you try to change your url to the following?
static String url = "jdbc:mysql://localhost:3306/Peoples?autoReconnect=true&useSSL=false";
I am developing a simple CRUD application, using JDBC to establish connection and perform basic CRUD operations. In that process, created a DatabaseListener to create a connection object at startup and storing it in the context attribute for reuse.
Below is the code.
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import org.apache.log4j.Logger;
public class DatabaseInitListner implements ServletContextListener {
private static final Logger LOG = Logger.getLogger(DatabaseInitListner.class);
private DBUtil databaseUtil = null;
#Override
public void contextDestroyed(ServletContextEvent event) {
databaseUtil.closeConnection();
}
#Override
public void contextInitialized(ServletContextEvent contextinitEvent) {
ServletContext servletContext = contextinitEvent.getServletContext();
String database = servletContext.getInitParameter("db_name");
String url = servletContext.getInitParameter("db_url")
+ database;
String username = servletContext.getInitParameter("db_user");
String password = servletContext.getInitParameter("db_password");
String driverName = servletContext.getInitParameter("db_driver");
databaseUtil = new DBUtil(url, username, password,
driverName);
servletContext.setAttribute("databaseSingleConnectionObject",
databaseUtil.getConnection());
}
}
public class DBUtil {
private Connection connection = null;
private static final Logger LOG = Logger.getLogger(DatabaseUtil.class);
public DatabaseUtil(String url, String username, String password,
String driver) {
try {
Class.forName(driver);
this.connection = DriverManager.getConnection(url, username,
password);
LOG.debug("Connection Established... ");
} catch (ClassNotFoundException | SQLException e) {
LOG.error("Could not create connection... ", e);
}
}
public Connection getConnection() {
return connection;
}
public void closeConnection() {
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
LOG.error("Unable to close connection... ", e);
}
}
}
}
I am accessing the connection in servlets like this
Connection jdbcConnection = (Connection) getServletContext().getAttribute("databaseSingleConnectionObject");
I am not sure if this is right approach. What are the effects of single database connection?
When you use a single database connection like this you make your application slow and brittle.
Slow: because the connection implementation is synchronized, each user has to wait until the connection is free. If one user's query takes a while to come back that directly increases the time any other concurrent users spend waiting. If there were multiple connections available from a pool then the time spent by one user would not impact other users nearly as greatly (unless a query's results take all the JVM's memory or a big query bogs down the database server).
Brittle: The connection is a network connection, they tend to go down. Without a provision to create new connections any kind of timeout, network hiccup, or period of database non-availability (such as taking the database offline for maintenance) is going to require an application restart. Using a connection pool will mean your application will be able to survive these episodes and recover without outside intervention.
This will not be threadsafe, and if it were, performance would be really poor.
Look into using a Connection Pool, like DBCP or C3PO
You should let your application server manage database connection. Add a JNDI datasource in its configuration file and make a lookup from your application to get a connection when needed (for instance when you instantiate a class that must access your database).
You may configure the datasource to manage a connection pool so that each user session will get its own.
Depending on the AS you use run a search with keywords 'JNDI' and 'datasource' and you will get further details about the AS configuration and how to implement it in your application.
I am using mongoDb with java. I am getting the following error while inserting data. Any help would be appreciated. Thanx
Jul 4, 2012 1:45:32 PM org.xsocket.connection.HandlerAdapter performOnData
WARNING: [6f829b013850ff7914137a5cceC291] closing connection. Error occured by performing onData of com.avaya.onex.hss.requesthandlers.ResponseHandler#18746603 java.lang.OutOfMemoryError: Java heap space
Jul 4, 2012 1:45:25 PM com.mongodb.DBPortPool gotError
WARNING: emptying DBPortPool to localhost:27017 b/c of error
java.net.SocketException: Connection reset by peer: socket write error
at java.net.SocketOutputStream.socketWrite0(Native Method)
at java.net.SocketOutputStream.socketWrite(SocketOutputStream.java:92)
at java.net.SocketOutputStream.write(SocketOutputStream.java:136)
at org.bson.io.PoolOutputBuffer.pipe(PoolOutputBuffer.java:129)
at com.mongodb.OutMessage.pipe(OutMessage.java:111)
at com.mongodb.DBPort.go(DBPort.java:119)
at com.mongodb.DBPort.go(DBPort.java:89)
at com.mongodb.DBPort.say(DBPort.java:84)
at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:153)
at com.mongodb.DBTCPConnector.say(DBTCPConnector.java:138)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:261)
at com.mongodb.DBApiLayer$MyCollection.insert(DBApiLayer.java:211)
at com.mongodb.DBCollection.insert(DBCollection.java:57)
at com.mongodb.DBCollection.insert(DBCollection.java:102)
at com.avaya.onex.hss.requesthandlers.DatabaseConnection.dbWrite(DatabaseConnection.java:50)
at com.avaya.onex.hss.requesthandlers.ResponseHandler.handleResponse(ResponseHandler.java:232)
at com.avaya.onex.hss.requesthandlers.ResponseHandler.onData(ResponseHandler.java:104)
at org.xsocket.connection.HandlerAdapter.performOnData(HandlerAdapter.java:242)
at org.xsocket.connection.HandlerAdapter.access$200(HandlerAdapter.java:42)
at org.xsocket.connection.HandlerAdapter$PerformOnDataTask.run(HandlerAdapter.java:210)
at org.xsocket.SerializedTaskQueue.performPendingTasks(SerializedTaskQueue.java:161)
at org.xsocket.SerializedTaskQueue.access$100(SerializedTaskQueue.java:40)
at org.xsocket.SerializedTaskQueue$MultithreadedTaskProcessor.run(SerializedTaskQueue.java:189)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
This is the class where I have dbWrite function which I call from other classes for inserting data in mongodb.
package com.avaya.onex.hss.requesthandlers;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.Mongo;
import com.mongodb.MongoOptions;
import com.mongodb.WriteConcern;
public class DatabaseConnection {
private static DatabaseConnection con = null;
static DB db=null;
static Mongo m;
//private static int count=0;
private DatabaseConnection(){
}
public void dbConnect() {
try{
// connect to the local database server
MongoOptions options = new MongoOptions();
options.connectionsPerHost = 9000000;
options.maxWaitTime = 1000000;
options.socketKeepAlive = true;
options.threadsAllowedToBlockForConnectionMultiplier = 1000;
m = new Mongo("localhost", options);
//m.dropDatabase("ClientSimulator");
db = m.getDB( "ClientSimulator" );
}catch(Exception e){
e.printStackTrace();
}
}
public static synchronized DatabaseConnection getInstanceClass(){
if(con==null)
con=new DatabaseConnection();
return con;
}
public DB getDatabaseObject(){
return db;
}
public synchronized void dbWrite(DBCollection coll, BasicDBObject obj){
coll.insert(obj);
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void dbRead(DBCollection coll){
}
public synchronized void dbUpdate(DBCollection coll) {
}
public synchronized void dbCollDrop(DBCollection coll){
coll.drop();
}
}
Try this.
options.connectionsPerHost = 100;
options.maxWaitTime = 2000;
options.socketKeepAlive = true;
options.threadsAllowedToBlockForConnectionMultiplier = 50;
connectionsPerHost is the connectionPool. If you put 9000000 it will try to create 90000000 threads and connection object to handle your request. I believe that it is by far more than necessary. You have to reduce that number, and you might also remove the sleep(100) in your insert function.
If java tries to create that many connections. Mongodb server might not be able to handle that much connection or you'd have to change the config to allow that much connections.
Keep in mind that connectionsPerHost is not the maximum quantity of threads. You could have 1000000 threads inserting but only 100 connections will be used and will be shared for all the threads once they are free. maxWaitTime of 2 second should be more than enough. If an insert takes more than 2 seconds, you might consider upgrading your server.
Driver can't remove dropped socket from connection from pool, you need use try catch to let the driver know the socket is dropped.
well I have a pretty awkward situation. I have a working database managers class, which works when I run it on the desktop version of it (Swing GUI), however, when I run the same class on the servlet, I get a strange error, that it can't get the connection. I am using database pooling for optimisation.
So the error looks as follows:
Error in Database Connection: Error getting connection to database - java.sql.SQLException: No suitable driver found for jdbc:sqlserver://isd.ktu.lt:1433;DatabaseName=LN2012_bakDB2
And the class with the methods involved looks like this:
package Core;
import DataTypes.Parameters;
import Interfaces.OutputInterface;
import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDriver;
import org.apache.commons.pool.impl.GenericObjectPool;
/**
*
* #author arturas
*/
public class DatabaseConnection {
String specificError = "Error in Database Connection: ";
OutputInterface gui = null;
boolean allowOutput = true;
GenericObjectPool connectionPool;
ConnectionFactory connectionFactory;
PoolableConnectionFactory poolableConnectionFactory;
PoolingDriver driver;
Connection con = null;
public DatabaseConnection(Parameters params) {
// parameters and the output
this.gui = params.getGui();
// activate database pool
connectionPool = new GenericObjectPool(null);
connectionFactory = new DriverManagerConnectionFactory(params.getDbAdr(), params.getDbUser(), params.getDbPass());
poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory, connectionPool, null, null, false, true);
driver = new PoolingDriver();
driver.registerPool("GenTreeDatabase", connectionPool);
}
public void openConn() {
if (allowOutput) gui.print("Getting connection to database");
try {
con = DriverManager.getConnection("jdbc:apache:commons:dbcp:GenTreeDatabase");
if (con != null) {
if (allowOutput) gui.print("Connection to database was successful");
}
} catch (SQLException ex) {
gui.err(specificError + "Error getting connection to database - " + ex);
}
}
public void closeConn() {
try {
con.close();
if (allowOutput) {
gui.print("Connection to database closed successfully");
}
} catch (SQLException ex) {
gui.err(specificError + ex);
}
}
The error appears when the try in method openConn is called.
Can anybody help me with this?
You are getting this error because there is no drivers in your classpath. Probably in your desktop application there were. You need to put driver's .jar file into your servlet container's global classpath or in your application classpath and it should work.
I prefer adding driver's jar into server global classpath, because there can be more than one application which will use the same .jar file to load drivers.
make sure of this
1) you should make sure that .jar library is compatabile with RDMS you are using
2) that you included the .jar for connection in your netbeans in
projectproperties-->libraries
3)copy the .jar into C:\Program Files\Apache Software Foundation\Apache Tomcat 6.0.26\lib
and this is important
if you dont have the driver in location you get not found error but
you get no suitable so i think the version must be incompatible so what version of sql server are you using...