Vous êtes sur la page 1sur 10

Source : www.javabeat.

net

Spring Roo and JPA Entities


Author : ManningPublications Date : Wed Apr 20th, 2011 Topic : spring spring-roo jpa

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 relationsit'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 init 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-manyRelates 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-oneA 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-manyRows 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-oneA reference from a child entity back to its parent. InheritanceJPA 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 taskssimple 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-manytasks 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: roo> entity --class org.distracted.tracker.model.Project --testAutomatically ~.model.Project roo> field string --fieldName projectName The resulting entity: 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: >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 Set getTasks(), 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 Rememberstay 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: ... 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 @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 #2 #3 #4 #5 Create our parent Project object. Use the generated DataOnDemand object to bring back a template task, which we can use to stand in for Add the task to the collection of Tasks for our project. Persisting the project also persists the child Task objects. If we fetch the project, we can expect the same number of child tasks (1), which will cause the test t

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 decisiondo 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-onetasks 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): 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: @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 @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 relationshipwe 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. 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 ]

Many-to-manytasks have tags

Let's provide a tagging facility for our application. This lets us mark any task with a tag or label so that we can allow users to organize their own categories. We do this by creating another entity, ActivityTag:

roo>entity --class ~.model.ActivityTag --testAutomatically roo>field string --fieldName tag The resulting ActivityTag entity: package org.distracted.tracker.model; ... @Entity @RooJavaBean @RooToString @RooEntity public class ActivityTag { private String tag; } Now that we've established our ActivityTag entity, we need to associate it with the Task entity. We are going to use a many-to-many relationship, which establishes that any row in one entity can be attached to any other row in the other entity. In database terms, we are establishing two tables, each with a one-to-many relationship to a middle table, which contains rows with a combination of primary keys from each of the parent tables, as shown in figure 2.

For the sake of management, we need to assign a relationship owner. In a one-to-many relationship, the owner is the parent. For a many-to-many relationship, this is an arbitrary selection. We'll choose Task as the owning side by specifying the --mappedBy attribute. ~.model.ActivityTag roo> field set --element ~.model.Task --fieldName taggedTasks --cardinality MANY_TO_MANY --mappedBy tags ~.model.ActivityTag roo> focus ~.model.Task ~.model.Task roo> field set --element ~.model.ActivityTag --cardinality MANY_TO_MANY --fieldName tags This results in changes to both classes. First, the Task entity is augmented with a relationship to ActivityTag: package org.distracted.tracker.model; ... @Entity @RooJavaBean @RooToString @RooEntity public class Task { ... @ManyToMany(cascade = CascadeType.ALL) private Set<org.distracted.tracker.model.ActivityTag> tags = new java.util.HashSet<org.distracted.tracker.model.ActivityTag>(); } You'll see the @ManyToMany annotation defined on the set of ActivityTag elements. The ActivityTag class will be amended with the following collection: package org.distracted.tracker.model; ... @Entity @RooJavaBean @RooToString @RooEntity public class ActivityTag { private String tag; @ManyToMany(cascade = CascadeType.ALL, mappedBy = "tags") private Set<org.distracted.tracker.model.Task> taggedTasks = new java.util.HashSet<org.distracted.tracker.model.Task>(); }

Adding either a Task to an ActivityTag or an ActivityTag to a Task will result in adding the data element to the intersecting table.

Many-to-many strategies
Implementing a many-to-many relationship in a relational database always involves three tables. The database design strategy is to create a table in the middle and map a one-to-many relationship from each of the outer tables into the new table. For example, for Task and ActivityTag above, JPA actually creates a table called task_tags and puts the value of the primary key from both Task and ActivityTag in this table, making it a composite primary key. As a JPA developer, you have a choice whether you want this relationship to be modeled as a many-to-many object relationship, or by decomposing it yourself into two one-to-many relationships to this intersecting table. The general rule is that, when you have to add attributes to the intersection, you want to set up all three entities. For example, the ranking of the tag within all tags of the task needs to occur at the "task tag" level. This can only be done by creating and modeling attributes for the third, intersecting entity.

One-to-onetags have extended attributes


