官术网_书友最值得收藏!

O/R Mapping Overriding Defaults

In this section we modify our object model and make it bidirectional. We will also take the opportunity to override some mapping defaults. Shown below is a UML diagram for the bidirectional model.

O/R Mapping Overriding Defaults

A Customer entity can now be referenced by Account and Address entities. However, we still leave the Customer and Referee relationship unidirectional; we have no need in our application to reference a Customer entity from a Referee entity.

Customer Entity

First we modify the Customer entity; the listing is shown below with modifications highlighted. We will discuss these modifications in detail.

@Entity
public class Customer implements Serializable {
private int id;
private String firstName;
private String lastName;
private Referee referee;
private Collection<Address> addresses;
private Collection<Account> accounts;
public Customer() {}
@Id
@Column(name="CUSTOMER_ID")
public int getId() { return id; }
public void setId(int id) { this.id = id; }
@Column(name="FIRST_NAME", length=30)
public String getFirstName() { return firstName; }
public void setFirstName(String firstName) {
this.firstName = firstName;
}
@Column(name="LAST_NAME", length=30)
public String getLastName() { return lastName; }
public void setLastName(String lastName) {
this.lastName = lastName;
}
@OneToOne
public Referee getReferee() { return referee; }
public void setReferee(Referee referee) {
this.referee = referee;
}
@OneToMany(mappedBy="customer", fetch=EAGER)
public Collection<Account> getAccounts() {
return accounts;
}
public void setAccounts(Collection<Account> accounts) {
this.accounts = accounts;
}
@ManyToMany(fetch=EAGER) @JoinTable( name="CUSTOMER_ADDRESS", joinColumns=@JoinColumn( name="CUST_ID", referencedColumnName="CUSTOMER_ID"), inverseJoinColumns=@JoinColumn( name="ADD_ID", referencedColumnName="ADDRESS_ID")
)
public Collection<Address> getAddresses() {
return addresses;
}
public void setAddresses(Collection<Address> addresses) {
this.addresses = addresses;
}
public String toString() {
return "[Customer Id =" + id + ",first name=" +
firstName + ",last name=" + lastName + ",
referee=" + referee + ",addresses=" +
addresses + ",accounts=" + accounts + "]";
}
}

To override the default column name, use the name element of the @Column annotation. The following code fragment specifies the column name for the id field to be CUSTOMER_ID :

@Column(name="CUSTOMER_ID")
public int getId() { return id; }

We will name primary key columns for all our entities according to the format ENTITY + underscore + ID. Recall the Derby database default column length for String fields is 255. To override this use the length element of the @Column annotation. The following renames the column name for the firstName field and gives a length of 30:

@Column(name="FIRST_NAME", length=30)
public String getFirstName() { return firstName; }

Next we examine the one-to-many relationship between Customer and Account. The relationship is now bidirectional, the many side of a bidirectional one-to-many relationship is always the owning side. So Account is the owning side. In the inverse side of a bidirectional, one-to-many relationship we need to specify the owning field using the mappedBy element. The customer field in the owner Account entity references the inverse Customer entity relationship. The resulting @OneToMany annotation will be:

@OneToMany(mappedBy="customer")
public Collection<Account> getAccounts() {
return accounts;
}

Recall that when we printed out the Customer entity in the previous section, the associated Account values were null. This is because the default is to lazily load associated entities in a one-to-many relationship. By this we mean that when we load a Customer object from the database we do not automatically load associated Account objects. This has obvious performance benefits at the loading stage. If the application then requires an associated Account object then this is retrieved when the application makes an explicit reference to that object. This is reasonable if, in our application, a Customer makes only occasional reference to an Account object. If, in our application, the Customer object frequently references an Account, then it makes sense to override the default and initialize associated Account objects at load time. This is called eager loading; we use the fetch element in the @OneToMany annotation for this:

@OneToMany(mappedBy="customer", fetch=EAGER)
public Collection<Account> getAccounts() {
return accounts;
}

In some circumstances the default is for eager loading to take place, for example with one-to-one mappings or with basic mappings. We can override this default. For example, if we had a large column which is infrequently referenced:

@Basic(fetch=LAZY)

However, a lazy loading override is taken as a hint to the persistence provider. The persistence provider may eagerly load the field in any case if it calculates negligible performance overhead. Typically an entire database row will occupy contiguous blocks on a disk, so there is no gain in retrieving just some of the columns of the database row. The reverse is not true. The persistence provider will always perform an eager fetch if this is explicitly specified.

We will now take a look at the @ManyToMany annotation options. Recall that for many-to-many relationships it is arbitrary which is the owning side; we have chosen Customer to be the owning side and Address the inverse side. The default is to map to a join table. To override the default, or just to make mappings explicit, we use the @JoinTable annotation as follows:

@ManyToMany(fetch=EAGER)
@JoinTable(
name="CUSTOMER_ADDRESS",
joinColumns=@JoinColumn(
name="CUST_ID",
referencedColumnName="CUSTOMER_ID"),
inverseJoinColumns=@JoinColumn(
name="ADD_ID",
referencedColumnName="ADDRESS_ID")
)
public Collection<Address> getAddresses() {
return addresses;
}

The name element specifies the join table name; in our example CUSTOMER_ADDRESS happens to be the same as the default. The joinColumns element specifies the join table foreign key column or columns which references the owning table. As our foreign key consists of a single column, we use an embedded @JoinColumn annotation. In the expression,

@JoinColumn(
name="CUST_ID",
referencedColumnName="CUSTOMER_ID")

the name element is the name of the foreign key column in the join table. In our case we choose to name this column CUST_ID, rather than accept the default CUSTOMER_ID. We would need to do this if we were mapping onto an existing join table with the foreign key column named CUST_ID. The referencedColumnName is the name of the column referenced by this foreign key. In our example this is the primary key column of the owning, CUSTOMER, table which we earlier specified as CUSTOMER_ID.

In some cases we may have a composite foreign key, consisting of two or more columns, although it is rare to have more than two columns. In such situations we would use the @JoinColumns annotation. Composite foreign keys usually occur when we are mapping our object model onto a legacy database. We shall see an example of this later in this chapter.

We return to the final element in our @JoinTable annotation example, namely inverseJoinColumns. This specifies the join table foreign key column or columns which reference the inverse table. Again we use an embedded @JoinColumn annotation:

@JoinColumn(
name="ADD_ID",
referencedColumnName="ADDRESS_ID")

Here we have overridden the default ADDRESS_ID, and named our foreign key column as ADD_ID. This references the primary key column, ADDRESS_ID, of the inverse ADDRESS table. Again inverseJoinColumns may refer to composite foreign columns, in which case these will be specified using the @JoinColumns annotation.

Note that we have added a fetch=EAGER clause to the @ManyToMany annotation. For many-to-many relationships lazy loading is the default behavior.

Account Entity

Now let's modify the Account entity so that it can handle the bidirectional relationship with Customer. The listing below has the modifications highlighted:

@Entity
public class Account implements Serializable {
private int id;
private double balance;
private String accountType;
private Customer customer;
public Account() {}
@Id
@Column(name = "ACCOUNT_ID")
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public double getBalance() { return balance; }
public void setBalance(double balance) {
this.balance = balance;
}
@Column(name="ACCOUNT_TYPE", length=2)
public String getAccountType() { return accountType; }
public void setAccountType(String accountType) {
this.accountType = accountType;
}
@ManyToOne @JoinColumn(name="CUSTOMER_ID", referencedColumnName="CUSTOMER_ID") public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; }
public String toString() {
return "[account id =" + id + ",balance=" +
balance + ",account type=" + accountType + "]";
}
}

The relationship between the Account and Customer entities is many-to-one so we need to add a @ManyToOne annotation. We also need to define a field customer of type Customer with associated getters and setters. The following code fragment shows this:

@ManyToOne
@JoinColumn(name="CUSTOMER_ID",
referencedColumnName="CUSTOMER_ID")
public Customer getCustomer() { return customer; }
public void setCustomer(Customer customer) {
this.customer = customer;
}

Note that, we have also used the @JoinColumn annotation to explicitly name the foreign key column in the ACCOUNT table and the referenced primary key column in the CUSTOMER table.

Address Entity

Now we modify the Address entity so that it can handle the bidirectional relationship with customer. A code fragment with the modifications is shown below:

@Entity
public class Address implements Serializable {
private int id;
private String addressLine;
private String country;
private String postCode;
private Collection<Customer> customers; // @ManyToMany(mappedBy="addresses", fetch=EAGER) public Collection<Customer> getCustomers() { return customers; } public void setCustomers(Collection<Customer> customers) { this.customers = customers; }
// other getters and setters
}

Note the @ManyToMany annotation. Recall that we have chosen Customer to be the owning side and Address to be the inverse side of the many-to-many relationship. In the inverse side we need to specify the owning field with a mappedBy element. The owning field is the field in the Customer entity, namely addresses, which references the Address entity. We can see this in the following code fragment from the Customer entity:

@Entity
public class Customer implements Serializable {
private int id;
private String firstName;
private String lastName;
private Referee referee;
private Collection<Address> addresses;
private Collection<Account> accounts;
public Customer() {}
@Id
@Column(name="CUSTOMER_ID")
public int getId() { return id; }
public void setId(int id) { this.id = id; }
...
@ManyToMany(fetch=EAGER)
@JoinTable(
name="CUSTOMER_ADDRESS",
joinColumns=@JoinColumn(
name="CUST_ID",
referencedColumnName="CUSTOMER_ID"),
inverseJoinColumns=@JoinColumn(
name="ADD_ID",
referencedColumnName="ADDRESS_ID")
)
public Collection<Address> getAddresses() {
return addresses;
}
public void setAddresses(Collection<Address> addresses) {
this.addresses = addresses;
}
...
}

Reverting back to the Address entity, note that we have added a fetch=EAGER clause to override the default lazy loading behavior.

We do not need to modify Referee as the relationship remains unidirectional with no reference to Customer from Referee.

BankServiceBean

To test our new bidirectional model we need to modify BankServiceBean. First we need to modify the createCustomer() method so that bidirectional references from Account to Customer and from Address to Customer are included. A code fragment with the modifications is shown below:

@Stateless
public class BankServiceBean implements BankService {
@PersistenceContext(unitName="BankService")
private EntityManager em;
public void createCustomers() {
Referee r1 = new Referee();
r1.setId(1);
r1.setName("SIR JOHN DEED");
r1.setComments("JUDGE");
em.persist(r1);
Customer c1 = new Customer();
c1.setId(1);
c1.setFirstName("SIMON");
c1.setLastName("KING");
c1.setReferee(r1);
Account a1 = new Account();
a1.setId(1);
a1.setBalance(430.5);
a1.setAccountType("C");
a1.setCustomer(c1);
ArrayList<Account> accounts1 =
new ArrayList<Account>();
accounts1.add(a1);
c1.setAccounts(accounts1);
em.persist(a1);
Address add1 = new Address();
add1.setId(1);
add1.setAddressLine("49, KINGS ROAD MANCHESTER");
add1.setCountry("UK");
add1.setPostCode("MN1 2AB");
ArrayList<Address> addresses1 =
new ArrayList<Address>();
addresses1.add(add1);
c1.setAddresses(addresses1);
em.persist(add1);
em.persist(c1);
Referee r2 = new Referee();
r2.setId(2);
r2.setName("DR WILLIAM SMITH");
r2.setComments("MEDICAL PRACTIONER");
em.persist(r2);
Customer c2 = new Customer();
c2.setId(2);
c2.setFirstName("JANE");
c2.setLastName("KING");
c2.setReferee(r2);
Account a2 = new Account();
a2.setId(2);
a2.setBalance(99);
a2.setAccountType("C");
a2.setCustomer(c2);
ArrayList<Account> accounts2 =
new ArrayList<Account>();
accounts2.add(a2);
c2.setAccounts(accounts2);
em.persist(a2);
ArrayList<Customer> customers1 = new ArrayList<Customer>(); customers1.add(c1); customers1.add(c2); add1.setCustomers(customers1); em.persist(add1);
c2.setAddresses(addresses1); // same address as
// John King
em.persist(c2);
...
}
public Customer findCustomer(int custId) {
return ((Customer) em.find(Customer.class, custId));
}
public Customer findCustomerForAccount(int accountId) {
Account account = (Account) em.find(Account.class,
accountId);
return account.getCustomer();
}
public Collection<Customer> findCustomersForAddress(
int addressId) {
Address address = (Address) em.find(Address.class,
addressId);
Collection<Customer> customers =
address.getCustomers();
return customers;
}
}

Note for Account object a1 we add the statement:

a1.setCustomer(c1);

This provides the link from Account to Customer; we already have a link from Customer to Account. Similar statements would be added for remaining Account and associated Customer objects.

When creating a link from Address to Customer for our first address object, add1, we note that two customers, c1 and c2, share this address. We deal with this by creating a list, customers1, containing the two customers and then populating the address object add1 with customers1:

ArrayList<Customer> customers1 =
new ArrayList<Customer>();
customers1.add(c1);
customers1.add(c2);
add1.setCustomers(customers1);

Finally note that we have added two methods to BankServiceBean. The findCustomerForAccount() method retrieves a Customer object for a supplied account id. The findCustomersForAddress() method retrieves a Collection of Customer objects for a supplied address id.

主站蜘蛛池模板: 石门县| 鄂伦春自治旗| 岚皋县| 建水县| 兴和县| 五家渠市| 遵义县| 安徽省| 涿州市| 固原市| 长子县| 通海县| 出国| 南平市| 于都县| 岳普湖县| 丹江口市| 大竹县| 鄂伦春自治旗| 容城县| 张家口市| 师宗县| 疏附县| 香港 | 剑川县| 菏泽市| 垫江县| 宕昌县| 长顺县| 高邮市| 西和县| 舒兰市| 花垣县| 施甸县| 温泉县| 屏边| 营口市| 平乐县| 文安县| 阳原县| 萨迦县|