Creating a many-to-one (or one-to-one) relationship between entities

In real-world applications, domain entities have relationships between them. In this section, we look at how Roo simplifies creating a many-to-one (or one-to-one) relationship between JPA entities.

The following figure shows the relationship between the FLIGHT_TBL and FLIGHT_DESC_TBL tables, which we will use as a reference to model our many-to-orelationship:

Creating a many-to-one (or one-to-one) relationship between entities

In the given figure, the FLIGHT_TBL table contains scheduled flight details and the FLIGHT_DESC_TBL table contains details of all the flights that an airline offers. Each record in the FLIGHT_TBL table refers to exactly one FLIGHT_DESC_TBL record. As there can be multiple flights from one city to another, the relationship between FLIGHT_TBL and FLIGHT_DESC_TBL is many-to-one. The FLIGHT_TBL table is mapped to the FLIGHT_DESC_TBL table by the FLIGHT_DESC_ID foreign key. It is expected that if a FLIGHT_TBL record is deleted, then the deletion is limited to the FLGHT_TBL only.

The following figure shows JPA entities corresponding to the FLIGHT_TBL and FLIGHT_DESC_TBL tables:

Creating a many-to-one (or one-to-one) relationship between entities

In the given figure, the flightId and flightDescId attributes represent primary keys of the Flight and FlightDescription JPA entities, respectively.

Getting ready

Exit the Roo shell and delete the contents of the C: oo-cookbookch03-recipes directory.

Start the Roo shell from the C: oo-cookbookch03-recipes directory.

Execute the ch03_jpa_setup.roo script which creates the flight-app Roo project, sets up Hibernate as the persistence provider, and configures MySQL as the database for the application. If you are using a different database than MySQL or your connection settings are different than what is specified in the script, then modify the script accordingly.

How to do it...

To create the Flight and FlightDescription JPA entities with a many to one relationship, follow the steps given here:

  1. Create the Flight JPA entity (including integration tests) corresponding to theFLIGHT_TBL table:
    .. roo> entity --class ~.domain.Flight --identifierColumn FLIGHT_ID --identifierField flightId --identifierType java.lang.Long --table FLIGHT_TBL --testAutomatically
    
  2. Add attributes to the Flight entity:
    ~.domain.Flight roo> field date --type java.util.Date --fieldName departureDate --column DEPARTURE_DATE
    
    ~.domain.Flight roo> field date --type java.util.Date --fieldName arrivalDate --column ARRIVAL_DATE
    
  3. Create the FlightDescription entity (including integration tests) corresponding to the FLIGHT_DESC_TBL table:
    ~.domain.Flight roo> entity --class ~.domain.FlightDescription --identifierColumn FLIGHT_DESC_ID --identifierField flightDescId --identifierType java.lang.Long --table FLIGHT_DESC_TBL --testAutomatically
    
  4. Add attributes to the FlightDescription entity:
    ~.domain.FlightDescription roo> field string --fieldName origin --column ORIGIN_CITY --notNull
    
    ~.domain.FlightDescription roo> field string --fieldName destination --column DESTINATION_CITY --notNull
    
    ~.domain.FlightDescription roo> field number --type java.lang.Float --fieldName price --column PRICE --notNull
    
  5. Set the focus of the Roo commands on the Flight entity and use the field reference command to create a many-to-one relationship between the Flight and FlightDescription entities, as shown here:
    ~.domain.FlightDescription roo> focus --class ~.domain.Flight
    
    ~.domain.Flight roo> field reference --fieldName flightDescription --type ~.domain.FlightDescription --joinColumnName FLIGHT_DESC_ID --notNull
    

How it works...

As we are not using composite primary keys for the entities, the following things you'll notice about the entity commands that we have used for creating Flight and FlightDescription entities:

  • identifierField: An optional argument has been used to give a custom name to the identifier field of the entities. For instance, the name of identifier field of the Flight entity is flightId and that of FlightDescription is flightDescId. If we had not used the identifierField argument, then by default Roo would have assigned id as the name of the entity's identifier field.
  • identifierColumn: An optional argument has been used to set the name of the table column to which the identifier field maps. This argument instructs Roo to add the @Column JPA annotation to the identifier field. If you don't specify the identifierColumn, by default the name of the mapping column is derived by splitting the camel-case name and adding underscore as the separator. For instance, if you don't specify the identifierColumn argument for an identifier field named myOwnIdField field, the name of the table column is assumed to be my_own_id_field. In the context of this recipe, the identifierColumn will not have any effect because the name of the table column, derived from the identifier field name, is the same as the name assigned by the identifierColumn argument.