Some entities need to be related to each other on a one-to-one basis. There are several reasons for this: Data partitioningIn some databases, tables can perform poorly because they become too wide (too many columns) or because less frequently used columns are contained within a table that is heavily queried. In these cases, you may benefit by breaking the table apart into several tables with fewer columns. Logical decompositionSome entities consist of multiple parts and logically can be manipulated in individual classes more easily. ReuseThe class may be a parent class to another hierarchy of objects and can be reused for this purpose. Whatever the reason, you can simply use the @OneToOne annotation to define a one-to-one relationship. Let's add extended attributes to our tasks. First, we'll create an TaskExtendedAttributes entity: roo> entity --class ~.model.TaskExtendedAttributes --testAutomatically ~.model.TaskExtendedAttributes roo> field string --fieldName comments ~.model.TaskExtendedAttributes roo> field date --fieldName lastUpdateDate --type java.util.Date ~.model.TaskExtendedAttributes roo> field number --fieldName cost --type java.math.BigDecimal Listing 3 details the generated TaskExtendedAttribute Entity. Listing 3 The TaskExtendedAttribute Entity package org.distracted.tracker.model; ... @Entity @RooJavaBean @RooToString @RooEntity public class TaskExtendedAttributes { private String comments; @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "S-") private Date lastUpdateDate; private BigDecimal cost; } Next, we will create the field reference to establish the one-to-one relationship. In our case, we'll make this a one-sided relationship because we will only fetch the TaskExtendedInfo entity when querying the tasks themselves. We will also pass a hint to the JPA engine that we should fetch this entity eagerly, always bringing it in when we request a Task: ~.model.TaskExtendedAttributes roo> field reference --class ~.model.Task --fieldName extendedAttributes --type org.distracted.tracker.model.TaskExtendedAttributes --cardinality ONE_TO_ONE Here are the resulting annotations. First, the Task: package org.distracted.tracker.model; ... @Entity @RooJavaBean @RooToString @RooEntity public class Task { ... @OneToOne(targetEntity = TaskExtendedAttributes.class) @JoinColumn private TaskExtendedAttributes extendedAttributes; } Let's write a test to exercise the relationship. We will create a task and configure the extended attributes. Add the following test method to the TaskIntegrationTest class, as shown in listing 4. Listing 4 Integration test for one-to-many relationship @Test public void testStoreTaskWithExtendedAttributes() {

TaskDataOnDemand tdod = new TaskDataOnDemand(); #1 Task t = tdod.getNewTransientTask(0); TaskExtendedAttributes tei = new TaskExtendedAttributes(); #2 tei.setComments("This is a complex task"); tei.setCost(new BigDecimal("10000.00")); tei.setLastUpdateDate(new Date()); t.setExtendedAttributes(tei); #3 t.persist(); #4 Task t2 = Task.findTask(t.getId()); #5 Assert.assertNotNull(t2.getExtendedAttributes()); } #1 #2 #3 #4 #5 Use the TaskDataOnDemand object to generate a new transient task. Build our TaskeExtendedAttributes entity data. Note no relationship to the Task itself; this is a oneEstablish the relationship with a Task object. Save task and related TaskExtendedAttributes entity. Verify that we can still find and fetch the Task and TaskExtendedAttributes entities.

Note the persist() call on the TaskExtendedAttributes object. Since you are relating two entities, you need to tell JPA to treat the object as an entity first; otherwise, it will not persist the relationship. So far, we have seen how to configure one-to-one, one-to-many, many-to-one, and many-to-many relationships using Roo and JPA. Let's take a look at one more feature, inheritance.

Tasks and typesinheritance


All of the relationships we've discussed so far are either "has a" (one-to-many, many-to-many) or "belongs to" (many-to-one) relationships. Sometimes, an instance of one entity is a more specific instance of another (hence, the moniker "is a"). JPA supports defining hierarchical relationships. These are exposed in Roo using the familiar entity and field commands. Let's define two types of tasksa simple task and a scheduled calendar event. Both of these activities are tasks, but the calendar event has a fixed time, whereas the task does not. The Inheritance model design is shown here in figure 3.

SimpleTask objects will have a due date, priority, completed flag, and completed date. Calendar events will have a scheduled time and an eventOccurred flag. The base task object can store attributes that are general, such as the description. Let's get started. First, we'll create the parent task, an abstract class we'll call BaseTask: roo> entity --class ~.model.BaseTask --abstract --inheritanceType TABLE_PER_CLASS --testAutomatically Let's put our basic attributes into the BaseTagissue the following commands: roo *.model.BaseTask> field string --fieldName name --sizeMax 80 --notNull roo *.model.BaseTask> field string --fieldName description & --sizeMax 800 Our completed BaseTask entity will look like listing 5. Listing 5 The BaseTask entity package org.distracted.tracker.model; import javax.persistence.Entity; ... @Entity

@RooJavaBean @RooToString @RooEntity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) #1 public class BaseTask { @NotNull @Size(max = 80) private String name; @Size(max = 800) private String description; } #1 Define the inheritance strategy using the table per class strategy. We are using JPA's @Inheritance strategy annotation to choose the table-per-class inheritance model. This model creates a separate table for each concrete class. This works perfectly for our case since most of the fields are going to be dissimilar and the entities handled differently. Table 1 outlines the available strategies.

