How can I override Play's default Ebean server

2020-07-09 08:51发布

问题:

I don't want to define the default database configuration in the application.conf file. Instead, I want to construct the default EbeanServer programmatically and inject it myself into the DAO.

The problem I am having is, if I create a guice binding for a EbeanServer provider and not define any configuration in the application.conf file, play errors out saying it cannot find a default configuration.

Here's the code I have:

public class EbeanServerProvider implements Provider<EbeanServer> {

    final Logger.ALogger log = Logger.of(this.getClass());

    @Override
    public EbeanServer get() {

        ServerConfig serverConfig = new ServerConfig();
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        Config dbConfig = ConfigFactory.load(classLoader,"env/default.conf");

        /* Read the config files */
        final String DB_DRIVER   = dbConfig.getString("db.default.driver");
        final String DB_URL      = dbConfig.getString("db.default.url");
        final String DB_USERNAME = dbConfig.getString("db.default.username");
        final String DB_PASSWORD = dbConfig.getString("db.default.password");

        log.debug("{}",DB_DRIVER);
        log.debug("{}",DB_URL);
        log.debug("{}",DB_USERNAME);
        log.debug("{}",DB_PASSWORD);
        /* Load the database driver */
        dataSourceConfig.setDriver(DB_DRIVER);
        try{
            Class.forName(DB_DRIVER).newInstance();
        }
        catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            log.error("Unable to load database driver:{}",DB_DRIVER);
            throw new WrappedServerError(InternalErrorCode.TA_ERROR,"Failed to load database driver",e);
        }

        /* Set the data source configs */
        dataSourceConfig.setUrl(DB_URL);
        dataSourceConfig.setUsername(DB_USERNAME);
        dataSourceConfig.setPassword(DB_PASSWORD);
        dataSourceConfig.setCaptureStackTrace(true);


        serverConfig.setDataSourceConfig(dataSourceConfig);
        serverConfig.setName("mysql");
        serverConfig.setDefaultServer(true);
        serverConfig.setDdlGenerate(false);
        serverConfig.setDdlRun(false);
        serverConfig.setRegister(true);

        EbeanServer dbServer=null;
        try {
            dbServer = EbeanServerFactory.create(serverConfig);
        }
        catch (Exception e){
            throw new WrappedServerError(InternalErrorCode.TA_INIT_ERROR,"Failed to create ebean server",e);
        }

        return dbServer;
    }
}

application.conf:

# Ebean configuration
ebean.default = ["com.giraffe.models.*"]

guice module binding

    //Ebean server
  bind(EbeanServer.class).toProvider(EbeanServerProvider.class).asEagerSingleton();

Error:

play.api.UnexpectedException: Unexpected exception[CreationException: Unable to create injector, see the following errors:

1) Error in custom provider, Configuration error: Configuration error[null]
  while locating play.db.ebean.DefaultEbeanConfig$EbeanConfigParser
  at play.db.ebean.EbeanModule.bindings(EbeanModule.java:24):
Binding(interface play.db.ebean.EbeanConfig to ProviderConstructionTarget(class play.db.ebean.DefaultEbeanConfig$EbeanConfigParser) eagerly) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
  while locating play.db.ebean.EbeanConfig
    for parameter 0 at play.db.ebean.EbeanDynamicEvolutions.<init>(EbeanDynamicEvolutions.java:36)
  at play.db.ebean.EbeanDynamicEvolutions.class(EbeanDynamicEvolutions.java:33)
  while locating play.db.ebean.EbeanDynamicEvolutions
  at play.db.ebean.EbeanModule.bindings(EbeanModule.java:23):
Binding(class play.api.db.evolutions.DynamicEvolutions to ConstructionTarget(class play.db.ebean.EbeanDynamicEvolutions) eagerly) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
  while locating play.api.db.evolutions.DynamicEvolutions
Caused by: Configuration error: Configuration error[null]
    at play.api.Configuration$.configError(Configuration.scala:178)
    at play.api.Configuration.reportError(Configuration.scala:829)
    at play.Configuration.reportError(Configuration.java:351)
    at play.db.ebean.DefaultEbeanConfig$EbeanConfigParser.parse(DefaultEbeanConfig.java:81)
    at play.db.ebean.DefaultEbeanConfig$EbeanConfigParser.get(DefaultEbeanConfig.java:60)
    at play.db.ebean.DefaultEbeanConfig$EbeanConfigParser.get(DefaultEbeanConfig.java:44)
    at com.google.inject.internal.ProviderInternalFactory.provision(ProviderInternalFactory.java:81)
    at com.google.inject.internal.BoundProviderFactory.provision(BoundProviderFactory.java:72)
    at com.google.inject.internal.ProviderInternalFactory.circularGet(ProviderInternalFactory.java:61)
    at com.google.inject.internal.BoundProviderFactory.get(BoundProviderFactory.java:62)
    at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46)
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103)
    at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40)

回答1:

I was able to solve the problem after following the hint from @BatteryAcid:

  1. Separate the ebean enhancement from ebean default server instantiation. To do this i removed the 'ebean.default = ['models.*]' setting from application.conf file. I added the following to build.sbt.