The following code snippet from the Flight.java file shows the Flight entity that was created in this recipe:

@RooJavaBean
@RooToString
@RooEntity(identifierField = "flightId", identifierColumn = "FLIGHT_ID", table = "FLIGHT_TBL")
public class Flight {
 ..
}

The given code shows that the @RooEntity annotation contains the identifierField, identifierColumn, and the table attributes with values that we specified for these arguments in the entity command. As mentioned in the Creating persistent entities recipe of Chapter 2, Persisting Objects Using JPA the elements of the @RooEntity annotation are used for generating the corresponding *_Roo_Entity.aj AspectJ ITD. The following code shows the identifier field as defined in the Flight_Roo_Entity.aj AspectJ ITD:

privileged aspect Flight_Roo_Entity
{
   .....    
   @Id
   @GeneratedValue(strategy = GenerationType.AUTO)
   @Column(name = "FLIGHT_ID")
   private Long Flight.flightId;	
   .....
}

In this code, the identifier field is annotated with the @Column annotation because of the presence of the identifierColumn argument in the @RooEntity annotation. The name flightId of the identifier field is derived from the value of the identifierField argument of the @RooEntity annotation.

Now, coming to the field reference command that is used for adding an attribute referring to another object in a relationship. In our next example, the field reference command adds the reference to the FlightDescription object in the Flight class. As only a reference to a related object is defined using field reference, you can consider that it defines many sides of a many-to-one relationship.

The field reference command is similar to the field other (described in Adding attributes to a Java class recipe of Chapter 1, Getting Started with Spring Roo) command because both the commands are used for defining a reference to custom Java objects. The only difference is that the field reference command is specifically meant for defining JPA relationships between entities. The following code from Flight.java shows the code introduced by executing the field reference command:

@NotNull
@ManyToOne
@JoinColumn(name = "FLIGHT_DESC_ID")
private FlightDescription flightDescription;

As we can see from the given code, the field reference command has added a many-to-one JPA relationship between the Flight and FlightDescription entities.

The following table describes the arguments that can be passed to the field reference command:

Argument

Description

fieldName

(mandatory)

Name of the attribute, which refers to the related entity. In our example, flightDescriptio n is the name of the attribute.

type

(mandatory)

Type of the related entity. In our example, FlightDescriptio n is the type of the related entity.

joinColumnName

The column which acts as the foreign key for the related entity. In our example, the FLIGHT_DESC_ID is the column in the FLIGHT_TBL (represented by the Flight JPA entity), which acts as the foreign key to the FLIGHT_DESCRIPTION_TBL table (represented by the FlightDescriptio n JPA entity).

cardinality

Cardinality of the relationship between JPA entities. By default, cardinality is MANY_TO_ONE. If you are creating a one-to-one relationship, then specify ONE_TO_ONE as the value of the cardinalit y argument.

fetch

JPA fetch strategy for related entity. The possible values are EAGER and LAZY. The value of the fetch argument translates into the value of the fetch element of a @ManyToOne or a @OneToOn e annotation.

referencedColumnName

Identifies the column in the table of the related entity to which this join column links. The value of this argument is used as a value of the referencedColumnName attribute of the @JoinColum n JPA annotation.

Note

Even though the Roo shell displays MANY_TO_ONE, MANY_TO_MANY, ONE_TO_MANY, and ONE_TO_ONE as possible values of the cardinality argument of the field reference command, it only accepts MANY_TO_ONE and ONE_TO_ONE. The reason for this is that field reference only makes sense in case of a one-to-one relationship or when the entity is on the many side of a many-to-one relationship.

There's more...

Let's now look at how Roo supports testing entities that participate in relationships with other entities and at how to add a custom dynamic finder method corresponding to the many-to-one relationship field.

Testing JPA entities that participate in relationships

Testing entities that participate in relationships can get a bit tricky sometimes because integration tests may not be able to check relationship constraints. For instance, the testRemove method of FlightDescriptionIntegrationTest (refer to the FlightDescriptionIntegrationTest_Roo_IntegrationTest.aj AspectJ ITD) doesn't test for the scenario in which one or more Flight entity instances are associated with the FlightDescription instance being removed.

