Persisting them

At this stage, we have learned the functionalities offered by Play! 2 to represent our data on both sides (server and client). However, that data was all transient. Indeed, the HTML form was submitting data to an action that rendered them directly.

In a web application, most data isn't transient, but persistent—data is the value of modern applications (moreover, social-oriented ones).

If we remember the structure of our User model, it includes two references to other users: one optional (spouse) and one multiple (friends). Such data must come from somewhere other than the User form, because the actual form is only defined for a single user.

This implies a third piece in our architecture, a database, in order to retrieve previously created data—User. Once we have that, we'll adapt the User form to present to the client's user a way to set this extra information.

Activating a database

Most of the time, within web applications, the chosen database is a relational one. This use case is so common that Play! 2 integrates perfectly with a dev/test in-memory relational database: H2. Of course, in production, we'll be able to target any database server that we would like to use.

In order to ask Play! 2 to start such an in-memory database server, all we have to do is enable it in the configuration file. In fact, the settings are already in our application.conf file but commented out.

So, in this file, locate the following lines and uncomment the db.default.driver and db.driver.url properties:

Activating a database

These lines are defining an in-memory HSQLDB (as the URL is telling us), targeting a database named play. The other settings, credentials, won't be useful for now (but they will be in production).

The very last setting is for helping us expose our data source as a JNDI name, which, as said in the comment, is very useful when using JPA (or other libraries requiring such JNDI stuff).

Something to note in this configuration is the form of the properties; they all start with db.default, which means that we're defining the default JDBC data source. Obviously, if we need several data sources, we can simply duplicate the block and use a different qualifier than default.

Ok, we enable the database, which will now start when we launch the server in dev mode (play run), but then, what can we do with it? That's the purpose of the next section.

Accessing the database

Having enabled a database server, loaded the related driver, and so on, we can use the tools provided in the play.db package. This package contains everything we might need to interact with the database, that is, all JDBC-related facilities. We will now discuss how we might deal with JDBC, HSQL, Play! 2, and a browser.

We start with a class that is able to interact with the underlying database—creating a table, inserting data, and getting it out.

Accessing the database

This class contains methods that are able to connect to a database using a DB singleton provided by Play! 2. This singleton is performing all of the necessary tasks for us when accessing a database, that is, it retrieves the connection string, the credentials (if any), and so on to create the connection.

Doing so, we are connected to the default data source (DB.getDatasource is also available for other data sources).

Note

A good exercise is to think about how this code would have been written in Scala, with the help of currying and sequences.

The implementations are purely JDBC ones, nothing very interesting in there, but we'll see in the next section how we could do the same with an ORM such as Ebean.

Now that we can deal with our database, the following screenshot shows a controller that will enable this code to be used in the frontend:

Accessing the database

It's trivial, but it does the job. This controller allows the outside world to create a table (showcase only), to insert data, and return them all... in JSON. However, we should pay attention to the test action that involves a String parameter: value.

The very first action is simply asking a dedicated template to be rendered:

Accessing the database

This template is meant to be self-sufficient and dynamic; that's why JavaScript code was necessary to call actions dynamically, retrieving either a string or a JSON to be shown in a dedicated result list (at the bottom).

But what is interesting here is what the HTML links are referring. Indeed, routes, and the last three are using variables to create the correct URLs—of course, test takes a parameter!

Now we still have to create those routing instructions, and the following screenshot shows them:

Accessing the database

Ok, great routes, but again, let's focus on the test one to assert that it does exactly what we expect. It defines that a GET request on /test/message will call the test action with message as argument.

See the following screenshot for this sample JDBC interaction in action:

Accessing the database

We can create full JDBC applications, but what if we would like to use an ORM in order to create many more boilerplates for us? This is what we're about to look at in the next section, that is, using Ebean with Play! 2.

Object-relational mapping

Play! 2 supports out of the box an object-relational mapping (ORM), Ebean, which will fill the gap between our domain model and the relational database. Like any other ORM, it aims to facilitate the usage of a model when dealing with relational databases by implementing common helpers or operations such as finders based on the model's properties or CRUD methods. But also, such an ORM is helpful when propagating a transaction or to lazy load data transparently. These tools have gained quite a lot of attention lately, but it's still a matter of taste whether to like or hate them.

Note

I want to cool down Hibernate lovers that were about to see how Play! 2 integrates with Ebean. Definitively, the intent of Play! 2 is not to restrict us to their officially supported third-party libraries. So you'll be able to use Hibernate (or whatever ORM) you're used to coding with. Furthermore, Play! 2 has good support for JPA, which will ease your environment setup.

As this is a book about Play! 2, it won't cover these options. However, the Play! 2 documentation will help with this, and you might want to explore these options further by going to http://localhost:9000/@documentation/JavaJPA.

How this integration begins is simply by enabling Ebean in the configuration file:

Object-relational mapping

Fairly simple; this line (that was commented) is enabling us to define our domain classes under the models package to be Ebean entities.

And, as mentioned in the comment, this single line will create an Ebean server and all related configurations, providing us with all we need to work with this ORM; in this case, it will target our default data source.

So far, so good; we can now make the necessary modifications to our model for Ebean, recognizing them correctly. Like many other ORMs, this will pollute your source code with meta instructions—annotations, hopefully. Ebean uses the standardized ones from javax.persistence.

Thus, in order to have Ebean discovering instances of User to be persisted, we must add two things: the first is noting that the class has to be considered an entity, and the second is setting an Id field.

Note