build.sbt

playEbeanModels in Compile := Seq("models.*")
playEbeanDebugLevel := 4

This ensures that your models are enhanced.

  1. For server instantiation i kept my EbeanServerProvider class

EbeanServerProvider.java

public class EbeanServerProvider implements Provider<EbeanServer> {

    final Logger.ALogger log = Logger.of(this.getClass());

    @Override
    public EbeanServer get() {

        ServerConfig serverConfig = new ServerConfig();
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

        Config dbConfig = ConfigFactory.load(classLoader,"env/default.conf");

        /* Read the config files */
        final String DB_DRIVER   = dbConfig.getString("db.default.driver");
        final String DB_URL      = dbConfig.getString("db.default.url");
        final String DB_USERNAME = dbConfig.getString("db.default.username");
        final String DB_PASSWORD = dbConfig.getString("db.default.password");

        log.debug("{}",DB_DRIVER);
        log.debug("{}",DB_URL);
        log.debug("{}",DB_USERNAME);
        log.debug("{}",DB_PASSWORD);
        /* Load the database driver */
        dataSourceConfig.setDriver(DB_DRIVER);
        try{
            Class.forName(DB_DRIVER).newInstance();
        }
        catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
            log.error("Unable to load database driver:{}",DB_DRIVER);
            throw new WrappedServerError(InternalErrorCode.TA_ERROR,"Failed to load database driver",e);
        }

        /* Set the data source configs */
        dataSourceConfig.setUrl(DB_URL);
        dataSourceConfig.setUsername(DB_USERNAME);
        dataSourceConfig.setPassword(DB_PASSWORD);
        dataSourceConfig.setCaptureStackTrace(true);


        serverConfig.setDataSourceConfig(dataSourceConfig);
        serverConfig.setName("mysql");
        serverConfig.setDefaultServer(true);
        serverConfig.setDdlGenerate(false);
        serverConfig.setDdlRun(false);
        serverConfig.setRegister(true);

        /* Add the models to the package */
        serverConfig.addClass(<your model class>);

        EbeanServer dbServer=null;
        try {
            dbServer = EbeanServerFactory.create(serverConfig);
        }
        catch (Exception e){
            throw new WrappedServerError(InternalErrorCode.TA_INIT_ERROR,"Failed to create ebean server",e);
        }

        return dbServer;
    }
}

The thing to note here is the serverConfig.addClass method. You will have to register all your model classes this way. Which means if you have 10 models, you will have 10 such calls to serverConfig.addClass. Which is stupid.

There is a serverConfig.addPackage method, which supposedly can read all the models in a given package and register it with EbeanServer. But it did not work me. Since I currently have only a few models this solution works for now.

Guice binding

//Ebean server
 bind(EbeanServer.class).toProvider(EbeanServerProvider.class).asEagerSingleton();

If any of you reading this has a better solution, please do post it here.



回答2:

UPDATE

I was able to get around your exception by removing the ebean.default from the application.conf and using this get() in EbeanServerProvider. The only problem is that I couldn't get ebean to scan a whole package, so I manually listed out my model classes (Configuration) - that would be a pain but at least I've narrowed it down.

    @Override
    public EbeanServer get() {
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        /* Set the data source configs */
        dataSourceConfig.setDriver("com.mysql.jdbc.Driver");
        dataSourceConfig.setUrl("jdbc:mysql://localhost/my_db");
        dataSourceConfig.setUsername("username");
        dataSourceConfig.setPassword("password");

        ServerConfig serverConfig = new ServerConfig();
        serverConfig.setName("default");
        serverConfig.setRegister(true);
        serverConfig.setDefaultServer(true);
        serverConfig.setDataSourceConfig(dataSourceConfig);
        serverConfig.setDatabasePlatform(new com.avaje.ebean.config.dbplatform.MySqlPlatform());

        //how to add a whole package?
        serverConfig.addClass(Configuration.class);

        return EbeanServerFactory.create(serverConfig);
    }

----- You can probably ignore the below ---

Take a look at this, I haven't tested it myself but maybe it will get you pointed in the right direction.

According to the Play 2.4 docs you can implement ServerConfigStartup to customize an ebean server before it starts:

To customise the underlying Ebean Server configuration, you can either add a conf/ebean.properties file, or create an instance of the ServerConfigStartup interface to programmatically manipulate the Ebean ServerConfig before the server is initialised. https://www.playframework.com/documentation/2.4.x/JavaEbean#Configuring-the-runtime-library

import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.event.ServerConfigStartup;

public class MyServerConfigStartup implements ServerConfigStartup {

    public void onStart(ServerConfig serverConfig) {
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        /* Set the data source configs */
        dataSourceConfig.setUrl(DB_URL);
        dataSourceConfig.setUsername(DB_USERNAME);
        dataSourceConfig.setPassword(DB_PASSWORD);
        dataSourceConfig.setCaptureStackTrace(true);

        serverConfig.setDataSourceConfig(dataSourceConfig);
    }
}