I set up a github repo for this question to provide as much of the boiled down environment as possible.
My goal is to set up ebean ORM for database manangement from a Paper Minecraft plugin. I'm able to shade in the ebean dependencies, but creating a query using "io.ebean:ebean-querybean:" throws an error saying that it cannot find an implementation of SpiRawSqlService.
Paper Minecraft: paper-1.19.3-367.jar
Java 18
Ebean enhancement plugin for IntelliJ(I checked that I have it enabled for this project)
io.ebean gradle plugin version 13.10.0
shadowJar gradle plugin version 7.1.2
Everything is fine setting up the database, and saving to the Database. Queries without using a querybean work fine as well. The error is thrown when initializing any class containing a reference to a generated querybean.
The error outputted is printed the latest.log
Caused by: java.lang.IllegalStateException: No service implementation found for interface org.example.ebean.io.ebean.service.SpiRawSqlService
The stacktrace tells us that it couldn't find org.example.ebean.io.ebean.service.SpiRawSqlService
.
Looking at the decompiled shadowJar after package relocation, the implementation for this class is found at org.example.ebean.io.ebeaninternal.server.rawsql.DRawSql
;
Printing out the ClassLoader#getDefinedPackages on the instance supplied when creating the ebean Database connection results in this:
org.example.ebean
org.example.ebean.database
org.example.ebean.io.ebean
org.example.ebean.io.ebean.annotation
org.example.ebean.io.ebean.config
org.example.ebean.io.ebean.config.dbplatform
org.example.ebean.io.ebean.datasource
org.example.ebean.io.ebean.meta
As you can see, the org.example.ebean.io.ebeaninternal
package and subpackages are not outputted in this list.
How/where is the package "ebeaninternal" being loaded if at all? How can I get the enhanced querybean to find this package so it can load the implementation (DRawSql) of SpiRawSqlService?
Bukkit's @EventHandler utilizes a different contextClassLoader than the ClassLoader that loads the ebean classes/services (contained in the ShadowJar).
The error states No service implementation found
because the thread that is initializing the querybean does not have access to that class.
The solution here is to use Thread#setContextClassLoader()
to use the same ClassLoader used when calling DatabaseFactory.createWithContextClassLoader()
. Set the ClassLoader, initialize every Class that uses a QueryBean, revert the ClassLoader to what it originally was.
EBean might be able to solve this problem. But for now, a fix is to just call an empty init method on every class that becomes an Enhanced-QueryBean from a thread that is using the proper ContextClassLoader
I pushed the full example containing the fix to the original github repo
public static void load() {
DataSourceConfig dataSourceConfig = configureDataSource();
DatabaseConfig dbConfig = configureDatabase(dataSourceConfig);
// We should use the classloader that loaded this plugin
// because this plugin has our ebean dependencies
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader pluginClassLoader = BukkitEBeanPlugin.class.getClassLoader();
// create the DatabaseFactory with the classloader containing ebean dependencies
DatabaseFactory.createWithContextClassLoader(dbConfig, pluginClassLoader);
// Set the current thread's contextClassLoader to the classLoader with the ebean dependencies
// This allows the class to initialize itself with access to the required class dependencies
Thread.currentThread().setContextClassLoader(pluginClassLoader);
// invoke the static initialization of every class that contains a querybean.
// Note that any method in the class will initialize the class.
FindByQueryBean.init();
// Restore the contextClassLoader to what it was originally
Thread.currentThread().setContextClassLoader(originalClassLoader);
BukkitEBeanPlugin.get().getLogger().info("Successfully created database");
}
...
}
public static void init() {
// intentionally empty
}