- EJB 3 Developer Guide
- Michael Sikora
- 1973字
- 2021-07-02 11:34:56
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.

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
.
- Photoshop CC實戰從入門到精通
- 創意UI:Photoshop玩轉移動UI設計
- Sencha Touch Cookbook, Second Edition
- Wordpress 3 Complete
- Pro/E Wildfire 5.0中文版入門、精通與實戰
- Microsoft SharePoint 2010 Administration Cookbook
- Maya 2019三維動畫基礎案例教程
- 下一代空間計算:AR與VR創新理論與實踐
- SOLIDWORKS 2020產品設計基本功特訓(第3版)
- ASP.NET Core 3從入門到實戰
- Unreal Development Kit Beginner's Guide
- 企業微信公眾平臺開發實戰:再小的個體也有自己的品牌
- Photoshop CS6淘寶美工完全實例教程(培訓教材版)
- UG NX 11中文版從入門到精通
- Premiere視頻編輯案例教程:Premiere Pro 2020(微課版·第2版)