javascalahibernateormmysql-connector

Why is my Scala project failing to find java.sql.Driver?


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?

enter image description here

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:

enter image description here

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.


Solution

  • 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.