Permalink
Browse files

Support Typesafe Config-based configuration of DataSource beans

- DataSourceJdbcDataSource is now a JdbcDataSourceFactory. DataSource
  classes are instantiated and configured as Java Beans (similar to
  the way HikariCP uses them). DataSourceJdbcDataSource is now used
  instead of DriverJdbcDataSource for “connectionPool = disabled”.

- Wrap Driver/DriverManager-based connections in DriverDataSource. This
  is used as the default (for full compatibility with the old config
  syntax) when using DataSourceJdbcDataSource without specifying the
  `dataSourceClass`.

- Support nested Properties values in ConfigExtensionMethods for proper
  configuration of nested DataSources.

- Deprecate DriverBasedJdbcDataSource and DriverJdbcDataSource.

Tests in DataSourceTest.
  • Loading branch information...
szeiger committed Jul 15, 2015
1 parent d190228 commit 79d2e67d6e02387f4205212fe9ff817c72880a74
@@ -14,3 +14,32 @@ tsql {
keepAliveConnection = true
}
}
// Explicit DriverDataSource using nested properties
ds1 {
driver = "slick.driver.H2Driver$"
db {
connectionPool = disabled
dataSourceClass = "slick.jdbc.DriverDataSource"
properties = {
driver = "org.h2.Driver"
url = "jdbc:h2:mem:test_ds1"
properties = {
LOCK_MODE = 1
}
}
}
}
// Implicitly created DriverDataSource for compatibility with 3.0 syntax
ds2 {
driver = "slick.driver.H2Driver$"
db {
connectionPool = disabled
driver = "org.h2.Driver"
url = "jdbc:h2:mem:test_ds2"
properties = {
LOCK_MODE = 2
}
}
}
@@ -38,10 +38,11 @@
<logger name="slick.compiler.RemoveFieldNames" level="${log.qcomp.removeFieldNames:-inherited}" />
<logger name="slick.compiler.InsertCompiler" level="${log.qcomp.insertCompiler:-inherited}" />
<logger name="slick.compiler.VerifyTypes" level="${log.qcomp.verifyTypes:-inherited}" />
<logger name="slick.jdbc.DriverDataSource" level="${log.jdbc.driver:-info}" />
<logger name="slick.jdbc.JdbcBackend.statement" level="${log.jdbc.statement:-info}" />
<logger name="slick.jdbc.JdbcBackend.benchmark" level="${log.jdbc.bench:-info}" />
<logger name="slick.jdbc.StatementInvoker.result" level="${log.jdbc.result:-info}" />
<logger name="slick.jdbcJdbcModelBuilder" level="${log.createModel:-info}" />
<logger name="slick.jdbc.JdbcModelBuilder" level="${log.createModel:-info}" />
<logger name="slick.memory.HeapBackend" level="${log.heap:-inherited}" />
<logger name="slick.memory.QueryInterpreter" level="${log.interpreter:-inherited}" />
<logger name="slick.relational.ResultConverterCompiler" level="${log.resultConverter:-inherited}" />
@@ -38,10 +38,11 @@
<logger name="slick.compiler.RemoveFieldNames" level="${log.qcomp.removeFieldNames:-inherited}" />
<logger name="slick.compiler.InsertCompiler" level="${log.qcomp.insertCompiler:-inherited}" />
<logger name="slick.compiler.VerifyTypes" level="${log.qcomp.verifyTypes:-inherited}" />
<logger name="slick.jdbc.DriverDataSource" level="${log.jdbc.driver:-info}" />
<logger name="slick.jdbc.JdbcBackend.statement" level="${log.jdbc.statement:-info}" />
<logger name="slick.jdbc.JdbcBackend.benchmark" level="${log.jdbc.bench:-info}" />
<logger name="slick.jdbc.StatementInvoker.result" level="${log.jdbc.result:-info}" />
<logger name="slick.jdbcJdbcModelBuilder" level="${log.createModel:-info}" />
<logger name="slick.jdbc.JdbcModelBuilder" level="${log.createModel:-info}" />
<logger name="slick.memory.HeapBackend" level="${log.heap:-inherited}" />
<logger name="slick.memory.QueryInterpreter" level="${log.interpreter:-inherited}" />
<logger name="slick.relational.ResultConverterCompiler" level="${log.resultConverter:-inherited}" />
@@ -0,0 +1,27 @@
package slick.test.jdbc
import org.junit.Test
import org.junit.Assert._
import slick.backend.DatabaseConfig
import slick.driver.JdbcProfile
import scala.concurrent.Await
import scala.concurrent.duration.Duration
class DataSourceTest {
@Test def testDataSourceJdbcDataSource: Unit = {
val dc = DatabaseConfig.forConfig[JdbcProfile]("ds1")
import dc.driver.api._
try {
assertEquals(1, Await.result(dc.db.run(sql"select lock_mode()".as[Int].head), Duration.Inf))
} finally dc.db.close
}
@Test def testDirectDataSource: Unit = {
val dc = DatabaseConfig.forConfig[JdbcProfile]("ds2")
import dc.driver.api._
try {
assertEquals(2, Await.result(dc.db.run(sql"select lock_mode()".as[Int].head), Duration.Inf))
} finally dc.db.close
}
}
@@ -0,0 +1,129 @@
package slick.jdbc
import scala.language.reflectiveCalls
import java.io.{PrintWriter, Closeable}
import java.sql._
import java.util.Properties
import java.util.logging.Logger
import javax.sql.DataSource
import slick.SlickException
import slick.util.{ClassLoaderUtil, Logging, ignoreFollowOnError}
import scala.beans.BeanProperty
import scala.collection.JavaConverters._
import scala.util.control.NonFatal
/** A DataSource that wraps the DriverManager API. It can be configured as a Java Bean and used
* both stand-alone and as a source for a connection pool. */
class DriverDataSource(
/** The JDBC URL (required) */
@BeanProperty @volatile var url: String,
/** Optional user name */
@BeanProperty @volatile var user: String = null,
/** Optional password */
@BeanProperty @volatile var password: String = null,
/** Optional connection properties */
@BeanProperty @volatile var properties: Properties = null,
/** Name of the `java.sql.Driver` class. This must be set unless a `driverObject` is set
* directly or the driver is already registered with the DriverManager. */
@BeanProperty @volatile var driverClassName: String = null,
/** When `close()` is called, try to deregister a driver that was registered by this instance. */
@BeanProperty @volatile var deregisterDriver: Boolean = false,
/** The JDBC driver to use. If this is set, `driverClassName` will be ignored. */
@volatile var driverObject: Driver = null,
/** The ClassLoader that is used to load `driverClassName` */
@volatile var classLoader: ClassLoader = ClassLoaderUtil.defaultClassLoader
) extends DataSource with Closeable with Logging {
def this() = this(null)
// Alias for `driverClassName`
def getDriver: String = driverClassName
def setDriver(s: String): Unit = driverClassName = s
// State that gets initialized by `init`
@volatile private[this] var registered: Boolean = false
@volatile private[this] var initialized = false
private[this] var driver: Driver = _
private[this] var connectionProps: Properties = _
def init: Unit = if(!initialized) {
try {
initialized = true
if(url eq null) throw new SQLException("Required parameter \"url\" missing in DriverDataSource")
driver = if(driverObject eq null) {
if(driverClassName ne null) {
DriverManager.getDrivers.asScala.find(_.getClass.getName == driverClassName).getOrElse {
logger.debug(s"Driver $driverClassName not already registered; trying to load it")
val cl = classLoader.loadClass(driverClassName)
registered = true
DriverManager.getDrivers.asScala.find(_.getClass.getName == driverClassName).getOrElse {
logger.debug(s"Loaded driver $driverClassName but it did not register with DriverManager; trying to instantiate directly")
try cl.newInstance.asInstanceOf[Driver] catch { case ex: Exception =>
logger.debug(s"Instantiating driver class $driverClassName failed; asking DriverManager to handle URL $url", ex)
try DriverManager.getDriver(url) catch { case ex: Exception =>
throw new SlickException(s"Driver $driverClassName does not know how to handle URL $url", ex)
}
}
}
}
} else try DriverManager.getDriver(url) catch { case ex: Exception =>
throw new SlickException(s"No driver specified and DriverManager does not know how to handle URL $url", ex)
}
} else driverObject
if(!driver.acceptsURL(url)) {
close()
throw new SlickException(s"Driver ${driver.getClass.getName} does not know how to handle URL $url")
}
connectionProps = propsWithUserAndPassword(properties, user, password)
} catch { case NonFatal(ex) =>
try close() catch ignoreFollowOnError
throw ex
} finally initialized = true
}
private[this] def propsWithUserAndPassword(p: Properties, user: String, password: String): Properties = {
if((p ne null) && (user eq null) && (password eq null)) p else {
val p2 = new Properties(p)
if(user ne null) p2.setProperty("user", user)
if(password ne null) p2.setProperty("password", password)
p2
}
}
def getConnection: Connection = {
init
driver.connect(url, connectionProps)
}
def getConnection(username: String, password: String): Connection = {
init
driver.connect(url, propsWithUserAndPassword(connectionProps, username, password))
}
def close(): Unit = if(registered && deregisterDriver && (driver ne null)) {
DriverManager.deregisterDriver(driver)
registered = false
}
def getLoginTimeout: Int = DriverManager.getLoginTimeout
def setLoginTimeout(seconds: Int): Unit = DriverManager.setLoginTimeout(seconds)
def getLogWriter: PrintWriter = throw new SQLFeatureNotSupportedException()
def setLogWriter(out: PrintWriter): Unit = throw new SQLFeatureNotSupportedException()
def getParentLogger: Logger = {
try driver.asInstanceOf[{ def getParentLogger(): Logger }].getParentLogger
catch { case _: NoSuchMethodException => throw new SQLFeatureNotSupportedException() }
}
def isWrapperFor(iface: Class[_]): Boolean = iface.isInstance(this)
def unwrap[T](iface: Class[T]): T =
if(iface.isInstance(this)) this.asInstanceOf[T]
else throw new SQLException(getClass.getName+" is not a wrapper for "+iface)
}
Oops, something went wrong.

0 comments on commit 79d2e67

Please sign in to comment.