Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot create and properly instantiate an eagerly() bound DI component when dependent on Ebean and Evolutions running. #65

Open
justinnichols opened this issue Nov 18, 2015 · 18 comments

Comments

@justinnichols
Copy link

justinnichols commented Nov 18, 2015

Here is what is happening and maybe I can get some guidance on this. I have read through and tried what was suggested in #51 without any solution.

I created a github project to demonstrate the issue. It is here: https://github.com/justinnichols/broken-play-ebean-di

EDIT>> I have migrated away from using Github. I now have the project here: https://gitlab.com/justinnichols/broken-play-ebean-di

The crux of the problem is that when using Ebean dynamic evolutions, the DI module, when eagerly() started, may or may not be attempted to be instantiated after the evolutions take place. It causes the following issue when starting (randomly, sometimes it works, sometimes it doesn't):

$ activator run
[info] Loading project definition from /home/justin/src/github/justinnichols/broken-play-ebean-di/project
[info] Set current project to broken-play-ebean-di (in build file:/home/justin/src/github/justinnichols/broken-play-ebean-di/)

--- (Running the application, auto-reloading is enabled) ---

[info] p.c.s.NettyServer - Listening for HTTP on /0:0:0:0:0:0:0:0:9000

(Server started, use Ctrl+D to stop and go back to the console...)

[info] - application - Creating Pool for datasource 'default'
[info] - play.api.db.DefaultDBApi - Database [default] connected at jdbc:mysql://localhost/testdb?useUnicode=yes&characterEncoding=UTF-8&connectionCollation=utf8_general_ci
[error] - application - 

! @6o6pp4o27 - Internal server error, for (GET) [/] ->

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

1) Error injecting constructor, javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Table 'testdb.log' doesn't exist]
  at components.BrokenPlayEbeanDIComponentImpl.<init>(BrokenPlayEbeanDIComponentImpl.java:15)
  at components.BrokenPlayEbeanDIComponentImpl.class(BrokenPlayEbeanDIComponentImpl.java:15)
  while locating components.BrokenPlayEbeanDIComponentImpl
  at modules.BrokenPlayEbeanDIModule.bindings(BrokenPlayEbeanDIModule.java:17):
Binding(interface components.BrokenPlayEbeanDIComponent to ConstructionTarget(class components.BrokenPlayEbeanDIComponentImpl) eagerly) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
  while locating components.BrokenPlayEbeanDIComponent

1 error]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:165) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:121) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at scala.Option.map(Option.scala:146) ~[scala-library-2.11.7.jar:na]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1.apply(DevServerStart.scala:121) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1.apply(DevServerStart.scala:119) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at scala.util.Success.flatMap(Try.scala:231) ~[scala-library-2.11.7.jar:na]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1.apply(DevServerStart.scala:119) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1.apply(DevServerStart.scala:111) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at scala.concurrent.impl.Future$PromiseCompletingRunnable.liftedTree1$1(Future.scala:24) ~[scala-library-2.11.7.jar:na]
    at scala.concurrent.impl.Future$PromiseCompletingRunnable.run(Future.scala:24) ~[scala-library-2.11.7.jar:na]
    at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402) ~[na:1.8.0_66]
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289) ~[na:1.8.0_66]
    at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056) ~[na:1.8.0_66]
    at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692) ~[na:1.8.0_66]
    at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157) ~[na:1.8.0_66]
Caused by: com.google.inject.CreationException: Unable to create injector, see the following errors:

1) Error injecting constructor, javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Table 'testdb.log' doesn't exist]
  at components.BrokenPlayEbeanDIComponentImpl.<init>(BrokenPlayEbeanDIComponentImpl.java:15)
  at components.BrokenPlayEbeanDIComponentImpl.class(BrokenPlayEbeanDIComponentImpl.java:15)
  while locating components.BrokenPlayEbeanDIComponentImpl
  at modules.BrokenPlayEbeanDIModule.bindings(BrokenPlayEbeanDIModule.java:17):
Binding(interface components.BrokenPlayEbeanDIComponent to ConstructionTarget(class components.BrokenPlayEbeanDIComponentImpl) eagerly) (via modules: com.google.inject.util.Modules$OverrideModule -> play.api.inject.guice.GuiceableModuleConversions$$anon$1)
  while locating components.BrokenPlayEbeanDIComponent