MORE ON JPA INHERITANCE


More information on JPA inheritance models can be found in a number of books on JPA. Remember that Spring Roo currently uses JPA 2.0, so you have the full range of features provided in that release. Now, let's define the child classes to complete our inheritance hierarchy. We'll start by creating the SimpleTask entity and fill it in with details on priority and completion: roo *.model.BaseTask> entity --class ~.model.SimpleTask --extends ~.model.BaseTask Whoops! We forgot to setup the integration test. Let's do that in an alternative way, using the test integration command in Roo: roo *.model.SimpleTask> test integration --entity ~.model.SimpleTask And, now, we can add the attributes for our simple, one-off tasks: roo *.model.SimpleTask> field date --fieldName dateDue --type java.util.Date --dateFormat SHORT roo *.model.SimpleTask> field boolean --fieldName completed roo *.model.SimpleTask> field date --fieldName dateComplete --type java.util.Date --dateFormat SHORT Our SimpleTask looks like listing 6. Listing 6 The SimpleTask entity package org.distracted.tracker.model; import org.distracted.tracker.model.BaseTask; import javax.persistence.Entity; ...

@Entity @RooJavaBean @RooToString @RooEntity public class SimpleTask extends BaseTask { #1 @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "S-") private Date dateDue; private Boolean completed; @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "S-") private Date dateComplete; } #1 Extend BaseTask to establish the relationship Here is the generated simple_task table, displayed as generated in a MySQL database (you could switch to MySQL just by rerunning the persistence setup command again and modifying your database.properties file to point to a valid MySQL database):

So far, we have built two of the three classes for our Task hierarchy. Let's create the CalendarEvent entity and finish out the hierarchy: roo> entity --class ~.model.CalendarEvent --extends ~.model.BaseTask --testAutomatically ~.model.CalendarEvent roo> field date --fieldName dateStart --type java.util.Date --dateFormat SHORT --timeFormat SHORT ~.model.CalendarEvent roo> field date --fieldName dateEnd --type java.util.Date --dateFormat SHORT --timeFormat SHORT The resultant class is described in listing 7. Listing 7 The CalendarEvent entity package org.distracted.tracker.model; import org.distracted.tracker.model.BaseTask; import javax.persistence.Entity; ... @Entity @RooJavaBean @RooToString @RooEntity public class CalendarEvent extends BaseTask { #1 @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "SS") private Date dateStart; @Temporal(TemporalType.TIMESTAMP) @DateTimeFormat(style = "SS") private Date dateEnd; } #1 Extend BaseTask class to establish the relationship Here is the generated calendar_event table:

Let's run our integration tests and make sure everything works. Rather than using SpringSource Tool Suite, we can use Maven directly. Open a separate command window from the Roo shell and issue the following command in the root directory of your project: mvn integration-test

This Maven command executes all integration tests, of which we are going to look at the results of BaseTaskIntegrationTest, SimpleTaskIntegrationTest, and CalendarEventIntegrationTest. To use our inherited objects, treat them like any other entity. For example, add a test to the CalendarEventIntegrationTest file: @Test @Transactional public void testPersistAnEvent() throws ParseException { DateFormat format = DateFormat.getDateTimeInstance(SHORT, SHORT); CalendarEvent event = new CalendarEvent(); event.setName("Meet with the Barber, Hack N. Slash"); event.setDescription("Ask him to take a little off the top..."); event.setDateStart(format.parse("6/21/2010 12:15 PM")); event.setDateEnd(format.parse("6/21/2010 01:45 PM")); event.persist(); event.flush(); assertNotNull("ID should not be null after persist and flush", event.getId()); } We have now defined an inheritance hierarchy and tested it using Roo's integration testing framework. Data persisted using the Event class will be persisted into the calendar_event table automatically just as the data persisted into the SimpleTask entity will be stored in the simple_task table. Spring Roo makes it easy to work with JPA relationships and hierarchies. Remember to build integration tests to confirm your assumptions about related data. This will help you when you begin to build your web application.

Summary
We have discussed how Spring Roo enables quick creation of JPA entities, relationships, and hierarchies. You may be thinking that Roo is subverting your natural need to separate your application into architectural layers, such as services and repositories. Don't worry, it's a normal reaction. The Roo team assumes you want to use a rapid application development style using the Active Record design pattern, where the persistence API is contained within the entities themselves. But you aren't forced to do this. In fact, there is vigorous debate within the Roo community about generation of Spring Repository beans, pulling the entity management code into separate objects and treating the JPA entities more as configuration and validation information.

Source : www.javabeat.net

Vous aimerez peut-être aussi