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:
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:
In the given figure, the flightId
and flightDescId
attributes represent primary keys of the Flight
and FlightDescription
JPA entities, respectively.
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.
To create the Flight
and FlightDescription
JPA entities with a many to one relationship, follow the steps given here:
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
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
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
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
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
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:
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.
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 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:
getNewTransientFlightDescription
method in the FlightDescriptionDataOnDemand.java
class, which creates the FlightDescription
objects that are associated with one or more Flight
instancestestRemove
method in the FlightDescriptionIntegrationTesting
class to first remove all the related Flight
entities, before attempting to remove the FlightDescription
entityIf 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); } ... }
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.
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.
3.145.109.234