I have written a Scala+AKKA api using Scala 3. I then wanted to use Hibernate ORM to store data on the backend. I followed this guide: https://docs.jboss.org/hibernate/orm/6.6/introduction/html_single/Hibernate_Introduction.html#introduction to get a basic example to being expanding from. I substituted the H2 implementation in the guide for MySQL due to memory requirements.
When I try to initiate the database, I get the following exception:
[error] java.lang.ExceptionInInitializerError
[error] at ServerRoutes.$init$$$anonfun$1$$anonfun$1$$anonfun$1$$anonfun$1(ServerRoutes.scala:34)
[error] at akka.http.scaladsl.server.util.ApplyConverterInstances.akka$http$scaladsl$server$util$ApplyConverterInstances$$anon$1$$_$apply$$anonfun$1(ApplyConverterInstances.scala:14)
[error] at akka.http.scaladsl.server.ConjunctionMagnet$$anon$3.apply$$anonfun$1$$anonfun$1$$anonfun$1(Directive.scala:234)
...
[error] Caused by: org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment] due to: java.lang.RuntimeException: Unable to get java.sql.Driver from DriverManager
...
[error] Caused by: java.lang.RuntimeException: Unable to get java.sql.Driver from DriverManager
[error] at io.agroal.pool.ConnectionFactory.newDriver(ConnectionFactory.java:130)
[error] at io.agroal.pool.ConnectionFactory.<init>(ConnectionFactory.java:68)
[error] at io.agroal.pool.ConnectionPool.<init>(ConnectionPool.java:112)
[error] at io.agroal.pool.DataSource.<init>(DataSource.java:37)
[error] at io.agroal.pool.DataSourceProvider.getDataSource(DataSourceProvider.java:21)
[error] at io.agroal.api.AgroalDataSource.from(AgroalDataSource.java:41)
[info] at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.configureService(StandardServiceRegistryImpl.java:136)
[info] at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(AbstractServiceRegistryImpl.java:247)
[info] at org.hibernate.service.internal.AbstractServiceRegistryImpl.getService(AbstractServiceRegistryImpl.java:215)
[info] at org.hibernate.service.ServiceRegistry.requireService(ServiceRegistry.java:68)
[info] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.buildJdbcConnectionAccess(JdbcEnvironmentInitiator.java:434)
[error] at io.agroal.api.AgroalDataSource.from(AgroalDataSource.java:33)
[error] at org.hibernate.agroal.internal.AgroalConnectionProvider.configure(AgroalConnectionProvider.java:93)
[error] ... 78 more
[error] Caused by: java.sql.SQLException: No suitable driver
[error] at java.sql/java.sql.DriverManager.getDriver(DriverManager.java:300)
[error] at io.agroal.pool.ConnectionFactory.newDriver(ConnectionFactory.java:128)
[error] ... 85 more
[info] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.getJdbcEnvironmentUsingJdbcMetadata(JdbcEnvironmentInitiator.java:305)
[info] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:129)
[info] at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:81)
[info] at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:130)
[info] at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263)
I've looked around at why this could be happening and i've seen several reports about the driver not being on the classpath. However, from what I can tell, it absolutely should be.
build.sbt dependencies:
libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.5.12",
"com.thesamet.scalapb" %% "scalapb-runtime" % scalapb.compiler.Version.scalapbVersion % "protobuf",
"com.thesamet.scalapb" %% "scalapb-json4s" % "0.12.1",
"commons-codec" % "commons-codec" % "1.17.1",
"com.typesafe.akka" %% "akka-http-testkit" % akkaHttpVersion % Test,
"com.typesafe.akka" %% "akka-actor-testkit-typed" % akkaVersion % Test,
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
"org.hibernate.orm" % "hibernate-core" % "6.6.3.Final",
"org.hibernate.validator" % "hibernate-validator" % "8.0.0.Final",
"org.glassfish" % "jakarta.el" % "5.0.0-M1" % Test,
"io.agroal" % "agroal-pool" % "2.5",
"org.hibernate.orm" % "hibernate-agroal" % "6.4.4.Final",
"com.mysql" % "mysql-connector-j" % "9.1.0",
"com.lihaoyi" %% "upickle" % "4.0.2"
)
And finally, the section of code creating the database:
def init() : Unit = {
sessionFactory = new Configuration()
.addAnnotatedClass(classOf[Registration])
.setProperty("driver", "com.mysql.cj.jdbc.Driver")
.setProperty("url", "jdbc:mysql://localhost:3306/hydra_data")
.setProperty("user", "root")
.setProperty("password", "")
// use Agroal connection pool
.setProperty("hibernate.agroal.maxSize", "20")
// display SQL in console
.setProperty("show_sql", "true")
.setProperty("format_sql", "true")
.setProperty("highlight_sql", "true")
.buildSessionFactory()
sessionFactory.getSchemaManager.exportMappedObjects(true)
...
}
Any suggestions? I presume (please correct me if i'm wrong) that as I have the mysql-connector-j dependency, that the Driver should be on the classpath. So I'm really unsure what the problem is here? This happens when I run 'sbt run' or run through the IDE
Java Version:OpenJDK 64-Bit Server VM (build 23.0.1+11-39, mixed mode, sharing)
Scala version: 3.6.2
Sbt version: 1.10.6
UPDATE:
As suggested by @GaëlJ I tried the following:
val sqlDriver = classOf[com.mysql.cj.jdbc.Driver]
This works fine. I also found this post: How to use MySQL JDBC driver in an SBT Scala project?
and tried adding this to my database init
function
classOf[com.mysql.cj.jdbc.Driver].getDeclaredConstructor().newInstance()
but I get the same error that the DriverManager could not load the driver.
UPDATE 2:
I followed some of the links to the internal Hibernate files in the error output. I found that the ConnectionFactory
class calls DriverManager.getDriver
and passes the URL defined in the config to the function to determine which Driver to use.
So I did this manually and I get the correct response. So why is this failing internally? Is this a bug perhaps?
UPDATE 3:
Have submitted a potential bug report: https://hibernate.atlassian.net/browse/HHH-18934
UPDATE 4:
I decided today to debug through the Hibernate initialisation code to see what exactly was going wrong.
I tracked the issue into the ConnectioFactory
constructor. When it tries to request the driver, it accesses configuration.jdbcUrl()
, which is null. So it runs DriverManager.getDriver(null)
, which throws the error when I ran DriverManager.getDriver(null)
manually:
Which trickles up the Hibernate exception handling and comes out with the original error (it is actually the bottom exception of the entire original error).
I have updated the bug report. It seems the connection URL is not correctly being placed into the Agroal configuration when it creates the service and tries to instantiate it.
I think I have found the issue. The problem was with my configuration and the Hibernate starter guide. This might save some other people some time.
The starter guide has references to URL
, USER
variables when setting the config, which from the imports, suggests these are available from:
org.hibernate.cfg.AvailableSettings.*
Which might be true for Java. However, they are actually located in an inherited class:
org.hibernate.cfg.JdbcSettings
So in Scala:
import org.hibernate.cfg.JdbcSettings._
This then allowed me to set my config like this:
val config = new Configuration()
.addAnnotatedClass(classOf[Registration])
.setProperty(JAKARTA_JDBC_URL, getConnectionString)
.setProperty(JAKARTA_JDBC_USER, user)
.setProperty(JAKARTA_JDBC_PASSWORD, pw)
// use Agroal connection pool
.setProperty("hibernate.agroal.maxSize", "20")
// display SQL in console
.setProperty(SHOW_SQL, "true")
.setProperty(FORMAT_SQL, "true")
.setProperty(HIGHLIGHT_SQL, "true")
Another note, the documentation is actually out of date. URL
, USER
and PASS
are deprecated, you must use JAKARTA_JDBC_*
(see above config) to set the values.
This fixed the above issue, so I'm marking this as resolved. I am now getting issues connecting to the database, which is outside the scope of the original question. I hope (given I found several people online having the same problems), this saves someone some time.