- EJB 3 Developer Guide
- Michael Sikora
- 2148字
- 2021-07-02 11:34:56
O/R Mapping Default Behavior
In the previous chapter we saw how to persist individual entities to a relational database. In this chapter, we take a further look at object-relational mapping with EJB 3. In any real world application, entities do not exist in isolation but have relationships with other entities. In this chapter we shall see how these relationships are mapped onto a relational database.
Object-relational mapping is specified using annotations, but XML mapping files can be used as an alternative. Annotations make extensive use of defaulting. This means that not only do we not have to specify any annotation options if we are satisfied with the defaults, but in many cases we do not have to provide an annotation itself. In this case a default annotation is used.
This defaulting capability is particularly helpful in cases where we start from a Java object model and derive our database schema from this model. However, if we are mapping our Java model to an existing database schema we will need to explicitly specify any mapping options. This is also necessary if our persistence provider does not provide an automatic database schema generation facility. Even if we have automatic schema generation and we are using a Java object model to determine the database schema, we would need to override defaults as part of fine-tuning our application before deploying it in production. For these reasons we will cover many of the object-relational mapping options later in this chapter rather than just describing the defaults.
To begin with, however, we will make full use of defaults to demonstrate the non-intrusiveness of EJB 3.
A Banking Example Application
We will use a simple banking example throughout this chapter. The following diagram shows an initial object model, which consists of Customer, Account, Address, and Referee entities. We will modify this model repeatedly throughout this chapter to illustrate EJB 3 Object-Relational mapping capabilities.

Note that, in our model, a customer can have many accounts (checking or savings, for example) and can have multiple addresses. Also more than one customer can share the same address. This is encapsulated by a many-to-many relationship between customer and address. We have also assumed a one-to-one relationship between customer and referee. A customer has to provide one referee before they can open an account. Note that the model is unidirectional. A Customer object can reference an Account object for example, but an Account object cannot reference a Customer object.
The following is the code for the Customer class, Customer.java
:
package ejb30.entity; import java.io.Serializable; import java.util.Collection; import javax.persistence.*; @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 public int getId() { return id; } public void setId(int id) { this.id = id; } public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } 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 public Collection<Account> getAccounts() { return accounts; } public void setAccounts(Collection<Account> accounts) { this.accounts = accounts; } @ManyToMany 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 + "]"; } }
The @Entity
annotation, as we have seen, indicates that this entity is to be mapped onto a database by the EntityManager
. We have not used a @Table
annotation. The default table name that is generated is the entity name in upper case, namely CUSTOMER
. The @Id
annotation indicates that the following field is the primary key. We have not used the @Column
annotation with the id
field, so the default is the field name in upper case, namely ID
. Recall from Chapter 3 that Java primitive types are mapped onto relational database column types according to JDBC to SQL mapping rules. These resulting SQL types will depend on the database. In the case of the Derby database embedded with GlassFish, fields of type int
are mapped to a database type of INTEGER
. A primary key cannot have NULL values so the id
field is mapped to:
ID INTEGER NOT NULL, PRIMARY KEY (ID)
The firstName
property is a String
type for which the default Derby mapping is VARCHAR(255)
.
The @OneToOne
annotation indicates there is a one-to-one mapping between the Customer and Referee entities. In this case the CUSTOMER
table will contain a foreign key to the REFEREE
table. The default name for the foreign key column is a concatenation of the following 3 items:
- the name of the relationship field of the Customer entity in uppercase, namely
REFEREE
. - an underscore,
"_"
. - the name of the primary key column of the Referee entity in uppercase, namely
ID
.
In this example the resulting foreign key column name is REFEREE_ID
. The type of REFEREE_ID
is the same as the primary key column of the referenced REFEREE
table, namely INTEGER
. If we want to override these defaults we need to use the @JoinColumn
annotation, which we will cover later in this chapter. To summarize, the default CUSTOMER
table will be:

The @OneToMany
annotation indicates that there is a one-to-many relationship between the Customer entity, the owner entity, and the Account entity, the inverse or owned entity. The default behavior is to map onto a join table named CUSTOMER_ACCOUNT
(owner table name + underscore + inverse table name). The join table consists of two foreign key columns, Customer_ID
and accounts_ID
. The first foreign key is the concatenation of the following:
- the name of the owning entity, in this case
Customer
. - an underscore,
"_"
. - the name of the primary key column in the owning
CUSTOMER
table, namelyID.
The type of this foreign key column is the same as the type of the CUSTOMER
table primary key column, namely INTEGER
. The second foreign key column is the concatenation of the following:
- the relationship field of the owning Customer entity, namely
accounts
. - an underscore,
"_"
. - the name of the primary key column of the inverse
ACCOUNT
table, namelyID
.
The type of this foreign key column is the same as the type of ACCOUNT
table primary key column, namely INTEGER
. To summarize the resulting default join table will be:

Later in this chapter we will show how to override these defaults with the @JoinTable
annotation.
The @ManyToMany
annotation indicates there is a many-to-many relationship between the Customer and the Address entity. Recall this is a unidirectional relationship where Customer references Address, but Address does not reference Customer. In such cases Customer is regarded as the owning entity and Address the owned or inverse entity. The default behavior is to map onto a join table named CUSTOMER_ADDRESS
(owner table name + underscore + inverse table name). The join table consists of two foreign key columns, Customer_ID
and addresses_ID
. The procedure for naming these columns is the same as the unidirectional one-to-many case described above. The resulting default join table will be:

Finally the Customer class contains an overridden toString()
method which is used when printing out the values of the Customer object from a client application.
Because our model is unidirectional and the only references between entities are those from Customer, no relationship annotations are required for the remaining classes.
The code for the Account class, Account.java
is listed below:
package ejb30.entity; import java.io.Serializable; import javax.persistence.*; @Entity public class Account implements Serializable { private int id; private double balance; private String accountType; public 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; } public String getAccountType() { return accountType; } public void setAccountType(String accountType) { this.accountType = accountType; } public String toString() { return "[account id =" + id + ",balance=" + balance + ",account type=" + accountType + "]"; } }
The only item to note is the default mapping of the balance
field. The database type to which a field is mapped depends on the underlying database. In the case of the GlassFish embedded Derby database, a field of type double
is mapped to a FLOAT
database type. The default table that the Account entity is mapped to is:

The code for Address class, Address.java
is listed below:
package ejb30.entity; import java.io.Serializable; import javax.persistence.*; @Entity public class Address implements Serializable { private int id; private String addressLine; private String country; private String postCode; public Address() {} @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public String getAddressLine() { return addressLine; } public void setAddressLine(String addressLine) { this.addressLine = addressLine; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } public String getPostCode() { return postCode; } public void setPostCode(String postCode) { this.postCode = postCode; } public String toString() { return "[address id=" + id + ",address=" + addressLine + ",country=" +country + ",post code=" + postCode +"]"; } }
The default mapping rules that we have already described apply here, and the resulting default mapped table is:

The code for the Referee class, Referee.java
is listed below:
package ejb30.entity; import java.io.Serializable; import javax.persistence.*; @Entity public class Referee implements Serializable { private int id; private String name; private String comments; public Referee() {} @Id public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getComments() { return comments; } public void setComments(String comments) { this.comments = comments; } public String toString() { return "[referee id=" + id + ",name=" + name + ",comments=" + comments + "]"; } }
No new mapping rules apply here, and the default mapped table is:

Testing the Application
To test our application we create a session bean interface, BankService
, with methods createCustomers()
and findCustomer()
as follows:
@Remote public interface BankService { void createCustomers(); Customer findCustomer(int custId); }
The createCustomers()
method populates and then persists the Customer, Address, Account, and Referee entities. The findCustomer()
method, which is invoked after createCustomers()
, retrieves a Customer entity for a given customer identifier. Now let's take a look at some of the code for the interface implementation, the stateless session bean BankServiceBean
:
@Stateless
public class BankServiceBean implements 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");
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);
...
In the above code we use the Referee
and Customer
methods to populate their respective entities. We can use the EntityManager.persist()
method to persist Referee as soon as its fields are populated. After populating the Customer entity we do not persist it as we have not yet populated the referenced Account and Address entities. Next, we create a new Account instance a1
and populate its fields. We then create an ArrayList
of type Account
and add a1
to this list as follows:
ArrayList<Account> accounts1 = new ArrayList<Account>(); accounts1.add(a1);
We can now invoke the Customer setAccounts()
method:
c1.setAccounts(accounts1);
In this case customer c1
has only one account, a1
. If a customer has more than one account then each account would be added to the above ArrayList
.
We handle addresses in a similar manner to accounts. Then we can persist our customer object.
The findCustomer()
implementation simply uses the entity manager find()
method that we have seen in the previous chapter:
public Customer findCustomer(int custId) { return ((Customer) em.find(Customer.class, custId)); }
The following code fragment shows how we might invoke the above methods from a client, BankClient
:
custId = Integer.parseInt(args[0]); bank.createCustomers(); Customer cust = bank.findCustomer(custId); System.out.println(cust);
We need to modify the build.xml
file to include passing of an argument to BankClient
. For example:
<target name="run-client"> <exec executable="${glassfish.home}/bin/appclient" failonerror="true" vmlauncher="false"> <arg line="-client ${glassfish.home}/domains/domain1/generated/xml/ j2ee-apps/BankService/BankServiceClient.jar -mainclass ejb30.client.BankClient 4"/> </exec> </target>
If we run the client we get the following output:
run-client:
[java] [Customer Id =4,first name=EDWARD,last name=COOK,
referee=[referee id=4,name=RICHARD BRANSON,
comments=HE SHOULD BANK WITH US],
addresses={IndirectList: not instantiated},
accounts={IndirectList: not instantiated}]
Note that, null values are printed out for addresses and accounts. This is because the default is to lazily load associated entities in a one-to-many and many-to-many relationship. By this we mean that when we load a Customer object from the database we do not automatically load associated Account and Address objects. We will discuss this in more detail later in this chapter.
- 剪映短視頻剪輯零基礎一本通
- RAW攝影后期從入門到精通:Photoshop+Lightroom雙修精解
- Painter 現代服裝效果圖表現技法
- Word-Excel-PowerPoint 2010三合一從新手到高手(超值版)
- Photoshop CC 實戰入門
- Flash CC從入門到精通
- Drupal Multimedia
- UG NX 9.0中文版 基礎教程 (UG工程師成才之路)
- AutoCAD 2018中文版從入門到精通
- 邊做邊學:Photoshop CS6數碼藝術照片后期處理教程
- 高等院校電腦美術教材:CorelDRAW X7中文版基礎教程
- Spark Cookbook 中文版
- Excel革命!超級數據透視表Power Pivot與數據分析表達式DAX快速入門
- Revit技巧精選應用教程
- LaTeX論文寫作教程