1 error
    at com.google.inject.internal.Errors.throwCreationExceptionIfErrorsExist(Errors.java:466) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:184) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.build(InternalInjectorCreator.java:110) ~[guice.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:96) ~[guice.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:73) ~[guice.jar:na]
    at com.google.inject.Guice.createInjector(Guice.java:62) ~[guice.jar:na]
    at play.api.inject.guice.GuiceBuilder.injector(GuiceInjectorBuilder.scala:126) ~[play_2.11-2.4.3.jar:2.4.3]
    at play.api.inject.guice.GuiceApplicationBuilder.build(GuiceApplicationBuilder.scala:93) ~[play_2.11-2.4.3.jar:2.4.3]
    at play.api.inject.guice.GuiceApplicationLoader.load(GuiceApplicationLoader.scala:21) ~[play_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1$$anonfun$2.apply(DevServerStart.scala:153) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1$$anonfun$2.apply(DevServerStart.scala:150) ~[play-server_2.11-2.4.3.jar:2.4.3]
    at play.utils.Threads$.withContextClassLoader(Threads.scala:21) ~[play_2.11-2.4.3.jar:2.4.3]
    at play.core.server.DevServerStart$$anonfun$mainDev$1$$anon$1$$anonfun$get$1$$anonfun$apply$1$$anonfun$1.apply(DevServerStart.scala:150) ~[play-server_2.11-2.4.3.jar:2.4.3]
    ... 14 common frames omitted
Caused by: javax.persistence.PersistenceException: ERROR executing DML bindLog[] error[Table 'testdb.log' doesn't exist]
    at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:90) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.insert(DmlBeanPersister.java:57) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.DefaultPersistExecute.executeInsertBean(DefaultPersistExecute.java:59) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeNow(PersistRequestBean.java:446) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.core.PersistRequestBean.executeOrQueue(PersistRequestBean.java:480) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:261) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:211) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.DefaultPersister.save(DefaultPersister.java:199) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1461) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1454) ~[avaje-ebeanorm-4.6.2.jar:na]
    at dao.LogDaoImpl.create(LogDaoImpl.java:26) ~[na:na]
    at components.BrokenPlayEbeanDIComponentImpl.<init>(BrokenPlayEbeanDIComponentImpl.java:16) ~[na:na]
    at components.BrokenPlayEbeanDIComponentImpl$$FastClassByGuice$$1a98aa44.newInstance(<generated>) ~[na:na]
    at com.google.inject.internal.cglib.reflect.$FastConstructor.newInstance(FastConstructor.java:40) ~[guice.jar:na]
    at com.google.inject.internal.DefaultConstructionProxyFactory$1.newInstance(DefaultConstructionProxyFactory.java:61) ~[guice.jar:na]
    at com.google.inject.internal.ConstructorInjector.provision(ConstructorInjector.java:105) ~[guice.jar:na]
    at com.google.inject.internal.ConstructorInjector.construct(ConstructorInjector.java:85) ~[guice.jar:na]
    at com.google.inject.internal.ConstructorBindingImpl$Factory.get(ConstructorBindingImpl.java:267) ~[guice.jar:na]
    at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46) ~[guice.jar:na]
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103) ~[guice.jar:na]
    at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40) ~[guice.jar:na]
    at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:145) ~[guice.jar:na]
    at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41) ~[guice.jar:na]
    at com.google.inject.internal.FactoryProxy.get(FactoryProxy.java:56) ~[guice.jar:na]
    at com.google.inject.internal.ProviderToInternalFactoryAdapter$1.call(ProviderToInternalFactoryAdapter.java:46) ~[guice.jar:na]
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1103) ~[guice.jar:na]
    at com.google.inject.internal.ProviderToInternalFactoryAdapter.get(ProviderToInternalFactoryAdapter.java:40) ~[guice.jar:na]
    at com.google.inject.internal.SingletonScope$1.get(SingletonScope.java:145) ~[guice.jar:na]
    at com.google.inject.internal.InternalFactoryToProviderAdapter.get(InternalFactoryToProviderAdapter.java:41) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:205) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator$1.call(InternalInjectorCreator.java:199) ~[guice.jar:na]
    at com.google.inject.internal.InjectorImpl.callInContext(InjectorImpl.java:1092) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.loadEagerSingletons(InternalInjectorCreator.java:199) ~[guice.jar:na]
    at com.google.inject.internal.InternalInjectorCreator.injectDynamically(InternalInjectorCreator.java:180) ~[guice.jar:na]
    ... 25 common frames omitted
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'testdb.log' doesn't exist
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_66]
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_66]
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_66]
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422) ~[na:1.8.0_66]
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:400) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.Util.getInstance(Util.java:383) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:980) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3847) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3783) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2447) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2594) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2545) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1901) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2113) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2049) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2034) ~[mysql-connector-java-5.1.36.jar:5.1.36]
    at com.zaxxer.hikari.proxy.PreparedStatementProxy.executeUpdate(PreparedStatementProxy.java:61) ~[HikariCP.jar:na]
    at com.zaxxer.hikari.proxy.PreparedStatementJavassistProxy.executeUpdate(PreparedStatementJavassistProxy.java) ~[HikariCP.jar:na]
    at com.avaje.ebeaninternal.server.type.DataBind.executeUpdate(DataBind.java:56) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.dml.InsertHandler.execute(InsertHandler.java:128) ~[avaje-ebeanorm-4.6.2.jar:na]
    at com.avaje.ebeaninternal.server.persist.dml.DmlBeanPersister.execute(DmlBeanPersister.java:80) ~[avaje-ebeanorm-4.6.2.jar:na]
    ... 58 common frames omitted

