3
votes

I have for the last three months been working on a new playframework 2.4 application that uses h2 in dev and postgres on the server. We are using ebean as the ORM and java 8 as development language, and I've generally been happy with that experience.

One of the requirements of the application is to store some data in an external mysql database.

So I added the database to my application.conf:

db.quba.driver = com.mysql.jdbc.Driver
db.quba.url = "jdbc:mysql://localhost:3306/quba"
db.quba.username = "..."
db.quba.password = "..."

Set the migrations for that db to off:

play.evolutions.db.quba.enabled = false

Added the ebean config:

ebean.quba= "quba.models.*"

Created the models:

package quba.models;

import javax.persistence.*;

import com.avaje.ebean.Expr;
import com.avaje.ebean.Model;

@Entity
@Table(name = "st_profile")
public class QubaStationProfile extends Model {
  public static Model.Finder<Long, QubaStationProfile> find = new Model.Finder<>("quba",QubaStationProfile.class);

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "stationid")
  public Long id;

  public Integer session;
  public String profiles;
  public String exception;
  public String site;

  public static QubaStationProfile findStProfileByStationAndTermin(Long stationid, int termin) {
    return QubaStationProfile.find.where()
        .conjunction()
          .add(Expr.eq("stationid", stationid))
          .add(Expr.eq("session", termin))
        .findUnique();
  }

}

And implemented the service that uses the models.

My issue is that altough I can use the model for querying the database using the finder method, as soon as I try to save() an entity I get the following error message:

javax.persistence.PersistenceException: The type [class quba.models.QubaStation] is not a registered entity? If you don't explicitly list the entity classes to use Ebean will search for them in the classpath. 
If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar().
at  com.avaje.ebeaninternal.server.persist.DefaultPersister.createRequest(DefaultPersister.java:1189) ~[avaje-ebeanorm-4.6.2.jar:na]

I have added

playEbeanDebugLevel := 9

to my build.sbt and verified that the classes in quba.models.* are indeed being picked up and instrumented by the by the ebean sbt agent.

ebean-enhance> cls: quba/models/QubaStation  msg: already enhanced entity
ebean-enhance> cls: quba/models/QubaStation  msg: no enhancement on class

I have also verified that the mysql user can access the database and has privilege to create and update records in that database.

Here is a failing test case

package models;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import play.test.FakeApplication;
import play.test.Helpers;
import quba.models.QubaStation;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

public class QubaStationModelTest  {

  public FakeApplication app;

  @Test
  public void testCreateStation(){
    QubaStation station = new QubaStation();
    station.name ="X";
    station.latitude = 1;
    station.longitude = 2;
    station.save();

    station = QubaStation.findStationByName("X");

    assertNotNull(station);
    assertEquals("","X",station.name);

    station.delete();

  }


  @Before
  public void before() {
    Map<String, String> mysql = new HashMap<>();

    mysql.put("db.quba.driver","com.mysql.jdbc.Driver");
    mysql.put("db.quba.url","jdbc:mysql://localhost:3306/quba");
    mysql.put("db.quba.username","...");
    mysql.put("db.quba.password","...");

    app = Helpers.fakeApplication(mysql);
    Helpers.start(app);
  }

  @After
  public void after() {
    Helpers.stop(app);
  }

}

The test output:

[error] Test models.QubaStationModelTest.testCreateStation failed: javax.persistence.PersistenceException: The type [class quba.models.QubaStation] is not a registered entity? If you don't explicitly list the entity classes to use Ebean will search for them in the classpath. If the entity is in a Jar check the ebean.search.jars property in ebean.properties file or check ServerConfig.addJar()., took 3.608 sec
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.createRequest(DefaultPersister.java:1189)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.insert(DefaultPersister.java:208)
[error]     at com.avaje.ebeaninternal.server.persist.DefaultPersister.save(DefaultPersister.java:199)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1461)
[error]     at com.avaje.ebeaninternal.server.core.DefaultServer.save(DefaultServer.java:1454)
[error]     at com.avaje.ebean.Model.save(Model.java:208)
[error]     at models.QubaStationModelTest.testCreateStation(QubaStationModelTest.java:26)

Any help on resolving this issue will be highly appreciated.

Update:

I also tried the new list syntax

ebean.quba= ["quba.models.*"]

as well as enumerating the individual model classes:

ebean.quba= ["quba.models.QubaStationProfile","quba.models.QubaStation"]

, but that made no difference.

Also note that I am not using the "default" server. I use

play.ebean.defaultDatasource=h2

during development, which is overridden in the server config using

play.ebean.defaultDatasource=postgres

I have also noticed, that even though evolutions are disabled for quba, play nevertheless created the evolution scripts. This may, or may not be relevant for this issue.

UPDATE:

I have determined that play-ebean is using the 'postgres' serverConfig when trying to save the object that belongs in the 'quba' database. Since the BeanDescriptorManager for 'postgres' does not contain any of the BeanDescriptors for the 'quba' database, it fails.

2

2 Answers

2
votes

Finally solved this issue.

Contrary to what I believed, play-ebean does not fully associate the datasource with the model classes, even when configured with 'ebean.quba="quba.models.*"'. It works for reads, but not for writes.

When modifying the external database, you need to code the application differently:

private final EbeanServer quba = Ebean.getServer("quba");
....
private QubaStation saveStationPosition(PositionModel positionModel)   
  {
   QubaStation station = new QubaStation();
   ...
   quba.save(station);
  }

Update:

Another approach would be to use Model.insert(serverName) or Model.update(serverName) instead of referencing the EbeanServer explicitly:

private QubaStation insertStationPosition(PositionModel positionModel)   
   {
       QubaStation station = new QubaStation();
       ...
       station.insert("quba");
   }

Note that Model only has insert(String serverName) and update(String serverName) methods, there is no Model.save(String serverName). So the code needs to check if the entity already exists before inserting, otherwise Model.update must be called.

0
votes

If you check the migration guide for 2.4, you can see the following:

And finally, configure Ebean mapped classes as a list instead of a comma separated string (which is still supported but was deprecated):

ebean.default = ["models.*"]

ebean.orders = ["models.Order", "models.OrderItem"]

It's possible a combination of the deprecation and the move to a more recent version of Ebean, you're getting this problem.