To effectively test entities that participate in relationships, you may want to modify auto-generated test methods and data on demand classes. For instance, in our example scenario, the data on demand class of the FlightDescription (refer to the getNewTransientFlightDescription method defined in the FlightDescriptionDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD) only creates FlightDescription instances, and doesn't create any associated Flight instances. As a result of this, the testRemove method will never be able to test a scenario in which one or more Flight instances are associated with the FlightDescription instance being removed. So, to perform effective testing of the FlightDescription entity, you'll need to do the following:

  • Define the getNewTransientFlightDescription method in the FlightDescriptionDataOnDemand.java class, which creates the FlightDescription objects that are associated with one or more Flight instances
  • Define the testRemove method in the FlightDescriptionIntegrationTesting class to first remove all the related Flight entities, before attempting to remove the FlightDescription entity

If entities are not related, data on demand classes are independent of each other. But, if the entities are related, then creating test data becomes a bit of a involved task. For instance, the Flight entity has a many-to-one relationship with FlightDescription; which means that the data on demand class for the Flight entity should create records in the FLIGHT_TBL table, which have a foreign key reference to the FLIGHT_DESC_TBL table records. It is mandatory to assign a foreign key reference to the Flight records in the FLIGHT_TBL table because the FlightDescription relationship is annotated with @NotNull.

Now, the question is how the data on the demand class of the Flight entity can discover the FlightDescription instances (that were created in the FLIGHT_DESC_TBL table by the getNewTransientFlightDescription method of the FlightDescriptionDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD) so that the Flight instances (created by the getNewTransientFlight method of the FlightDataOnDemand_Roo_DataOnDemand.aj AspectJ ITD) in the FLIGHT_TBL can specify the foreign key reference to the FlightDescription instances? It's simple! It can be done by using the data on the demand class of the FlightDescription entity because it maintains the list of records that it created in the FLIGHT_DESC_TBL table. This is exactly what Spring Roo does while generating data on demand classes—it creates dependency between data on demand classes of related entities to create test data.

The following code from the FlightDataOnDemand_Roo_DataOnDemand.aj file shows the Flight data on the demand class:

privileged aspect FlightDataOnDemand_Roo_DataOnDemand
{
  ..  
   @Autowired
   private FlightDescriptionDataOnDemand 
         FlightDataOnDemand.flightDescriptionDataOnDemand;
    
   public Flight FlightDataOnDemand.
         getNewTransientFlight(int index)
   {

      sample.roo.flightapp.domain.Flight obj = 
                 new sample.roo.flightapp.domain.Flight();

      obj.setArrivalDate(obj, index);
      obj.setDepartureDate(obj, index);
      obj.setFlightDescription(obj, index);
      return obj;
   }

   private void FlightDataOnDemand.
         setFlightDescription(Flight obj, int index) {

   sample.roo.flightapp.domain.FlightDescription
   flightDescription = flightDescriptionDataOnDemand.
         getRandomFlightDescription();
   obj.setFlightDescription(flightDescription);
   }
...
}

Note

In the given code, the FlightDescriptionDataOnDemand object is autowired into the FlightDataOnDemand object. As the Flight entity has a many-to-one relationship with the FlightDescription, data on demand class of the Flight entity uses the data on demand class of the FlightDescription to obtain a reference to an existing FlightDescription instance and set it in the Flight instance. This ensures that the test data created during integration testing of the many-to-one relationship is as per the relationship that exists between related entities.

It is important to note that @NotNull and @ManyToOne(...,optional=false) mean the same thing. It is possible to specify that a relationship between entities must exist either by using @NotNull (JSR 303: Bean Validation annotation) or by specifying false as the value of the optional element of the @ManyToOne JPA annotation. In either case, the data on demand class of the entity will ensure that the data on demand class of the related entity is used to enforce a foreign key constraint on the test data created for integration testing.

Dynamic finder method for a many-to-one relationship field

As with other persistent fields, you can use the finder list and finder add commands to view and add finder method(s) corresponding to the relationship field. For instance, if you execute the finder list command against the Flight entity, then one of the finders is for the flightDescription relationship field, as shown here:

~.domain.Flight roo> finder list
..
findFlightsByFlightDescription(FlightDescription flightDescription)

Also, you can use the depth argument of the finder list command to view dynamic finder methods based on the relationship field and other persistent fields of the entity.

See also

  • The Creating a one-to-many (or many-to-many) relationship between entities recipe that follows
..................Content has been hidden....................

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