Notice the Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'testdb.log' doesn't exist

If I immediately re-run the application, it works perfectly (because that table now exists).

Please if at all possible, can I get some guidance on how to make it work so that I can use Ebean in an eagerly() instantiated component with evolutions turned on?

@justinnichols
Copy link
Author

Anyone have any insight on this?

@justinnichols
Copy link
Author

From what I can tell, after upgrading another application to Play 2.5.3 and play-ebean 3.0.1, this seems fixed. I would like it if someone could independently verify before closing this, but as I have time I will attempt to update my sample project linked above to Play 2.5.3 to see if it is also fixed.

@justinnichols
Copy link
Author

It seems I also had to inject the following into my module to not have a "Datasource is null?" exception during boot:

play.db.ebean.EbeanDynamicEvolutions dynamicEvolutions,
play.db.ebean.EbeanConfig ebeanConfig

@justinnichols
Copy link
Author

So it looks like this is still an issue even in Play 2.5.3. I have an eagerly bound module for my application startup and need evolutions to be applied prior to my module constructor running. Injecting DynamicEvolutions and EbeanConfig doesn't seem to be enough for the evolutions to be applied prior to the constructor running in my module.

@justinnichols
Copy link
Author

justinnichols commented Jun 1, 2016

Okay, so while this is still a problem for eagerly bound components that need Ebean evolutions to be completed prior to the instantiation of the component, I have found a workaround for now:

  1. Stop eagerly binding the component
  2. Create a custom Controller that extends Controller. Have all controllers extend the custom Controller.
  3. In that custom Controller, inject a private instance variable of the component interface.

The only drawback to this is that it requires a controller to be run for the component to be instantiated. I know this is the intent behind using DI, but it does get in the way of having a headless service (one with no controllers) that may need to rely on a database and using Ebean with evolutions.

@splendidbits
Copy link

+1

I spend a day troubleshooting this and thought I was going crazy. No matter what I did, Evolutions screwed up schema creations, but had a different effect for me. All submodule's ebean database schemas were mapped to the main project, breaking the ability to migrate and update.

@splendidbits
Copy link

@justinnichols Were you able to get any luck following the new migration page on compile-time DI with Evolutions?

https://www.playframework.com/documentation/2.5.x/ScalaCompileTimeDependencyInjection

@justinnichols
Copy link
Author

I've not tried that, but I'm also using Java, not Scala. If it translates I may try it, but I've got the above workaround at this time. The biggest problem is that we need a component for Ebean that will guarantee evolutions has run which will allow us to inject in our eagerly instantiated components. To my knowledge no such play-ebean component exists.

@splendidbits
Copy link

How about this: it's a bit hacky, but I'm also using Java and struggling with the same thing.

One of these two options

  1. Set Evolutions to create and apply evolution scripts automatically.
    or
  2. Create an Akka Scheduler in the Module Startup class that has a 60 second delay on running a single time.

@justinnichols
Copy link
Author

I had thought of that and putting any arbitrary time delay will ultimately fail because the time it takes evolutions to run is not fixed. Imagine adding an index on a table that has millions of records. It takes minutes to run on a fairly fast system.