In Chapter 1, Getting Started with Play! Framework 2, we saw how to add a dependency. There we chose the Guava one; as we won't use it, I'd recommend removing it from the Build.scala file. Otherwise, it may lead to errors, due to conflicts with Ebean's dependencies.

Object-relational mapping

As mentioned earlier, new annotations were added and they came from the standard persistence package.

Now, what if we hit refresh on our web page?

Object-relational mapping

Huh! Merlin was around?

Yes, some magic happened, and it's the combination of the Ebean DDL generation and the Play!'s evolution plugin. In short—but it would be worth it for advanced usage to check the documentation for both—the former is generating the relational database DDL for us, based on the added instructions, and the latter is detecting that changes are needed. How does that work? Let's break it down:

  1. Ebean detects the DDL changes needed.
  2. Play! asks it to generate it.
  3. Ebean generates it.
  4. Play! intercepts the DDL and stores it in an evolutions folder:/conf/evolutions/default/1.sql
  5. The evolution plugin detects that an evolution file has been applied (based on the file number which corresponds to a version) and hooks the Play! 2 startup to render the error page.
  6. We, as the users, click on the Apply this script now! button.
  7. Play! 2 plays the SQL script on the database.

Looking into the created file, we'll discover that a basic structure is created based on properties, but not the external references such as the address or other users:

Object-relational mapping

It is not hard to get that the plugin will enable us to write a 2.sql file, and so on in order to have incremental DB schema changes. What is trivial also is that we'll have a different folder by data source—here the folder is named default.

Having said that, we now have to update the Address class to be an entity, but something more will be needed for this class. Indeed, as it doesn't have any primary key, we'll have to create an internal Id field, shown as follows:

Object-relational mapping

As we can see from the previous screenshot, it's not the only thing to do; to link User with Address, we also need the address field of User to be added with a new persistence instruction, @ManyToOne, because addresses can be shared across several users.

This will result in a new DDL (to be applied on refresh), which will contain the new address table and a new relation (foreign key) from a new property (address_internal_id) in the user table to the address table's primary key.

We have to use this functionality carefully because it has caveats on production. The most important one is probably that such generation is not done incrementally, and so it will generate the whole DDL at once. However, Play! 2 has evolutions to carry out the incremental adaptations.

A safe way to use it would be to generate the DDL on a test database and then create the incremental files manually.

Storing and fetching – a simple story

Reaching this section, we have created our domain model; we have also configured our system and the model classes to be mapped with an ORM—Ebean. In the end, we have a server that is able to connect to a database with tables created for our entities.

In this section, we'll see how to use this ORM to persist and retrieve our model instances to and from the database.

As we're building a web application, we'll use a controller to ask the frontend user what to create and what to retrieve. This controller will be our Data one, which we'll adapt and enhance for more advanced usage.

So, back to our Data controller; we can recall that we only had one way to create an in-memory User and to show some of its information. What must be done now is the following:

  • Add a persist instruction to the current action to save the newly created user
  • Ensure that the address is saved and is not inserted several times with the same values
  • Add a new action to retrieve all created users in our database

As we can imagine, we'll need to query the database to retrieve either all users, but we will also need to fetch a potential address based on its properties.

Again, Play! 2 and its perfect integration with Ebean will help us. Indeed, Ebean defines Query that not only enables us to query the database using our model and hierarchy, but also its internal structure. However, we won't have to manage it by ourselves, because Play! 2 will do it for us through a Finder wrapper class.

Also, the Play! 2 integration with Ebean will avoid a lot of boilerplate for CRUD operations, because such methods will be automatically added to our model. This is simply done by updating our model classes to extend play.db.ebean.Model. That's all folks!

Note

As this book is not about Ebean, we won't go deeply into its features or arguing why it's so good. If you rely on the Play!'s team choices blindly, I'd recommend you go to the Ebean documentation (http://www.avaje.org/ebean/documentation.html) and check out the Model implementation too (which is almost a delegation to Ebean classes). Actually, using it is very straightforward thanks to what has just been discussed.

Storing and fetching – a simple story

Some checkpoints have been dropped into the code to let us review the additions easily. Let's review them one by one now:

  1. The very first thing we can see is that we have flagged the post action to be Transactional; this wouldn't have been mandatory if we didn't have to interact several times with the database (which is checking the address and then saving the user with or without a new address).
  2. Then we enter the wild indeed, we're using what has been added to Address and User and we have access to it via Model, that is; a Finder wrapper class. This is shown in the following screenshot (in Address):
    Storing and fetching – a simple story
  3. We created play.db.ebean.Model.Finder that enables us to work on address' store easily, providing access to querying, update, and so on. In this first usage, we queried all addresses where all three properties match the ones from the user's address. As we expect such a combination to be unique, we called findUnique on the resulted query.

    The result of such a unique query is an instance of Address (the given type to find) or null if none has been found. In the former case, we've updated the user's address to use the existing one.

  4. Now that the user is referring to a valid, unique address, we can use another method provided by extending Model: save. This will save the user but will also save all other resources related to it, in this case, Address (because of the cascade strategy). The address will be created if it wasn't found earlier (based on internal Id this time), or it may be updated. And, at the very end, the user is created.
  5. We also added a new action, allUsers, which retrieves all users from the database. But it does it differently than expected (all() is also available in Model), because of the lazy loading of the address field.

    That's true. Yes, Ebean is also handling the lazy loading for us, but if we want to show all users at once, with their address in a template, we must ask Ebean to preload them while fetching the users—so a join is performed on the address field.

  6. The view part is left as an exercise, but an example is provided in the code files of the book.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset