scalikejdbc.ConnectionPool.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2011 Kazuhiro Sera
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/
package scalikejdbc
import javax.sql.DataSource
import java.sql.Connection
/**
* Connection Pool
*
* Using Commons DBCP internally.
*
* @see http://commons.apache.org/dbcp/
*/
object ConnectionPool extends LogSupport {
type MutableMap[A, B] = scala.collection.mutable.HashMap[A, B]
type CPSettings = ConnectionPoolSettings
type CPFactory = ConnectionPoolFactory
val DEFAULT_NAME: Symbol = 'default
private[this] val pools = new MutableMap[Any, ConnectionPool]()
/**
* Returns true when the specified Connection pool is already initialized.
* @param name pool name
* @return is initialized
*/
def isInitialized(name: Any = DEFAULT_NAME) = pools.synchronized {
pools.get(name).isDefined
}
private def ensureInitialized(name: Any): Unit = {
if (!isInitialized(name)) {
val message = ErrorMessage.CONNECTION_POOL_IS_NOT_YET_INITIALIZED + "(name:" + name + ")"
throw new IllegalStateException(message)
}
}
/**
* Returns Connection pool. If the specified Connection pool does not exist, returns null.
*
* @param name pool name
* @return connection pool
*/
def apply(name: Any = DEFAULT_NAME): ConnectionPool = get(name)
/**
* Returns Connection pool. If the specified Connection pool does not exist, returns null.
*
* @param name pool name
* @return connection pool
*/
def get(name: Any = DEFAULT_NAME): ConnectionPool = pools.synchronized {
pools.get(name).orNull
}
/**
* Register new named Connection pool.
*
* @param name pool name
* @param url JDBC URL
* @param user JDBC username
* @param password JDBC password
* @param settings Settings
*/
def add(name: Any, url: String, user: String, password: String,
settings: CPSettings = ConnectionPoolSettings())(implicit factory: CPFactory = CommonsConnectionPoolFactory) {
import scalikejdbc.JDBCUrl._
val oldPoolOpt: Option[ConnectionPool] = pools.get(name)
// register new pool or replace existing pool
pools.synchronized {
// Heroku support
val pool = url match {
case HerokuPostgresRegexp(_user, _password, _host, _dbname) =>
val _url = "jdbc:postgresql://%s/%s".format(_host, _dbname)
factory.apply(_url, _user, _password, settings)
case url @ HerokuMySQLRegexp(_user, _password, _host, _dbname) =>
val defaultProperties = """?useUnicode=yes&characterEncoding=UTF-8&connectionCollation=utf8_general_ci"""
val addDefaultPropertiesIfNeeded = MysqlCustomProperties.findFirstMatchIn(url).map(_ => "").getOrElse(defaultProperties)
val _url = "jdbc:mysql://%s/%s".format(_host, _dbname + addDefaultPropertiesIfNeeded)
factory.apply(_url, _user, _password, settings)
case _ =>
factory.apply(url, user, password, settings)
}
pools.update(name, pool)
// wait a little because rarely NPE occurs when immediately accessed.
Thread.sleep(100L)
}
// asynchronously close the old pool if exists
oldPoolOpt.foreach { oldPool =>
// TODO concurrent.ops is deprecated in Scala 2.10. When we give up supporting 2.9, rewrite this code.
scala.concurrent.ops.spawn {
log.info("The old pool destruction started. connection pool : " + get(name).toString())
var millis = 0L
while (millis < 60000L && oldPool.numActive > 0) {
Thread.sleep(100L)
millis += 100L
}
oldPool.close()
log.info("The old pool is successfully closed. connection pool : " + get(name).toString())
}
}
log.debug("Registered connection pool : " + get(name).toString())
}
/**
* Register the default Connection pool.
*
* @param url JDBC URL
* @param user JDBC username
* @param password JDBC password
* @param settings Settings
*/
def singleton(url: String, user: String, password: String,
settings: CPSettings = ConnectionPoolSettings())(implicit factory: CPFactory = CommonsConnectionPoolFactory): Unit = {
add(DEFAULT_NAME, url, user, password, settings)(factory)
log.debug("Registered singleton connection pool : " + get().toString())
}
/**
* Returns javax.sql.DataSource.
* @param name pool name
* @return datasource
*/
def dataSource(name: Any = DEFAULT_NAME): DataSource = {
ensureInitialized(name)
get(name).dataSource
}
/**
* Borrows a java.sql.Connection from the specified connection pool.
* @param name pool name
* @return connection
*/
def borrow(name: Any = DEFAULT_NAME): Connection = {
ensureInitialized(name)
val pool = get(name)
log.debug("Borrowed a new connection from " + pool.toString())
pool.borrow()
}
/**
* Close a pool by name
* @param name pool name
*/
def close(name: Any = DEFAULT_NAME): Unit = {
pools.synchronized {
val removed = pools.remove(name)
removed.foreach { pool => pool.close() }
}
}
/**
* Close all connection pools
*/
def closeAll(): Unit = {
pools.synchronized {
pools.foreach {
case (name, pool) =>
close(name)
}
}
}
}
/**
* Connection Pool
*/
abstract class ConnectionPool(
val url: String,
val user: String,
password: String,
val settings: ConnectionPoolSettings = ConnectionPoolSettings()) {
/**
* Borrows java.sql.Connection from pool.
* @return connection
*/
def borrow(): Connection
/**
* Returns javax.sql.DataSource object.
* @return datasource
*/
def dataSource: DataSource
/**
* Returns num of active connections.
* @return num
*/
def numActive: Int = throw new UnsupportedOperationException
/**
* Returns num of idle connections.
* @return num
*/
def numIdle: Int = throw new UnsupportedOperationException
/**
* Returns max limit of active connections.
* @return num
*/
def maxActive: Int = throw new UnsupportedOperationException
/**
* Returns max limit of idle connections.
* @return num
*/
def maxIdle: Int = throw new UnsupportedOperationException
/**
* Returns self as a String value.
* @return printable String value
*/
override def toString() = "ConnectionPool(url:" + url + ", user:" + user + ")"
/**
* Close this connection pool.
*/
def close(): Unit = throw new UnsupportedOperationException
}
/**
* Commons DBCP Connection Pool
*
* @see http://commons.apache.org/dbcp/
*/
class CommonsConnectionPool(
override val url: String,
override val user: String,
password: String,
override val settings: ConnectionPoolSettings = ConnectionPoolSettings())
extends ConnectionPool(url, user, password, settings) {
import org.apache.commons.pool.impl.GenericObjectPool
import org.apache.commons.dbcp.{ PoolingDataSource, PoolableConnectionFactory, DriverManagerConnectionFactory }
private[this] val _pool = new GenericObjectPool(null)
_pool.setMinIdle(settings.initialSize)
_pool.setMaxIdle(settings.maxSize)
_pool.setMaxActive(settings.maxSize)
_pool.setMaxWait(settings.connectionTimeoutMillis)
_pool.setWhenExhaustedAction(GenericObjectPool.WHEN_EXHAUSTED_BLOCK)
_pool.setTestOnBorrow(true)
// Initialize Connection Factory
// (not read-only, auto-commit)
new PoolableConnectionFactory(
new DriverManagerConnectionFactory(url, user, password),
_pool,
null,
settings.validationQuery,
false,
true)
private[this] val _dataSource: DataSource = new PoolingDataSource(_pool)
override def dataSource: DataSource = _dataSource
override def borrow(): Connection = dataSource.getConnection()
override def numActive: Int = _pool.getNumActive
override def numIdle: Int = _pool.getNumIdle
override def maxActive: Int = _pool.getMaxActive
override def maxIdle: Int = _pool.getMaxIdle
override def close(): Unit = _pool.close()
}