@justinnichols
Copy link
Author

justinnichols commented Jun 13, 2016

If I ever have enough time I may look deep into Ebean Play Evolutions to try to understand how I could hook into the evolutions being run and getting notified of when it's complete, if even possible. Then that could be used directly or propagated via a component somehow such that eagerly instantiated components could make use of that hook.

No matter how much DI tries to do away with bootstraps (assuming that's an intent and not just a byproduct), there are valid cases where it's useful. This is one of those cases if you subscribe to using the runtime to make database changes (which we do).

@wsargent
Copy link
Member

I'm starting to think that Evolutions isn't all that useful as a general concept, given how tied in it is to application startup. Especially when there's code generation tools involved -- then you want the database schema created before code generation, and then compilation and application load should happen after that.

@rbygrave
Copy link

If I ever have enough time I may look deep into Ebean to try to understand how I could hook into the evolutions being run and getting notified of when it's complete, if even possible.

Just to be clarify, I'm pretty sure you are talking about "Play Evolutions" and not "Ebean DB Migrations". If you start looking at Ebean you'll see "create-all" DDL generation (for testing usually), DB Migration generation (DDL diff generation) and DB Migration running (using Flyway or Ebean's own DB Migration runner).

Ebean's own DB Migration runner runs the migrations when EbeanServer instance starts just before it becomes available (Refer DefaultServer.run() and DbMigrationConfig.isRunMigration()) ... but understand that this is not Play evolutions.

Note: That Ebean's DB Migration runner is actually a separate module that can run by itself (without Ebean, without the application etc) much like how you can run FlywayDB by itself. If Play evolutions is similar then that should be an option.

@justinnichols
Copy link
Author

justinnichols commented Jun 14, 2016

Thanks for the response, Rob. My apologies for any confusion, but I looked in the play-ebean code and the only thing I saw it doing was generating a 1.sql (in other words, I didn't see it actually attempt to use the SQL files to run them on the database). Are you saying Ebean is not the responsible party for running the 1.sql, 2.sql, 3.sql...n.sql? If not, then I will instead look into Play evolutions as I must have missed something when I dug into Play's side in the past.

@rbygrave
Copy link

> only thing I saw it doing was generating a 1.sql

Yes. I think Play is getting Ebean to generate the "create-all" script and
then rename it to 1.sql

The point of Ebean DB Migration is to generate (and run) diff's and for
that it uses a different DDL generation mechanism (to generate the DDL to
match the changes in the model).

*> Are you saying Ebean is not the responsible party for running the 1.sql,
2.sql, 3.sql...n.sql? *

Yes, that is what I am saying. Play evolutions is running those which would
check the "play_evolutions" table for migration versions that have already
run etc.

On 15 June 2016 at 00:29, Justin Nichols notifications@github.com wrote:

Thanks for the response, Rob. My apologies for confusion, but I looked in
the play-ebean code and the only thing I saw it doing was generating a
1.sql (in other words, I didn't see it actually attempt to use the SQL
files to run them on the database). Are you saying Ebean is not the
responsible party for running the 1.sql, 2.sql, 3.sql...n.sql? If not, then
I will instead look into Play evolutions as I must have missed something
when I dug into Play's side in the past.


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
#65 (comment),
or mute the thread
https://github.com/notifications/unsubscribe/AAMwTSE0f4_9hgfh9gzLHfrAY5ZQ-hKmks5qLp64gaJpZM4GlIkA
.

@justinnichols
Copy link
Author

Of course. I had a brain fart about the play_evolutions. Thanks for the reminder!

In this case, I'll update my previous comment to state that I'll dig into what is handling the play_evolutions then.

@splendidbits
Copy link

I wholeheartedly concur. This situation has been bothering me for weeks.

Evolutions has always been temperamental, and actually casued more headaches than it's worth. Right now I'm just programmatically flipping the ebean DDL creation flags, and it seems to be far far more reliable.

I looked into Flyway but it's really a different use-case and you'd have to auto create your DDLs anyway, so that's barely worth it. Maybe I'm misunderstanding the two products, but in any case, Evolutions seems poorly supported. I couldn't even find an Evolutions changelog.

@BrandonArp
Copy link

As a hack, I have had success by having an EbeanDynamicEvolutions variable injected into the components that access ebean. I looked through a lot of the classes in the plugin and that one seemed to have the most promise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants