Professional Documents
Culture Documents
net
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 ]
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.
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.
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.
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.
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.
@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