Although H2 (Play’s in-memory database) might seem easier at first, it is better to bite the bullet and use the same database for development that you’ll use for deployment. For now, that means MySQL since MySQL is the database used by CloudBees.
Enable EBean, MySQL, and Evolutions
In the application.conf file, you’ll want code similar to the following to enable EBean, MySQL, and evolutions:
# Database configuration
# ~~~~~
db.default.driver=com.mysql.jdbc.Driver
db.default.url="jdbc:"${DATABASE_URL_DB}
db.default.user=${DATABASE_USERNAME_DB}
db.default.password=${DATABASE_PASSWORD_DB}
#
# Evolutions
# ~~~~~
applyEvolutions.default=true
#
# Ebean configuration
# ~~~~~
ebean.default="models.*"
The Play for Java book, for reasons known only to the authors, persists in recommending that you allow Play to implicitly create accessor methods despite well-documented problems.
Do not follow their recommendation. Instead, follow traditional Java best practices and create private fields and public accessors following the standard JavaBean naming conventions.
The Play Framework EBean documentation page also discusses some of these issues and explicitly notes that the byte-code enhancement is provided for backward compatibility.
Note that you must name your accessors according to JavaBeans conventions, which are finicky. That means if a field is named “productId”, your getter must be named getProductId and your setter must be named setProductId. If you name the getter (for example), getProductID, then you’ll get an error like:
Test ControllerTest.testProductController failed:
JSR-303 validated property 'productId' does not have a corresponding accessor for data binding -
check your DataBinder's configuration (bean property versus direct field access)
Entity classes should:
Do not name any class used as an entity with an SQL reserved word (catalog, connection, cube, cycle, data, group, key, level, input, names, month, module, output, row, space, system, table, user, values, work, etc.) You will get a weird error. Either rename the class or use the JPA annotation to specify a non-conflicting table name.
Consider the following code:
public class StockItem extends Model {
@ManyToOne
private Warehouse warehouse;
:
}
How do you “read” that annotation (and similar ones) in such a way that you understand what’s going on? I find the following ways of reading the annotation helpful:
So the above annotation of the private warehouse field could be read as: “Many of me (StockItems) maps to one of the following (Warehouse).”
A bidirectional relationship means that if two entities A and B are in a relationship, they each “point” at each other. From a Java perspective, it means that class A has a field that takes instance(s) of B, and class B has a field that takes instance(s) of A.
While bidirectionality is not required, I recommend that you always create bidirectional relationships in order to simplify model manipulation.
The implications of a bidirectional relationship for annotations are as follows:
So, for example, if there is a StockItem class that declares that many instances of it map to one instance of a Warehouse:
public class StockItem extends Model {
@ManyToOne
private Warehouse warehouse;
:
}
Then the Warehouse class should have a field that declares that one instance of it maps to many instances of StockItem:
public class Warehouse extends Model {
@OneToMany(mappedBy="warehouse")
private List stockItems = new ArrayList<>();
:
}
See the next guideline for explanation of the mappedBy parameter.
Anytime you specify a bidirectional relationship, only one of the entities will actually “implement” the relationship through the creation of a column in the table associated with that entity. The entity containing the column is referred to as the “owner” of the relationship. There should be only one “owner” in a bidirectional relationship, and to indicate that, you provide a “mappedBy” parameter in the non-owning entity. Let’s look at three cases:
Entities with an @ManyToOne field must always “own” that relationship: the ORM will define a column in the table associated with that relationship to store the IDs of the other entity.
To make an @ManyToOne relationship truly bidirectional, you have to not only annotate a field in the other entity with @OneToMany, you also have to tell the ORM explicitly that the other entity is going to manage the relationship. You do this by adding the “mappedBy” parameter to the @OneToMany annotation, specifying the field in the other entity that will hold the relationship. For example:
public class StockItem extends Model {
@ManyToOne
private Warehouse warehouse;
:
}
public class Warehouse extends Model {
@OneToMany(mappedBy="warehouse")
private List stockItems = new ArrayList<>();
:
}
Thus, StockItem “owns” the relationship with Warehouse, and Warehouse indicates this by telling the ORM that its OneToMany relationship is implemented through the warehouse field in the StockItem entity.
The exact same situation exists with bidirectional OneToOne relationships. In this case, one of the entities should have the mappedBy parameter to indicate that the other entity is the owner, but either entity can be chosen. Here is an example:
public class Warehouse extends Model {
@OneToOne(mappedBy="warehouse")
private Address address;
:
}
public class Address extends Model {
@OneToOne
private Warehouse warehouse;
}
Bidirectional ManyToMany relationships are just like bidirectional OneToOne relationships: you have to provide the mappedBy parameter to specify an owner. Here’s an example:
public class Product extends Model {
@ManyToMany
private List tags = new ArrayList<>();
:
}
public class Tag extends Model {
@ManyToMany(mappedBy="tags")
private List products = new ArrayList<>();
}
The OpenJPA documentation recommends “liberal application” of the parameter “cascade=CascadeType.PERSIST” in relationship annotations. This tells the ORM to implicitly save out entities by following links. For example:
public class Product extends Model {
:
@OneToMany(mappedBy="product", cascade=CascadeType.PERSIST)
public List stockItems = new ArrayList<>();
:
}
This says that if you invoke the save() on a Product instance, any StockItem instances in the stockitems field will also be saved.
Note that other cascade types, such as CascadeType.ALL, can result in unexpected entity deletion.
Provide all Entity classes with a static find() method that returns a new instance of an EBean Finder() instance. It will look something like this, assuming the Entity class is named Foo:
public static Finder<Long, Foo> find() {
return new Finder<Long, Foo>(Long.class, Foo.class);
}
Now you can do queries using this find() method. Here are some examples:
// Get all Foos in DB.
List foos = Foo.find().findList();
// Get the Foo with PK=1
Foo foo = Foo.find().byId(1L)
// A more complicated query
List foos = Foo.find()
.where()
.ge("orderDate", lastWeek)
.orderBy("customerId, idDescription")
.setMaxRows(10)
.findList();
See the resources associated with this module for links to pages with more information about the Finder API.