ProJPA-Skripta-Napredne softverske tehnologije-Informatika_3, Skripte' predlog Programiranje složenih softverskih sistema
dcplover
dcplover

ProJPA-Skripta-Napredne softverske tehnologije-Informatika_3, Skripte' predlog Programiranje složenih softverskih sistema

150 str.
1broj preuzimanja
753broj poseta
100%od1broj ocena
1broj komentara
Opis
ProJPA,Skripta,Napredne softverske tehnologije,Informatika, Enterprise Applications,Object-Relational Mapping,Collection Mapping, Entity Manager,Using Queries, Query Language, Criteria API, Advanced Object-Relational Mapping, Advanced Topics, XML Mapping Files,Packaging and Deployment, Testing,Migration
20 poeni
poeni preuzimanja potrebni da se preuzme
ovaj dokument
preuzmi dokument
pregled3 str. / 150
ovo je samo pregled
3 prikazano na 150 str.
ovo je samo pregled
3 prikazano na 150 str.
ovo je samo pregled
3 prikazano na 150 str.
ovo je samo pregled
3 prikazano na 150 str.

C H A P T E R 10

■ ■ ■

273

Advanced Object-Relational

Mapping

Every application is different, and, while most have some elements of complexity in them, the difficult parts in one application will tend to be different than those in other types of applications. Chances are that whichever application you are working on at any given time will need to make use of at least one advanced feature of the API. This chapter will introduce and explain some of these more advanced ORM features.

Some of the features in this chapter are targeted at applications that need to reconcile the differences between an existing data model and an object model. For example, when the data in an entity table would be better decomposed in the object model as an entity and a dependent sub-object that is referenced by the entity, then the mapping infrastructure should be able to support that. Likewise, when the entity data is spread across multiple tables, the mapping layer should allow for this kind of configuration to be specified.

There has been no shortage of discussion in this book about how entities in JPA are just regular Java classes and not the heavy persistent objects that were generated by the old EJB entity bean compilers. One of the benefits of entities being regular Java classes is that they can adhere to already established concepts and practices that exist in object-oriented systems. One of the traditional object-oriented innovations is the use of inheritance and creating objects in a hierarchy in order to inherit state and behavior.

This chapter will discuss some of the more advanced mapping features and delve into some of the diverse possibilities offered by the API and the mapping layer. We will see how inheritance works within the framework of the Java Persistence API and how inheritance affects the model.

Table and Column Names In previous sections, we have shown the names of tables and columns as upper-case identifiers. We did this, first, because it helps differentiate them from Java identifiers and, second, because the SQL standard defines that undelimited database identifiers do not respect case, and most tend to display them in upper case.

Anywhere a table or column name is specified, or is defaulted, the identifier string is passed through to the JDBC driver exactly as it is specified, or defaulted. For example, when no table name is specified for the Employee entity, then the name of the table assumed and used by the provider will be Employee, which by SQL definition is no different from EMPLOYEE. The provider is neither required nor expected to do anything to try to adjust the identifiers before passing them to the database driver.

The following annotations should, therefore, be equivalent in that they refer to the same table in a SQL standard compliant database:

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

274

@Table(name="employee") @Table(name="Employee") @Table(name="EMPLOYEE")

Some database names are intended to be case-specific, and must be explicitly delimited. For example, a table might be called EmployeeSales, but without case distinction would become EMPLOYEESALES, clearly less readable and harder to ascertain its meaning. While it is by no means common, or good practice, a database in theory could have an EMPLOYEE table as well as an Employee table. These would need to be delimited in order to distinguish between the two. The method of delimiting is the use of a second set of double quotes, which must be escaped, around the identifier. The escaping mechanism is the backslash (the “\” character), which would cause the following annotations to refer to different tables:

@Table(name="\"Employee\"") @Table(name="\"EMPLOYEE\"")

Notice that the outer set of double quotes is just the usual delimiter of strings in annotation elements, but the inner double quotes are preceded by the backslash to cause them to be escaped, indicating that they are part of the string, not string terminators.

When using an XML mapping file, the identifier is also delimited by including quotes in the identifier name. For example, the following two elements represent different columns:

<column name="&quot;ID&quot;"/> <column name="&quot;Id&quot;"/>

The method of XML escaping is different than the one used in Java. Instead of using the backslash, XML escapes with an ampersand (“&”) character followed by a word describing the specific thing being escaped (in this case, “quot”) and finally a trailing semicolon character.

TIP Some vendors support features to normalize the case of the identifiers that are stored and passed back and forth between the provider and the JDBC driver. This works around certain JDBC drivers that, for example, accept upper-case identifiers in the native SQL query select statement, but pass them back mapped to lower-case identifiers.

Sometimes the database is set to use case-specific identifiers, and it would become rather tedious (and look exceedingly ugly) to have to put the extra quotes on every single table and column name. If you find yourself in that situation, there is a convenience setting in the XML mapping file that will be of value to you.

By including the empty delimited-identifiers element in the XML mapping file, all identifiers in the persistence unit will be treated as delimited, and quotes will be added to them when they are passed to the driver. The only catch is that there is no way to override this setting. Once the delimited identifier flag is turned on, all identifiers must be specified exactly as they exist in the database. Furthermore, if you decide to turn on the delimited-identifiers option, make sure you remove any escaped quotes in your identifier names or you will find that they will be included in the name. Using escaping in addition to the delimited identifiers option will take the escaped quotes and wrap them with further quotes, making the escaped ones become part of the identifier.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

275

Complex Embedded Objects In Chapter 4, we looked at embedding objects within entities, and how an embedded object becomes part of, and dependent upon, the entity that embeds it. We will now explain how more can be done with embedded objects, and how they can contain more than just basic mappings.

Advanced Embedded Mappings Embedded objects can embed other objects, have element collections of basic or embeddable types, as well as have relationships to entities. This is all possible under the assumption that objects embedded within other embedded objects are still dependent upon the embedding entity. Similarly, when bidirectional relationships exist within an embedded object, they are treated as though they exist in the owning entity, and the target entity points back to the owning entity, not to the embedded object.

