Issue
I have not used this traditional way to deploy wars to a tomcat in the past years.
In these days, I tried to update myself to Spring 6/Jakarta EE 9/Java 17 and created a simple example to demo a Spring WebMvc war application, when deploying to a Tomcat 10 via cargo maven plugin or manually , I got the famous no suitable drivers at the application startup stage when hibernate is initialized.
But it worked in unit tests.
I've also tried to deploy to an external Tomcat server, firstly copy a postgres jar to the tomcat/lib folder, and then copy the war package to the tomcat/webapps folder, and start it manually, but got the same errors.
My example project: https://github.com/hantsy/spring6-sandbox/tree/master/jpa , based on the following tech stack:
- Jakarta EE 9/Java 17
- Spring 6.0.0-M1
- Hibernate 5.6 jakarta variants/Postgres Jdbc Driver 42.3.1
- Apache Tomcat 10.0.14
To reproduce the issues, start Postgres via docker compose up postgres
and run mvn clean package cargo:run -Ptomcat
in the project folder to deploy to the cargo managed tomcat server.
PS: I also configured Jetty and WildFly, both worked well.
Update: This issue is a little weird, because packaging Postgres jdbc driver into war worked well with Spring5/Tomcat9. But it does not work with Spring6/Tomcat10, I have to exclude the pg Jdbc driver from the war package, and copy it to the tomcat/lib folder, see the integration tests for it.
BTW, Tomcat was preferred for Java developers, one reason was most of the popular Jdbc drivers worked well when packaging it into wars,in contrast most of the traditional application servers did not support it in the past years(you have to install it to application server via the application server command line tools or admin console). But since Java EE 7, it allows developers to define application-scoped DataSource
s(via @DataSourceDefinition
) and package Jdbc drivers into the application war packages directly, when the application is being deployed, it will register the Jdbc drivers automatically. Not sure why the newest Tomcat increases the development complexity and uses the legacy application server approach.
Solution
Short answer: in your DataSourceConfig
class you don't call DriverManagerDataSource#setDriverClassName
, hence the DriverManager
does not know where to find a suitable driver for a jdbc:postgresql:
URI.
Long answer: wait, do I really need to specify the driver's class name, aren't JDBC drivers autodetected? Yes, since Java 6 and JDBC 4, JDBC drivers are automatically detected through the ServiceLoader
mechanism.
However this happens only once during the first call to DriverManager.getDrivers()
. Tomcat makes sure this happens from the server's classpath (cf. JreMemoryLeakPrevention
), so only the drivers installed globally are registered (no references to application classloaders are held by the system classloader).
Therefore, instead of specifying the driver's class name, you can:
- either install the PostgreSQL JDBC driver in the server's classpath (e.g.
$CATALINA_BASE/lib
) and remove it from the application, - or make sure the driver is loaded when your application starts (and deregistered when it stops):
Class.forName("org.postgresql.Driver");
- you can also emulate the auto-discovery mechanism used by
DriverManager
to register all the drivers distributed with your application:private static void registerJdbcDrivers(ServletContext context) { final ServiceLoader<Driver> serviceLoader = ServiceLoader.load(Driver.class, context.getClassLoader()); final Iterator<Driver> iter = serviceLoader.iterator(); while (iter.hasNext()) { // Just for the side-effect of loading the class and registering the driver iter.next(); } }
Answered By - Piotr P. Karwasz