blob: 76c44fc901304d3450dda0c53b998de331e49eb7 (
plain) (
tree)
|
|
package org.openslx.imagemaster.db;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.log4j.Logger;
import org.openslx.imagemaster.util.Util;
public class Database
{
private static final Logger LOGGER = Logger.getLogger( Database.class );
/**
* Pool of available connections.
*/
private static final Queue<MysqlConnection> pool = new ConcurrentLinkedQueue<>();
/**
* Set of connections currently handed out.
*/
private static final Set<MysqlConnection> busyConnections = Collections.newSetFromMap( new ConcurrentHashMap<MysqlConnection, Boolean>() );
private static final String host;
private static final String dbname;
private static final String user;
private static final String password;
/**
* Static initializer for setting up the database connection.
* This gets called implicitly as soon as the class loader loads
* the class. In most cases that happens when the class is being
* accessed for the first time during run time.
*/
static
{
// Load connection info from class (TODO: Make pretty)
Properties properties = new Properties();
try {
final BufferedInputStream stream = new BufferedInputStream(
new FileInputStream(
"config/mysql.properties" ) );
properties.load( stream );
stream.close();
} catch ( FileNotFoundException e ) {
LOGGER.fatal( "config/mysql.properties not found!" );
System.exit( 1 );
} catch ( IOException e ) {
LOGGER.fatal( "Error reading from config/mysql.properties: " + e.getMessage() );
System.exit( 1 );
} catch ( Exception e ) {
LOGGER.fatal( "Generic error loading mysql properties file." );
e.printStackTrace();
System.exit( 1 );
}
host = properties.getProperty( "host" );
dbname = properties.getProperty( "db" );
user = properties.getProperty( "user" );
password = properties.getProperty( "password" );
Util.notNullFatal( host, "host not set in mysql properties" );
Util.notNullFatal( dbname, "db not set in mysql properties" );
Util.notNullFatal( user, "user not set in mysql properties" );
Util.notNullFatal( password, "password not set in mysql properties" );
try {
Class.forName( "com.mysql.jdbc.Driver" ).newInstance();
} catch ( InstantiationException | IllegalAccessException | ClassNotFoundException e ) {
LOGGER.fatal( "Cannot get mysql JDBC driver!", e );
System.exit( 1 );
}
}
/**
* Get a connection to the database. If there is a valid connection in the
* pool, it will be returned. Otherwise, a new connection is created. If
* there are more than 20 busy connections, <code>null</code> is returned.
*
* @return connection to database, or <code>null</code>
*/
public static MysqlConnection getConnection()
{
MysqlConnection con;
for ( ;; ) {
con = pool.poll();
if ( con == null )
break;
if ( !con.isValid() ) {
con.release();
continue;
}
if ( !busyConnections.add( con ) )
throw new RuntimeException( "Tried to hand out a busy connection!" );
return con;
}
// No pooled connection
if ( busyConnections.size() > 20 ) {
LOGGER.warn( "Too many open MySQL connections. Possible connection leak!" );
return null;
}
try {
// Create fresh connection
String uri = "jdbc:mysql://" + host + "/" + dbname
+ "?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8"
+ "&characterSetResults=utf8&connectionCollation=utf8mb4_unicode_ci";
Connection rawConnection = DriverManager.getConnection( uri,
user, password );
// By convention in our program we don't want auto commit
rawConnection.setAutoCommit( false );
// Wrap into our proxy
con = new MysqlConnection( rawConnection );
// Keep track of busy mysql connection
if ( !busyConnections.add( con ) )
throw new RuntimeException( "Tried to hand out a busy connection!" );
return con;
} catch ( SQLException e ) {
LOGGER.info( "Failed to connect to local mysql server", e );
}
return null;
}
/**
* Called by a {@link MysqlConnection} when its <code>close()</code>-method
* is called, so the connection will be added to the pool of available
* connections again.
*
* @param connection
*/
static void returnConnection( MysqlConnection connection )
{
if ( !busyConnections.remove( connection ) )
throw new RuntimeException( "Tried to return a mysql connection to the pool that was not taken!" );
pool.add( connection );
}
public static void printCharsetInformation()
{
LOGGER.info( "MySQL charset related variables:" );
try ( MysqlConnection connection = Database.getConnection() ) {
MysqlStatement stmt = connection.prepareStatement( "SHOW VARIABLES LIKE :what" );
stmt.setString( "what", "char%" );
ResultSet rs = stmt.executeQuery();
while ( rs.next() ) {
LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
}
stmt.setString( "what", "collat%" );
rs = stmt.executeQuery();
while ( rs.next() ) {
LOGGER.info( rs.getString( "Variable_name" ) + ": " + rs.getString( "Value" ) );
}
} catch ( SQLException e ) {
LOGGER.error( "Query failed in Database.printCharsetInformation()", e );
}
LOGGER.info( "End of variables" );
}
public static void printDebug()
{
LOGGER.info( "Available: " + pool.size() );
LOGGER.info( "Busy: " + busyConnections.size() );
}
}// end class
|