TIP The ability to have embeddables contain other nested embeddables, relationships to entities, and element collections was introduced in JPA 2.0.

As an example, let’s bring back our Employee and embedded Address objects from Chapter 4 and update the model just a little bit. Insert a ContactInfo object, containing the address plus the phone information, into each employee. Instead of having an address attribute, our Employee entity would now have an attribute named contactInfo, of type ContactInfo, annotated with @Embedded. The model is shown in Figure 10-1.

Figure 10-1. Nested embeddables with relationships

The ContactInfo class contains an embedded object, as well as some relationships, and would be annotated as shown in Listing 10-1.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

276

Listing 10-1. Embeddable ContactInfo Class

@Embeddable @Access(AccessType.FIELD) public class ContactInfo { @Embedded private Address residence; @ManyToOne @JoinColumn(name="PRI_NUM") private Phone primaryPhone; @ManyToMany @MapKey(name="type") @JoinTable(name="EMP_PHONES") private Map<String, Phone> phones; // ... }

The Address class remains the same as in Listing 4-26, but we have added more depth to our contact information. Within the ContactInfo embeddable, we have the address as a nested embedded object, but we also have an additional unidirectional relationship to the phone number serving as the primary contact number. A bidirectional many-to-many relationship to the employee’s phone numbers would have a default join table named EMPLOYEE_PHONE, and on the Phone side the relationship attribute would refer to a list of Employee instances, with the mappedBy element being the qualified name of the embedded relationship attribute. By qualified, we mean that it must first contain the attribute within Employee that contains the embeddable, as well as a dot separator character (“.”) and the relationship attribute within the embeddable. Listing 10-2 shows the Phone class and its mapping back to the Employee entity.

Listing 10-2. Phone Class Referring To Embedded Attribute

@Entity public class Phone { @Id String num; @ManyToMany(mappedBy="contactInfo.phones") List<Employee> employees; String type; // ... }

A proviso about embeddable types is that if an embedded object is a part of an element collection then the embedded object in the collection can only include mappings where the foreign key is stored in the source table. It can contain owned relationships, such as one-to-one and many-to-one, but it cannot contain one-to-many or many-to-many relationships where the foreign key is in either the target table or a join table. Similarly, collection table-based mappings like element collections are unsupported.

Overriding Embedded Relationships When we first introduced embeddables back in Chapter 4, we showed how embeddable types could be reused by being embedded within multiple entity classes. Even though the state is mapped within the embeddable, the embedding entity can override those mappings by using @AttributeOverride to redefine how the embedded state is mapped within that particular entity table. Now that we are using

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

277

relationships within embeddables, @AttributeOverride does not suffice. To override how a relationship is mapped we need to use @AssociationOverride, which provides us with the ability to override relationship join columns and join tables.

Before we look at an example of overriding an embeddable with a relationship in it, let’s first think about the reusability of such an object. If a relationship from entity A to entity B is defined within the embeddable of type E, then either the relationship is owned by A and the foreign key is in the table corresponding to A (or in a join table owned by A) or it is owned by B and the foreign key is going to be in B’s table (or a join table owned by B). If it is owned by B then the foreign key will be to A’s table, and there would be no way to use E in any other entity because the foreign key would be to the wrong table. Similarly, if the relationship was bidirectional then the attribute in B would be of type A (or a collection of A), and could not refer to an object of some other type. We can understand, therefore, that only embeddables with relationships that are owned by the source entity, A, and that are unidirectional, can be reused in other entities.

Suppose the many-to-many relationship in ContactInfo was unidirectional and Phone didn’t have a reference back to the Employee that embedded the ContactInfo. We might want to embed instances of ContactInfo within a Customer entity as well. The CUSTOMER table, however, might have a PRI_CONTACT foreign key column instead of PRI_NUM, and of course we would not be able to share the same join table for both Employee and Customer relationships to the Phone. The resulting Customer class is shown in Listing 10-3.

Listing 10-3. Customer Class Embedding ContactInfo

@Entity public class Customer { @Id int id; @Embedded @AttributeOverride(name="address.zip", column=@Column(name="ZIP")) @AssociationOverrides({ @AssociationOverride(name="primaryPhone", joinColumns=@JoinColumn(name="EMERG_PHONE")), @AssociationOverride(name="phones", joinTable=@JoinTable(name="CUST_PHONE"))}) private ContactInfo contactInfo; // ... }

We can override the zip attribute in the address that is embedded within contactInfo by using @AttributeOverride and navigating to the attribute in the nested embedded Address object.

Because we are overriding two associations we need to use the plural variant of @AssociationOverrides. Note that if there had not been a join table explicitly specified for the phones attribute then the default join table name would have been different depending upon which entity was embedding the ContactInfo. Since the default name is composed partly of the name of the owning entity, the table joining the Employee entity to the Phone entity would have defaulted to EMPLOYEE_PHONE, whereas in Customer the join table would have defaulted to CUSTOMER_PHONE.

TIP There is no way to override the collection table for an element collection in an embeddable.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

278

Compound Primary Keys In some cases, an entity needs to have a primary key or identifier that is composed of multiple fields, or from the database perspective the primary key in its table is made up of multiple columns. This is more common for legacy databases and also occurs when a primary key is composed of a relationship, a topic that we will discuss later in this chapter.

We have two options available to us for having compound primary keys in our entity, depending on how we want to structure our entity class. Both of them require that we use a separate class containing the primary key fields called a primary key class; the difference between the two options is determined by what the entity class contains.

Primary key classes must include method definitions for equals() and hashCode() in order to be able to be stored and keyed on by the persistence provider, and their fields or properties must be in the set of valid identifier types listed in the previous chapter. They must also be public, implement Serializable, and have a no-arg constructor.

As an example of a compound primary key, we will look at the Employee entity again, only this time the employee number is specific to the country where he works. Two employees in different countries can have the same employee number, but only one can be used within any given country. Figure 10-2 shows the EMPLOYEE table structured with a compound primary key to capture this requirement. Given this table definition, we will now look at how to map the Employee entity using the two different styles of primary key class.

Figure 10-2.EMPLOYEE table with a compound primary key

Id Class The first and most basic type of primary key class is an id class. Each field of the entity that makes up the primary key is marked with the @Id annotation. The primary key class is defined separately and associated with the entity by using the @IdClass annotation on the entity class definition. Listing 10-4 demonstrates an entity with a compound primary key that uses an id class. The accompanying id class is shown in Listing 10-5.

Listing 10-4. Using an Id Class

@Entity @IdClass(EmployeeId.class) public class Employee { @Id private String country; @Id @Column(name="EMP_ID") private int id; private String name;

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

279

private long salary; // ... }

The primary key class must contain fields or properties that match the primary key attributes in the entity in both name and type. Listing 10-5 shows the EmployeeId primary key class. It has two fields, one to represent the country and one to represent the employee number. We have also supplied equals() and hashCode() methods to allow the class to be used in sorting and hashing operations.

Listing 10-5. The EmployeeId Id Class

public class EmployeeId implements Serializable { private String country; private int id; public EmployeeId() {} public EmployeeId(String country, int id) { this.country = country; this.id = id; } public String getCountry() { return country; } public int getId() { return id; } public boolean equals(Object o) { return ((o instanceof EmployeeId) && country.equals(((EmployeeId)o).getCountry()) && id == ((EmployeeId)o).getId()); } public int hashCode() { return country.hashCode() + id; } }

Note that there are no setter methods on the EmployeeId class. Once it has been constructed using the primary key values, it can’t be changed. We do this to enforce the notion that a primary key value cannot be changed, even when it is made up of multiple fields. Because the @Id annotation was placed on the fields of the entity, the provider will also use field access when it needs to work with the primary key class.

The id class is useful as a structured object that encapsulates all of the primary key information. For example, when doing a query based upon the primary key, such as the find() method of the EntityManager interface, an instance of the id class can be used as an argument instead of some unstructured and unordered collection of primary key data. Listing 10-6 shows a code snippet that searches for an Employee with a given country name and employee number. A new instance of the EmployeeId class is constructed using the method arguments and then used as the argument to the find() method.

Listing 10-6. Invoking a Primary Key Query on an Entity with an Id Class

EmployeeId id = new EmployeeId(country, id); Employee emp = em.find(Employee.class, id);

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

280

TIP Because the argument to find() is of type Object, vendors can support passing in simple arrays or collections of primary key information. Passing arguments that are not primary key classes is not portable.

Embedded Id Class An entity that contains a single field of the same type as the primary key class is said to use an embedded id class. The embedded id class is just an embedded object that happens to be composed of the primary key components. We use an @EmbeddedId annotation to indicate that it is not just a regular embedded object but also a primary key class. When we use this approach, there are no @Id annotations on the class, nor is the @IdClass annotation used. You can think of @EmbeddedId as the logical equivalent to putting both @Id and @Embedded on the field.

Like other embedded objects, the embedded id class must be annotated with @Embeddable, but the access type might differ from that of the entity that uses it. Listing 10-7 shows the EmployeeId class again, this time as an embeddable primary key class. The getter methods, equals() and hashCode() implementations, are the same as the previous version from Listing 10-5.

Listing 10-7. Embeddable Primary Key Class

@Embeddable public class EmployeeId { private String country; @Column(name="EMP_ID") private int id; public EmployeeId() {} public EmployeeId(String country, int id) { this.country = country; this.id = id; } // ... }

Using the embedded primary key class is no different than using a regular embedded type, except that the annotation used on the attribute is @EmbeddedId instead of @Embedded. Listing 10-8 shows the Employee entity adjusted to use the embedded version of the EmployeeId class. Note that since the column mappings are present on the embedded type, we do not specify the mapping for EMP_ID as was done in the case of the id class. If the embedded primary key class is used by more than one entity, then the @AttributeOverride annotation can be used to customize mappings just as you would for a regular embedded type. To return the country and id attributes of the primary key from getter methods, we must delegate to the embedded id object to obtain the values.

Listing 10-8. Using an Embedded Id Class

@Entity public class Employee { @EmbeddedId private EmployeeId id; private String name; private long salary;

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

281

public Employee() {} public Employee(String country, int id) { this.id = new EmployeeId(country, id); } public String getCountry() { return id.getCountry(); } public int getId() { return id.getId(); } // ... }

We can create an instance of EmployeeId and pass it to the find() method just as we did for the id class example, but, if we want to create the same query using JP QL and reference the primary key, we have to traverse the embedded id class explicitly. Listing 10-9 shows this technique. Even though id is not a relationship, we still traverse it using the dot notation in order to access the members of the embedded class.

Listing 10-9. Referencing an Embedded Id Class in a Query

public Employee findEmployee(String country, int id) { return (Employee) em.createQuery("SELECT e " + "FROM Employee e " + "WHERE e.id.country = ?1 AND e.id.id = ?2") .setParameter(1, country) .setParameter(2, id) .getSingleResult(); }

The decision to use a single embedded identifier attribute or a group of identifier attributes, each mapped separately in the entity class, mostly comes down to personal preference. Some people like to encapsulate the identifier components into a single entity attribute of the embedded identifier class type. The trade-off is that it makes dereferencing a part of the identifier a little bit longer in code or in JP QL, although having helper methods, like those in Listing 10-8, can help.

If you access or set parts of the identifier individually, then it might make more sense to create a separate entity attribute for each of the constituent identifier parts. This presents a more representative model and interface for the separate identifier components. However, if most of the time you reference and pass around the entire identifier as an object, then you might be better off with an embedded identifier that creates and stores a single instance of the composite identifier.

Derived Identifiers When an identifier in one entity includes a foreign key to another entity, we call it a derived identifier. Because the entity containing the derived identifier depends upon another entity for its identity, we call the first the dependent entity. The entity that it depends upon is the target of a many-to-one or one-to- one relationship from the dependent entity, and is called the parent entity. Figure 10-3 shows an example of a data model for the two kinds of entities, with DEPARTMENT table representing the parent entity and PROJECT table representing the dependent entity. Note that in this example there is an additional name primary key column in PROJECT, meaning that the corresponding Project entity has an identifier attribute that is not part of its relationship to Department.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

282

Figure 10-3. Dependent and parent entity tables.

The dependent object cannot exist without a primary key, and since that primary key consists of the foreign key to the parent entity it should be clear that a new dependent entity cannot be persisted without the relationship to the parent entity being established. It is undefined to modify the primary key of an existing entity, thus the one-to-one or many-to-one relationship that is part of a derived identifier is likewise immutable and must not be reassigned to a new entity once the dependent entity has been persisted, or already exists.

We spent the last few sections discussing different kinds of identifiers, and you might think back to what you learned and realize that there are a number of different parameters that might affect how a derived identifier can be configured. For example, the identifier in either of the entities might be composed of one or a plurality of attributes. The relationship from the dependent entity to the parent entity might make up the entire derived identifier, or, as in Figure 10-3, there might be additional state in the dependent entity that contributes to it. One of the entities might have a simple or compound primary key, and in the compound case might have an id class or an embedded id class. All of these factors combine to produce a multitude of scenarios, each of which requires slightly different configurations. The basic rules for derived identifiers are outlined first, with some more detailed descriptions in the following sections.

Basic Rules for Derived Identifiers Most of the rules for derived identifiers can be summarized in a few general statements, although applying the rules together might not be quite as easy. We will go through some of the cases later to explain them, and even show an exception case or two to keep it interesting, but to lay the groundwork for those use cases the rules can be laid out as follows:

• A dependent entity might have multiple parent entities, i.e., a derived identifier might include multiple foreign keys.

• A dependent entity must have all its relationships to parent entities set before it can be persisted.

• If an entity class has multiple id attributes, then not only must it use an id class, but there must also be a corresponding attribute of the same name in the id class as each of the id attributes in the entity.

• Id attributes in an entity might be of a simple type, or of an entity type that is the target of a many-to-one or one-to-one relationship.

• If an id attribute in an entity is of a simple type, then the type of the matching attribute in the id class must be of the same simple type.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

283

• If an id attribute in an entity is a relationship, then the type of the matching attribute in the id class is of the same type as the primary key type of the target entity in the relationship (whether the primary key type is a simple type, an id class, or an embedded id class).

• If the derived identifier of a dependent entity is in the form of an embedded id class, then each attribute of that id class that represents a relationship should be referred to by a @MapsId annotation on the corresponding relationship attribute.

The following sections describe how these rules may be applied.

Shared Primary Key A simple, if somewhat less common case, is when the derived identifier is composed of a single attribute that is the relationship foreign key. As an example, suppose there was a bidirectional one-to-one relationship between Employee and EmployeeHistory entities. Because there is only ever one EmployeeHistory per Employee, we might decide to share the primary key. In Listing 10-10, if the EmployeeHistory is the dependent entity, then we indicate that the relationship foreign key is the identifier by annotating the relationship with @Id.

Listing 10-10. Derived Identifier with Single Attribute

@Entity public class EmployeeHistory { // … @Id @OneToOne @JoinColumn(name="EMP_ID") private Employee employee; // … }

The primary key type of EmployeeHistory is going to be of the same type as Employee, so if Employee has a simple integer identifier then the identifier of EmployeeHistory is also going to be an integer. If Employee has a compound primary key, either with an id class or an embedded id class, then EmployeeHistory is going to share the same id class (and should also be annotated with the @IdClass annotation). The problem is that this trips over the id class rule that there should be a matching attribute in the entity for each attribute in its id class. This is the exception to the rule, because of the very fact that the id class is shared between both parent and dependent entities.

Occasionally, somebody might want the entity to contain a primary key attribute as well as the relationship attribute, with both attributes mapped to the same foreign key column in the table. Even though the primary key attribute is unnecessary in the entity, some people might want to define it separately for easier access. Despite the fact that the two attributes map to the same foreign key column (which is also the primary key column), the mapping does not have to be duplicated in both places. The @Id annotation is placed on the identifier attribute and @MapsId annotates the relationship attribute to indicate that it is mapping the id attribute as well. This is shown in Listing 10-11. Note that physical mapping annotations (e.g. @Column) should not be specified on the empId attribute since @MapsId is indicating that the relationship attribute is where the mapping occurs.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

284

Listing 10-11. Derived Identifier with Shared Mappings

@Entity public class EmployeeHistory { // … @Id int empId; @MapsId @OneToOne @JoinColumn(name="EMP_ID") private Employee employee; // … }

There are a couple of additional points worth mentioning about @MapsId, before we move on to derived identifiers with multiple mapped attributes.

The first point is really a logical follow-on to the fact that the relationship annotated with @MapsId defines the mapping for the identifier attribute as well. If there is no overriding @JoinColumn annotation on the relationship attribute, then the join column will be defaulted according to the usual defaulting rules. If this is the case, then the identifier attribute will also be mapped to that same default. For example, if the @JoinColumn annotation was removed from Listing 10-11 then both the employee and the empId attributes would be mapped to the default EMPLOYEE_ID foreign key column (assuming the primary key column in the EMPLOYEE table was ID).

Secondly, even though the identifier attribute shares the database mapping defined on the relationship attribute, from the perspective of the identifier attribute it is really a read-only mapping. Updates or inserts to the database foreign key column will only ever occur through the relationship attribute. This is one of the reasons why you must always remember to set the parent relationships before trying to persist a dependent entity.

NOTE Do not attempt to set only the identifier attribute (and not the relationship attribute) as a means to shortcut persisting a dependent entity. Some providers may have special support for doing this, but it will not portably cause the foreign key to be written to the database.

The identifier attribute will get filled in automatically by the provider when an entity instance is read from the database, or flushed/committed. However, it cannot be assumed to be there when first calling persist() on an instance unless the user sets it explicitly.

Multiple Mapped Attributes A more common case is probably the one in which the dependent entity has an identifier that includes not only a relationship, but also some state of its own. We will use the example shown in Figure 10-3, where a Project has a compound identifier composed of a name, and a foreign key to the department that manages it. With the unique identifier being the combination of its name and department, no department would be permitted to create more than one project with the same name. However, two

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

285

different departments may choose the same name for their own projects. Listing 10-12 illustrates the trivial mapping of the Project identifier using @Id on both the name and dept attributes.

Listing 10-12. Project with Dependent Identifier

@Entity @IdClass(ProjectId.class) public class Project { @Id private String name; @Id @ManyToOne private Department dept; // ... }

The compound identifier means that we must also specify the primary key class using the @IdClass annotation. Recall our rule that primary key classes must have a matching named attribute for each of the id attributes on the entity, and usually the attributes must also be of the same type. However, this rule only applies when the attributes are of simple types, not entity types. If @Id is annotating a relationship, then that relationship attribute is going to be of some target entity type, and the rule extension is that the primary key class attribute must be of the same type as the primary key of the target entity. This means that the ProjectId class specified as the id class for Project in Listing 10-12 must have an attribute named name, of type String, and another named dept that will be the same type as the primary key of Department. If Department has a simple integer primary key, then the dept attribute in ProjectId will be of type int, but if Department has a compound primary key, with its own primary key class, say DeptId, then the dept attribute in ProjectId would be of type DeptId, as shown in Listing 10-13.

Listing 10-13. ProjectId and DeptId Id Classes

public class ProjectId implements Serializable { private String name; private DeptId dept;

public ProjectId() {} public ProjectId(DeptId deptId, String name) { this.dept = deptId; this.name = name; } // … }

public class DeptId implements Serializable { private int number; private String country;

public DeptId() {} public DeptId (int number, String country) { this.number = number; this.country = country; } // ... }

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

286

Using EmbeddedId It is also possible to have a derived identifier when one or the other (or both) of the entities uses @EmbeddedId. When the id class is embedded, the non-relationship identifier attributes are mapped within the embeddable id class, as usual, but the attributes in the embedded id class that correspond to relationships are mapped by the relationship attributes in the entity. Listing 10-14 shows how the derived identifier is mapped in the Project class when an embedded id class is used. We annotate the relationship attribute with @MapsId(“dept”), indicating that it is also specifying the mapping for the dept attribute of the embedded id class. The dept attribute of ProjectId is of the same primary key type as Department in Listing 10-15.

Note that we have used multiple join columns on the department relationship because Department has a compound primary key. Mapping multipart identifiers is explained in more detail in the “Compound Join Columns” section later in the chapter.

Listing 10-14. Project and Embedded ProjectId Class

@Entity public class Project { @EmbeddedId private ProjectId id; @MapsId("dept") @ManyToOne @JoinColumns({ @JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"), @JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")}) private Department department; // ... } @Embeddable public class ProjectId implements Serializable { @Column(name="P_NAME") private String name; @Embedded private DeptId dept; // ... }

The Department entity has an embedded identifier, but it is not a derived identifier because the DeptId id class does not have any attributes that correspond to relationship attributes in Department. The @Column annotations in the DeptId class map the identifier fields in the Department entity, but when DeptId is embedded in ProjectId those column mappings do not apply. Once the dept attribute is mapped by the department relationship in Project, the @JoinColumn annotations on that relationship are used as the column mappings for the PROJECT table.

Listing 10-15. Department and Embedded DeptId Class

@Entity public class Department { @EmbeddedId private DeptId id;

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

287

@OneToMany(mappedBy="department") private List<Project> projects; // ... } @Embeddable public class DeptId implements Serializable { @Column(name="NUM") private int number; @Column(name="CTRY") private String country; // ... }

If the Department class had a simple primary key, for example a long instead of an id class, then the dept attribute in ProjectId would just be the simple primary key type of Department, e.g., the long type, and there would only be one join column on the many-to-one department attribute in Project.

ALTERNATIVE TO DERIVED IDENTIFIERS

The @MapsId annotation and the ability to apply @Id to relationship attributes was introduced in JPA 2.0 to improve the situation that existed in JPA 1.0. At that time only the one-to-one shared primary key scenario was specified using the @PrimaryKeyJoinColumn annotation (using the @Id annotation is the preferred and recommended method going forward). Although there was no specified way to solve the general case of including a foreign key in an identifier, it was generally supported through the practice of adding one or more additional (redundant) fields to the dependent entity. Each added field would hold a foreign key to the related entity, and, because both the added field and the relationship would be mapped to the same join column(s), one or the other of the two would need to be marked as read-only (see “Read-Only Mappings” section), or not updatable or insertable. The following example shows how Listing 10-12 would be done using JPA 1.0. The id class would be the same. Since the deptNumber and deptCountry attributes are identifier attributes, and can’t be changed in the database, there is no need to set their updatability to false.

@Entity @IdClass(ProjectId.class) public class Project { @Id private String name; @Id @Column(name="DEPT_NUM", insertable=false) private int deptNumber; @Id @Column(name="DEPT_CTRY", insertable=false) private String deptCountry; @ManyToOne @JoinColumns({

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

288

@JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"), @JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")}) private Department department; // ... }

Advanced Mapping Elements Additional elements may be specified on the @Column and @JoinColumn annotations (and their @MapKeyColumn, @MapKeyJoinColumn, and @OrderColumn relatives), some of which apply to schema generation that will be discussed in Chapter 13. Other parts we can describe separately as applying to columns and join columns in the following sections.

Read-Only Mappings JPA does not really define any kind of read-only entity, although it will likely show up in a future release. The API does, however, define options to set individual mappings to be read-only using the insertable and updatable elements of the @Column and @JoinColumn annotations. These two settings default to true but can be set to false if we want to ensure that the provider will not insert or update information in the table in response to changes in the entity instance. If the data in the mapped table already exists and we want to ensure that it will not be modified at runtime, then the insertable and updatable elements can be set to false, effectively preventing the provider from doing anything other than reading the entity from the database. Listing 10-16 demonstrates the Employee entity with read-only mappings.

Listing 10-16. Making An Entity Read-only

@Entity public class Employee { @Id @Column(insertable=false) private int id; @Column(insertable=false, updatable=false) private String name; @Column(insertable=false, updatable=false) private long salary; @ManyToOne @JoinColumn(name="DEPT_ID", insertable=false, updatable=false) private Department department; // ... }

We don’t need to worry about the identifier mapping being modified, because it is illegal to modify identifiers. The other mappings, though, are marked as not being able to be inserted or updated, so we are assuming that there are already entities in the database to be read in and used. No new entities will be persisted, and existing entities will never be updated.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

289

Note that this does not guarantee that the entity state will not change in memory. Employee instances could still get changed either inside or outside a transaction, but at transaction commit time or whenever the entities get flushed to the database, this state will not be saved and the provider will likely not throw an exception to indicate it. Be careful modifying read-only mappings in memory, however, as changing the entities can cause them to become inconsistent with the state in the database and could wreak havoc on a vendor-specific cache.

Even though all of these mappings are not updatable, the entity as a whole could still be deleted. A proper read-only feature will solve this problem once and for all in a future release, but in the meantime some vendors support the notion of read-only entities, and can optimize the treatment of them in their caches and persistence context implementations.

Optionality As we will see in Chapter 13, when we talk about schema generation, there exists metadata that either permits the database columns to be null or requires them to have values. While this setting will affect the physical database schema, there are also settings on some of the logical mappings that allow a basic mapping or a single-valued association mapping to be left empty or required to be specified in the object model. The element that requires or permits such behavior is the optional element in the @Basic, @ManyToOne, and @OneToOne annotations.

When the optional element is specified as false, it indicates to the provider that the field or property mapping may not be null. The API does not actually define what the behavior is in the case when the value is null, but the provider may choose to throw an exception or simply do something else. For basic mappings, it is only a hint and can be completely ignored. The optional element may also be used by the provider when doing schema generation, because, if optional is set to true, then the column in the database must also be nullable.

Because the API does not go into any detail about ordinality of the object model, there is a certain amount of non-portability associated with using it. An example of setting the manager to be a required attribute is shown in Listing 10-17. The default value for optional is true, making it necessary to be specified only if a false value is needed.

Listing 10-17. Using Optional Mappings

@Entity public class Employee { // ... @ManyToOne(optional=false) @JoinColumn(name="DEPT_ID", insertable=false, updatable=false) private Department department; // ... }

Advanced Relationships If you are in the opportune position of starting from a Java application and creating a database schema, then you have complete control over what the schema looks like and how you map the classes to the database. In this case, it is likely that you will not need to use very many of the advanced relationship features that are offered by the API. The flexibility of being able to define a data model usually makes for a less demanding mapping configuration. However, if you are in the unfortunate situation of mapping a Java model to an existing database, then in order to work around the data schema you might need access to more mappings than those we have discussed so far. The mappings described in the following

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

290

sections are primarily for mapping to legacy databases, and will most often be used because they are the only option. A notable exception is the orphan removal feature, used to model a parent–child relationship.

Using Join Tables We have already seen mappings such as the many-to-many and unidirectional one-to-many mappings that use join tables. Sometimes a database schema uses a join table to relate two entity types, even though the cardinality of the target entity in the relationship is one. A one-to-one or many-to-one relationship does not normally need a join table because the target will only ever be a single entity and the foreign key can be stored in the source entity table. But if the join table already exists for a many-to- one relationship, then of course we must map the relationship using that join table. To do so, we need only add the @JoinTable annotation to the relationship mapping.

Whether the relationship is unidirectional or bidirectional, the @JoinTable annotation is a physical annotation and must be defined on the owning side of the relationship, just as with all other mappings. However, because a join table is not the default configuration for mappings that are not many-to-many or unidirectional one-to-many, we do need to specify the annotation when we want a join table to be used. The elements of the @JoinTable annotation can still be used to override the various schema names.

In Listing 10-18, we see a join table being used for a many-to-one relationship from Employee to Department. The relationship may be unidirectional or it may be bidirectional, with a one-to-many relationship from Department back to Employee, but in either case the “many” side must always be the owner. The reason is because even if it were bidirectional the @ManyToOne side could not be the owner because there would be no way for the @ManyToOne attribute to refer to the owning @OneToMany attribute side. There is no mappedBy element in the @ManyToOne annotation definition.

As with most other mappings, the non-owning side of a bidirectional relationship does not change based upon whether the relationship is mapped using a join table or not. It simply refers to the owning relationship and lets it map to the physical tables/columns accordingly.

Listing 10-18. Many-to-one Mapping Using a Join Table

@Entity public class Employee { @Id private int id; private String name; private long salary; @ManyToOne @JoinTable(name="EMP_DEPT") private Department department; // ... }

TIP The ability to use a join table for unidirectional or bidirectional one-to-one or many-to-one relationships was added in JPA 2.0.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

291

Avoiding Join Tables Up to this point, we have discussed a unidirectional one-to-many mapping in the context of using a join table, but it is also possible to map a unidirectional mapping without using a join table. It requires the foreign key to be in the target table, or “many” side of the relationship, even though the target object does not have any reference to the “one” side. We call this a unidirectional one-to-many target foreign key mapping, because the foreign key is in the target table instead of a join table.

To use this mapping, we first indicate that the one-to-many relationship is unidirectional by not specifying any mappedBy element in the annotation. Then we specify a @JoinColumn annotation on the one-to-many attribute to indicate the foreign key column. The catch is that the join column that we are specifying applies to the table of the target object, not the source object in which the annotation appears.

Listing 10-19. Unidirectional One-to-many Mapping Using a Target Foreign Key

@Entity public class Department { @Id private int id; @OneToMany @JoinColumn(name="DEPT_ID") private Collection<Employee> employees; // ... }

The example in Listing 10-19 shows how simple it is to map a unidirectional one-to-many mapping using a target foreign key. The DEPT_ID column refers to the table mapped by Employee, and is a foreign key to the DEPARTMENT table, even though the Employee entity does not have any relationship attribute back to Department.

Before you use this mapping, you should understand the implications of doing so, as they can be quite negative, both from a modeling perspective and a performance perspective. Each row in the EMPLOYEE table corresponds to an Employee instance, with each column corresponding to some state or relationship in the instance. When there is a change in the row, there is the assumption that some kind of change occurred to the corresponding Employee, but in this case that does not necessarily follow. The Employee might have just been changed to a different Department, and because there was no reference to the Department from the Employee there was no change to the Employee.

From a performance standpoint, think of the case when both the state of an Employee is changed, and the Department that it belongs to is changed. When writing out the Employee state the foreign key to the Department is not known because the Employee entity does not have any reference to it. In this case, the Employee might have to be written out twice, once for the changed state of the Employee, and a second time when the Department entity changes are written out and the foreign key from Employee to Department must be updated to point to the Department that is referring to it.

TIP Support for unidirectional one-to-many target foreign key relationships was added in JPA 2.0.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

292

Compound Join Columns Now that we have discussed how to create entities with compound primary keys, it is not a far stretch to figure out that, as soon as we have a relationship to an entity with a compound identifier, we will need some way to extend the way we currently reference it.

Up to this point, we have dealt with the physical relationship mapping only as a join column, but, if the primary key that we are referencing is composed of multiple fields, then we will need multiple join columns. This is why we have the plural @JoinColumns annotation that can hold as many join columns as we need to put into it.

There are no default values for join column names when we have multiple join columns. The simplest answer is to require the user to assign them, so, when multiple join columns are used, both the name element and the referencedColumnName element, which indicates the name of the primary key column in the target table, must be specified.

Now that we are getting into more complex scenarios, let’s add a more interesting relationship to the mix. Let’s say that employees have managers and that each manager has a number of employees that work for him. You may not find that very interesting until you realize that managers are themselves employees, so the join columns are actually self-referential, that is, referring to the same table they are stored in. Figure 10-4 shows the EMPLOYEE table with this relationship.

Figure 10-4. EMPLOYEE table with self-referencing compound foreign key

Listing 10-20 shows a version of the Employee entity that has a manager relationship, which is many- to-one from each of the managed employees to the manager, and a one-to-many directs relationship from the manager to its managed employees.

Listing 10-20. Self-referencing Compound Relationships

@Entity @IdClass(EmployeeId.class) public class Employee { @Id private String country; @Id @Column(name="EMP_ID") private int id; @ManyToOne @JoinColumns({ @JoinColumn(name="MGR_COUNTRY", referencedColumnName="COUNTRY"), @JoinColumn(name="MGR_ID", referencedColumnName="EMP_ID")

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

293

}) private Employee manager; @OneToMany(mappedBy="manager") private Collection<Employee> directs; // ... }

Any number of join columns can be specified, although in practice very seldom are there more than two. The plural form of @JoinColumns may be used on many-to-one or one-to-one relationships or more generally whenever the single @JoinColumn annotation is valid.

Another example to consider is in the join table of a many-to-many relationship. We can revisit the Employee and Project relationship described in Chapter 4 to take into account our compound primary key in Employee. The new table structure for this relationship is shown in Figure 10-5.

Figure 10-5. Join table with a compound primary key

If we keep the Employee entity as the owner, where the join table is defined, then the mapping for this relationship will be as shown in Listing 10-21.

Listing 10-21. Join Table with Compound Join Columns

@Entity @IdClass(EmployeeId.class) public class Employee { @Id private String country; @Id @Column(name="EMP_ID") private int id; @ManyToMany @JoinTable( name="EMP_PROJECT", joinColumns={ @JoinColumn(name="EMP_COUNTRY", referencedColumnName="COUNTRY"), @JoinColumn(name="EMP_ID", referencedColumnName="EMP_ID")}, inverseJoinColumns=@JoinColumn(name="PROJECT_ID")) private Collection<Project> projects; // ... }

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

294

Orphan Removal The orphanRemoval element provides a convenient way of modeling parent-child relationships, or more specifically privately owned relationships. We differentiate these two because privately owned is a particular variety of parent-child in which the child entity may only be a child of one parent entity, and may not ever belong to a different parent. While some parent-child relationships allow the child to migrate from one parent to another, in a privately owned mapping the owned entity was created to belong to the parent and cannot ever be migrated. Once it is removed from the parent, it is considered orphaned and is deleted by the provider.

Only relationships with single cardinality on the source side can enable orphan removal, which is why the orphanRemoval option is defined on the @OneToOne and @OneToMany relationship annotations, but on neither of the @ManyToOne or @ManyToMany annotations.

When specified, the orphanRemoval element causes the child entity to be removed when the relationship between the parent and the child is broken. This can be done either by setting to null the attribute that holds the related entity, or additionally in the one-to-many case by removing the child entity from the collection. The provider is then responsible, at flush or commit time (whichever comes first), for removing the orphaned child entity.

In a parent-child relationship, the child is dependent upon the existence of the parent. If the parent is removed, then by definition the child becomes an orphan and must also be removed. This second feature of orphan removal behavior is exactly equivalent to a feature that we covered in Chapter 6 called cascading, in which it is possible to cascade any subset of a defined set of operations across a relationship. Setting orphan removal on a relationship automatically causes the relationship to have the REMOVE operation option added to its cascade list, so it is not necessary to explicitly add it. Doing so is simply redundant. It is impossible to turn off cascading REMOVE from a relationship marked for orphan removal since its very definition requires such behavior to be present.

In Listing 10-22, the Employee class defines a one-to-many relationship to its list of annual evaluations. It doesn’t matter whether the relationship is unidirectional or bidirectional, the configuration and semantics are the same, so we need not show the other side of the relationship.

Listing 10-22. Employee Class with Orphan Removal of Evaluation Entities

@Entity public class Employee { @Id private int id; @OneToMany(orphanRemoval=true) private List<Evaluation> evals; // ... }

Suppose an employee receives an unfair evaluation from a manager. The employee might go to the manager to correct the information and the evaluation might be modified, or the employee might have to appeal the evaluation, and if successful the evaluation might simply be removed from the employee record. This would cause it to be deleted from the database as well. If the employee decided to leave the company, then when the employee is removed from the system his evaluations will be automatically removed along with him.

If the collection in the relationship was a Map, keyed by a different entity type, then orphan removal would only apply to the entity values in the Map, not to the keys. This means that entity keys are never privately owned.

Finally, if the orphaned object is not currently managed in the persistence context, either because it has been created in memory and not yet persisted, or is simply detached from the persistence context, orphan removal will not be applied. Similarly, if it has already been removed in the current persistence context.

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

295

TIP The orphanRemoval element was introduced into the relationship annotations in JPA 2.0, although many vendors provided similar functionality in the form of vendor-specific metadata (e.g. the Reference Implementation supported a @PrivateOwned annotation).

Mapping Relationship State There are times when a relationship actually has state associated with it. For example, let’s say that we want to maintain the date an employee was assigned to work on a project. Storing the state on the employee is possible but less helpful, since the date is really coupled to the employee’s relationship to a particular project (a single entry in the many-to-many association). Taking an employee off a project should really just cause the assignment date to go away, so storing it as part of the employee means that we have to ensure that the two are consistent with each other, which can be bothersome. In UML, we would show this kind of relationship using an association class. Figure 10-6 shows an example of this technique.

Figure 10-6. Modeling state on a relationship using an association class

In the database everything is rosy, because we can simply add a column to the join table. The data model provides natural support for relationship state. Figure 10-7 shows the many-to-many relationship between EMPLOYEE and PROJECT with an expanded join table.

Figure 10-7. Join table with additional state

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

296

When we get to the object model, however, it becomes much more problematic. The issue is that Java has no inherent support for relationship state. Relationships are just object references or pointers, hence no state can ever exist on them. State exists on objects only, and relationships are not first-class objects.

The Java solution is to turn the relationship into an entity that contains the desired state and map the new entity to what was previously the join table. The new entity will have a many-to-one relationship to each of the existing entity types, and each of the entity types will have a one-to-many relationship back to the new entity representing the relationship. The primary key of the new entity will be the combination of the two relationships to the two entity types. Listing 10-23 shows all of the participants in the Employee and Project relationship.

Listing 10-23. Mapping Relationship State with an Intermediate Entity

@Entity public class Employee { @Id private int id; // ... @OneToMany(mappedBy="employee") private Collection<ProjectAssignment> assignments; // ... } @Entity public class Project { @Id private int id; // ... @OneToMany(mappedBy="project") private Collection<ProjectAssignment> assignments; // ... } @Entity @Table(name="EMP_PROJECT") @IdClass(ProjectAssignmentId.class) public class ProjectAssignment { @Id @ManyToOne @JoinColumn(name="EMP_ID") private Employee employee; @Id @ManyToOne @JoinColumn(name="PROJECT_ID") private Project project; @Temporal(TemporalType.DATE) @Column(name="START_DATE", updatable=false) private Date startDate; // ... } public class ProjectAssignmentId implements Serializable { private int employee;

Download at WoweBook.Com

CHAPTER 10 ■ ADVANCED OBJECT-RELATIONAL MAPPING

297

private int project; // ... }

Here we have the primary key entirely composed of relationships, with the two foreign key columns making up the primary key in the EMP_PROJECT join table. The date at which the assignment was made could be manually set when the assignment is created, or it could be associated with a trigger that causes it to be set when the assignment is created in the database. Note that, if a trigger were used, then the entity would need to be refreshed from the database in order to populate the assignment date field in the Java object.

Multiple Tables The most common mapping scenarios are of the so-called meet-in-the-middle variety. This means that the data model and the object model already exist, or, if one does not exist, then it is created independently of the other model. This is relevant because there are a number of features in the Java Persistence API that attempt to address concerns that arise in this case.

Up to this point, we have assumed that an entity gets mapped to a single table and that a single row in that table represents an entity. In an existing or legacy data model, it was actually quite common to spread data, even data that was tightly coupled, across multiple tables. This was done for different administrative as well as performance reasons, one of which was to decrease table contention when specific subsets of the data were accessed or modified.

To account for this, entities may be mapped across multiple tables by making use of the @SecondaryTable annotation and its plural @SecondaryTables form. We call the default table or the table defined by the @Table annotation the primary table and any additional ones secondary tables. We can then distribute the data in an entity across rows in both the primary table and the secondary tables simply by defining the secondary tables as annotations on the entity and then specifying when we map each field or property which table the column is in. We do this by specifying the name of the table in the table element in @Column or @JoinColumn. We did not need to use this element earlier, because the default value of table is the name of the primary table.

The only bit that is left is to specify how to join the secondary table or tables to the primary table. We saw in Chapter 4 how the primary key join column is a special case of a join column where the join column is just the primary key column (or columns in the case of composite primary keys). Support for joining secondary tables to the primary table is limited to primary key join columns and is specified as a @PrimaryKeyJoinColumn annotation as part of the @SecondaryTable annotation.

To demonstrate the use of a secondary table, consider the data model shown in Figure 10-8. There is a primary key relationship between the EMP and EMP_ADDRESS tables. The EMP table stores the primary employee information, while address information has been moved to the EMP_ADDRESS table.

Figure 10-8. EMP and EMP_ADDRESS tables

Download at WoweBook.Com

ovo je samo pregled
3 prikazano na 150 str.