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

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.

A Banking Example Application

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.

Customer Entity

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, namely ID.

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, namely ID.

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.

Account Entity

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:

Address Entity

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:

Referee Entity

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.

主站蜘蛛池模板: 墨玉县| 平乡县| 石楼县| 潍坊市| 灯塔市| 富裕县| 林西县| 深水埗区| 房山区| 迁安市| 贵德县| 井研县| 蕲春县| 哈巴河县| 小金县| 嵊泗县| 凌海市| 德格县| 洛阳市| 峨山| 阳春市| 绍兴市| 晴隆县| 集贤县| 南雄市| 比如县| 潢川县| 西林县| 江永县| 阳曲县| 宣城市| 青川县| 昆明市| 前郭尔| 哈巴河县| 牙克石市| 铜陵市| 阳泉市| 屏边| 大关县| 大宁县|