Spring Roo and JPA Entities

April 20, 2011

Spring Framework

«»

This article is based on Spring Roo in Action, to be published Summer-2011. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) eBooks and pBooks. MEAPs are sold exclusively through Manning.com. All pBook purchases include free PDF, mobi and epub. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information. [ Use promotional code 'java40beat' and get 40% discount on eBooks and pBooks ]

Using Roo to Create Related JPA Entities

Introduction

In this article, we’ll tell you how to relate entities to each other using the Roo shell. You’ll use the field reference and field set commands, which establish JPA relationships via collections and references. We will explore various relationships, including one to many, many to many, and inheritance hierarchies. Let’s begin by discussing the concept of relationships within JPA.

Object relations—it’s all relative

Tasks can’t just live in the world all by themselves! In a relational database, data is related together from table to table via special columns called primary and foreign keys. In the object-oriented world, we relate entities via references to each other through composition and aggregation. That’s where object-relational mapping comes in—it defines a mapping strategy to relate Java objects to relational tables.

JPA defines relationships using fields and annotations. Spring Roo provides variants of the field command that define references between entities, either as a single, scalar object reference, or as a set of elements of a particular type.

JPA supports the major relationship types and their variants:

  • One-to-many—Relates a row in a parent table to zero or more rows in a child table. The relationship can be defined as bidirectional or unidirectional.
  • One-to-one—A single row in one table is related to a single row in another table. Often, database tables are partitioned into multiple smaller tables for performance or security reasons and the one-to-one relationship can manage this for you.
  • Many-to-many—Rows from each table are related to rows in another table. For example, tracking the authors for a series of books, where books can be authored by more than one author, and an author can write any number of books.
  • Many-to-one—A reference from a child entity back to its parent.
  • Inheritance—JPA supports object-based inheritance and provides several physical models to map this onto a database.

JPA mappings are established via annotations but, since we’re using Roo, we can create these relationships via the Roo shell commands. Let’s create a concept of a project in our system, so that we can relate tasks together.

The Task Tracker database

In our Task Tracker project, which can be downloaded here, we define a set of related entities to track tasks. Let’s review some requirements to help us design our data model. We need to be able to perform the following activities:

  • Define two types of tasks—simple tasks, which can be completed in one step, and complex tasks that may require several, independently processed tasks.
  • Assign tasks to projects.
  • Define scheduled events.
  • Assign tags to our tasks, so that we can search for them later.

To better understand where we’re going, let’s take a look at a diagram of the entities we will be manipulating in figure 1:

Let’s dive in and create a series of relationships.

Defining entity relationships

We’ll start by grouping tasks into projects. Next we’ll associate tasks with tags, which are labels that can be attached to any task to help categorize them. We’ll round out the discussion by creating an extended attributes relationship and further defining tasks as two distinct subclasses, SimpleTask and CalendarEvent.

One-to-many—tasks and projects

In our task tracking system, we would like to place tasks into projects, so that they can be more easily managed. We will limit the functionality to one project per task in our case. Within the Roo shell, let’s create the newProject entity and configure the name field:

1
2
roo> entity --class org.distracted.tracker.model.Project --testAutomatically
~.model.Project roo> field string --fieldName projectName

The resulting entity:

1
2
3
4
5
6
7
8
9
10
11
12
package org.distracted.tracker.model;
import javax.persistence.Entity;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.entity.RooEntity;
@Entity
@RooJavaBean
@RooToString
@RooEntity
public class Project {
	private String projectName;
}

Next, let’s create the relationship between Project and Task using the field set command:

1
2
3
>roo focus ~.model.Project
~.model.Project roo>field set --element org.distracted.tracker.model.Task
--fieldName tasks --cardinality ONE_TO_MANY --mappedBy project

This command performs the following tasks:

  • Adds a Set called tasks to track the tasks created for this project
  • Adds the necessary @OneToMany annotation, which establishes the relationship
  • Adds two methods to the Project_Roo_Javabean.aj file: setTasks(Set) and SetgetTasks(), which provide access to the tasks collection
  • Adds the elicitation of tasks to the Task_Roo_ToString.aj ITD, if not disabled by removal or overriding @RooToString

Remember—stay focused!

If you restart Roo or want to switch to adding fields to another entity, you can use the focus Roo shell command to switch the entity you’re working on.

Here is the code for the Roo-generated Project Java class:

1
2
3
4
5
6
7
...
public class Project {
	private String projectName;
	@OneToMany(cascade = CascadeType.ALL, mappedBy = "project")
	private Set<org.distracted.tracker.model.Task> tasks =
	new java.util.HashSet<org.distracted.tracker.model.Task>();
}

Let’s test this relationship straight away, following the TDD mantra of writing tests “before” writing code (well, at least when we write our code). Open up ProjectIntegrationTest.java from the org.distracted.tracker.model package in the src/test/java directory. Add a method to the class to test the relationship, using your IDE’s “fix import” feature to import any classes such as the Spring @Transactional annotation and the JUnit Assert class. The resulting method should look like listing 1.

Listing 1 Test adding a Project with a related Task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
@Transactional
public void testAddProjectAndTask() {
	Project p = new Project(); #1
	p.setProjectName("The Big One");
	TaskDataOnDemand dod = new TaskDataOnDemand(); #2
	Task randomTask = dod.getNewTransientTask(0);
	p.getTasks().add(randomTask); #3
	p.persist(); #4
	Project p2 = Project.findProject(p.getId()); #5
	Assert.assertEquals(p.getTasks().size(), p2.getTasks().size());
}
 
#1 Create our parent Project object.
#2 Use the generated DataOnDemand object to bring back a template task, which we can use to stand in for regular Task.
#3 Add the task to the collection of Tasks for our project.
#4 Persisting the project also persists the child Task objects.
#5 If we fetch the project, we can expect the same number of child tasks (1), which will cause the test to pass.

This test creates a new Project instance, and then uses the TaskDataOnDemand class to create a new dummy Task object, making it quick for us to get started testing our relationship with Project. It then tells the Project instance to persist itself. Finally, the test fetches the project from scratch and asserts that the newly found instance contains the same number of tasks. More tests could be performed, such as testing for primary key equality, but this test will suffice for a basic straw test of the framework.

Run the ProjectIntegrationTest class in SpringSource Tool Suite (right-click, select Run As…, and select JUnit Test or issue a mvn integration-test command line) and see how quickly we can test this relationship. Note that, by saving the Project, the tasks get saved automatically.

Why annotate with @Transactional?

You will notice that the @Transactional annotation has been added to this test method. That’s because, unless otherwise specified, JPA normally defers loading collections until they are actually accessed. In order to perform the load, a connection to the database must be established. The Spring LocalContainerEntityManagerBean, which hosts JPA in a Spring-based application, works with the Spring JPATransactionManager and the @Transactional annotation in order to figure out when to attempt to commit a transaction.

The @Transactional annotation tells Spring and JPA to keep the same connection active until the end of the annotated method. To see what happens without the @Transactional annotation, remove it and test again. You will see a LazyInstantiationException when the p2.getTasks() method is called, because the connection that brought the p2 instance of the Project object back from the database did not load the tasks collection.

Another key concept: testing using Spring’s integration test framework (which is the technology implemented by the ProjectIntegrationTest) automatically rolls back any transactions begun within a test marked as @Transactional, which makes it easy to repeatedly test the same method. This can be modified but is usually the default behavior.

At this point, we have to make a decision—do we want to support navigation from the parent to the child and also from the child back to the parent? This depends on whether you want to provide a way for Tasks to find their Project. In the case of our system, we do, since we would like to load a list of all tasks and, for each one, display the Project information. Let’s set up that reverse navigation by letting the Task know about the owning Project with a @ManyToOne annotation.

Many-to-one—tasks access projects

Loading a project and navigating to see the collection of tasks is a pretty straightforward thing. Setting up the reverse navigation from a Task to its parent Project is easy. Just use the field reference command (this is one long line, but we have broken it up for readability):

1
2
3
roo>field reference --class org.distracted.tracker.model.Task
--fieldName project --type org.distracted.tracker.model.Project
--cardinality MANY_TO_ONE --joinColumn project_id

WHAT, NO FOCUS COMMAND?

Yes, that’s right, we are just directly issuing a command without worrying what entity we are focused on. We can do that by adding the –class parameter to our command. This is just another way to run commands in Roo without worrying about switching focus among a number of entities.

The preceding command performs the following tasks:

  • Defines a field in the Task object named project, which is a reference to a Project entity.
  • Adds a @ManyToOne annotation, which references the relationship to the Project class.
  • Adds a @JoinColumn annotation, which denotes that a foreign key must be mapped in the resulting table, referring to the primary key of the parent table. The default name is parentEntityName_id, which in this example resolves to project_id.
  • Adds the project to the Task_Roo_ToString.aj ITD unless the method was overridden or disabled.

The Task entity now looks like this:

1
2
3
4
5
6
7
8
9
10
@Entity
@RooJavaBean
@RooToString
@RooEntity
public class Task {
	...
	@ManyToOne(targetEntity = org.distracted.tracker.model.Project.class)
	@JoinColumn
	private org.distracted.tracker.model.Project project;
}

And now, this relationship is bidirectional. We can now load a Task and fetch the parent Project object, in addition to loading a Project instance and requesting all of the held Task entities within the tasks field.

TWO TABLES OR THREE?

If you forget to specify the –mappedBy setting, you might find that you will end up with three tables in this relationship: task, employee, and employee_task, with an extra table containing both the task_id and employee_id. This is an alternate form of mapping for a one-to-many relationship. For more information about the details of JPA 2.0 mappings, consider reviewing a book such as Java Persistence with JPA, by Daoqi Yang, Ph.D.

Now, let’s go back to our ProjectIntegrationTest class and add another method to test the reverse relationship. Add the code in listing 2.

Listing 2 Adding a method to test the reverse relationship

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
@Transactional
public void testAddProjectAndTaskReverseFind() {
	Project p = new Project();
	p.setProjectName("The Big One");
	TaskDataOnDemand dod = new TaskDataOnDemand();
	Task randomTask = dod.getNewTransientTask(0);
	p.getTasks().add(randomTask);
	randomTask.setProject(p); #1
	p.persist();
	Long savedTaskId = p.getTasks().iterator().next().getId(); #2
	Task t = Task.findTask(savedTaskId);
	Assert.assertEquals(t.getProject().getProjectName(), #3
	p.getProjectName());
}
 
#1 Establish many to one relationship by also setting the project reference on the task.
#2 Get key of new task for testing purposes.
#3 Fetch the task by the Task ID, and compare parent project names.

The main difference between this and the preceding test method is that we now must establish both sides of the relationship—we add the new Task to the Project.tasks collection and we set the project reference variable in the Task. The persist method still modifies both items since we are saving from the parent. However, with the bidirectional relationship established, even saving from the child to the parent works, based on the reachability concept. Try changing the test to use randomTask.persist() instead to see it work.

IS ROO HIDING JPA COMPLETELY?

We aren’t suggesting you use Roo to ignore JPA, rather to rapidly get you on your feet in developing a JPA-based application. Finders write JPA query code for you. To get the most out of Roo, we highly suggest you learn and master JPA.

Now, let’s establish a more complex relationship. We’ll relate Tasks to a new entity, a Tag, so that we can tag tasks with labels across projects.

email

«»

Comments

comments

,