Login | Register   
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Persistence Pays Offs: Advanced Mapping with JPA

In this follow-up to an earlier article on the Java Persistence API, learn how to use JPA to map inheritance, one-to-many, and many-to-many relationships. And learn to use the query language (JPQL) to query concrete and abstract classes.


advertisement
he Java Persistence API is a new addition to the Java specification that updates Java's method of performing object/relational mapping. In my first article on JPA, I explained the basics of JPA, how to map a one-to-one relationship, and how to query simple objects. In this article I'll move on to discuss some advanced features. Unlike with Entity Beans 2.x, JPA allows inheritance and can implement it in various ways (SINGLE_TABLE, TABLE_PER_CLASS, and JOINED). Because JPA supports coding-by-exception, developers needn't use additional technologies to accomplish advanced ORM. However, if you want, you can always use Java's new annotations to customize mappings and relationships. I'll also show you how to query complex object graphs with inheritance and collections using JQPL.

In this article I'm going to build on the example application that I created for the first article: a customer address book for a fictional music company called Watermelon. To begin, I'll imagine that Watermelon has some new requirements. There are now two types of customers: individuals and companies. Individuals are customers who have a first name, a last name, and a date of birth; companies have a name, a contact name, and a number of employees. This will give me the opportunity to map inheritance with JPA.

Customers have a home address but Watermelon also needs to define a set of delivery addresses. Each of these addresses can be used on the weekend or/and in the evenings. I will implement this information by adding tags to an address. To do all this I will use one-to-many and many-to-many relationships with JPA. Figure 1 shows the business model to implement.



What You Need
First of all, you need to have a JDK 1.5 installed. This article uses MySQL 5 and TopLink Essentials as the implementation of JPA. To compile and execute the code that comes with this article you need Ant 1.7. Test classes are written in JUnit 4.1. If you are not familiar with the new JUnit 4 syntax check this article. Before running the code, you need to create a database called watermelonDB in MySQL with the root user and no password, i.e. mysql> create database watermelonDB;

Author's Note: As in the previous article I will use TopLink Essentials as a JPA provider and MySQL as a relational database.

Inheritance with JPA
As I said, Watermelon deals with two types of customers: individuals and companies. In the model I can describe this using an abstract Customer class from which inherits Individual and Company classes. Customer has an ID, a telephone number, an email address, and a method to validate telephone numbers (see Callback annotations). It also holds the relationships to Address. Both Individual and Company adds specific attributes and behaviors (e.g. a method to calculate the age of an individual).

Figure 1. Watermelon’s address book object model is shown.
Inheritance is a built-in notion in object languages in general and in Java in particular. But storing this hierarchical information in a relational structure is not easy to do. The proof is that, until now, inheritance hasn't been well handled in the EJB persistence domain. Thanks to JPA, polymorphism and inheritance are now possible in a Java EE and Java SE application. Contrary to associations, which are straightforward to map, with inheritance the developer has to choose between several strategies:
  • A single table per class hierarchy (SINGLE_TABLE) is the default. That means that the three classes Customer, Individual, and Company are flattened into one table only (t_customer, for example). All the attributes of the three classes are mapped down to a single table. This approach is the best in terms of performance, since only a single table is involved. On the other hand, concrete class attributes cannot be mapped into not null columns (as all the subclasses are stored in the same main table).
  • A table per concrete entity class (TABLE_PER_CLASS). All properties of the concrete class, including inherited properties, are mapped to columns of one table. In my example I would only have a t_company and t_individual table. Attributes from the abstract class Customer would be copied into these two tables. If this option provides poor support for polymorphic relationships, it is also still optional in the JPA 1.0 specification. TopLink doesn't support it yet, Hibernate does.
  • A table per class (JOINED). In this strategy the root of the class hierarchy (Customer) as well as each subclass is represented by a separate table. In the Watermelon example I will have a common base table t_customer with an ID, telephone number, and email address. Subclass tables (t_company and t_individual) will then be joined with the root through their primary key. This option provides support for polymorphic relationships and that's the option I will use to map inheritance for the Watermelon address book.
The choice of the mapping strategy has to be done only once in the root entity using the @javax.persistence.Inheritance annotation. It is specified on the Customer class like this:

@Entity @Table(name = "t_customer") @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn(name = "DISC", discriminatorType = DiscriminatorType.STRING, length = 5) public abstract class Customer { @Id @GeneratedValue private Long id; @Column(length = 15) protected String telephone; @Column(name = "e_mail") protected String email; // constructors, getters, setters }

To work, the JOINED strategy (as well as the SINGLE_TABLE) needs additional information: a discriminator column. This column, situated in the root table, enables the persistent provider to identify which instance of the concrete class is stored (is it an individual or a company that is stored in the t_customer table?). Without any annotations the coding-by-exception will create a column named DTYPE of varchar(31) which value is defaulted to the concrete class name.

In the code above, I've decided to name the discriminator column DISC and to give it a length of 5. I can do this using the @javax.persistence.DiscriminatorColumn annotation. The content of this column can be customized in each concrete class by using the @DiscriminatorValue annotation. The value has to be unique and serves to identify the concrete class. The coding-by-exception will default to the class name, but I've decided to change it to indiv and comp (see Table 1).

Table 1. Individual and Company Class Extending Customer

Individual class Company class

@Entity
@Table(name = "t_individual")
@DiscriminatorValue("indiv")

public class Individual extends Customer {
@Column(nullable = false)
private String firstname;
@Column(nullable = false, length = 30)
private String lastname;
@Column(name = "date_of_birth")
@Temporal(TemporalType.DATE)
private Date dateOfBirth;
@Transient
private Integer age;
// constructors, getters, setters
}

@Entity
@Table(name = "t_company")
@DiscriminatorValue("comp")

public class Company extends Customer {
@Column(nullable = false)
private String name;
private String contactName;
@Column(name = "nb_employees")
private Integer numberOfEmployees;
// constructors, getters, setters
}


Notice that each of the three entities is mapped to its own table (using the @Table annotation). As explained in the previous article, the EntityManager and the properties set in the persistent unit will automatically create these tables. The result of a JOINED strategy is the DDLs and the integrity constraints between the three tables, as shown in Table 2.

Table 2. DDLs of t_customer, t_individual and t_company

Customer Individual Company

mysql> desc t_customer

+-----------------+-------------+
| Field | Type                  |
+-----------------+-------------+
| ID              | bigint(20)  |
| DISC            | varchar(5)  |
| e_mail          | varchar(255)|
| TELEPHONE       | varchar(15) |
| home_address_fk | bigint(20)  |
+-----------------+-------------+

mysql> desc t_individual

+---------------+-------------+
| Field | Type |
+---------------+-------------+
| ID | bigint(20) |
| LASTNAME | varchar(30) |
| FIRSTNAME | varchar(255)|
| date_of_birth | date |
+---------------+-------------+

mysql> desc t_company

+--------------+--------------+
| Field | Type |
+--------------+--------------+
| ID | bigint(20) |
| CONTACTNAME | varchar(255) |
| NAME | varchar(255) |
| nb_employees | int(11) |
+--------------+--------------+

ALTER TABLE t_individual ADD CONSTRAINT FK_t_individual_ID FOREIGN KEY (ID) REFERENCES t_customer (ID)
ALTER TABLE t_company ADD CONSTRAINT FK_t_company_ID FOREIGN KEY (ID) REFERENCES t_customer (ID)


Once you've chosen the strategy to map inheritance, you can CRUD any of these concrete classes using the EntityManager like any other entity. In the following code, I create an instance of a Company and an Individual class that I persist into a database.

public void createCustomers() { // Gets an entity manager EntityManagerFactory emf = Persistence.createEntityManagerFactory("watermelonPU"); EntityManager em = emf.createEntityManager(); EntityTransaction trans = em.getTransaction(); // Instantiates a company object Company company = new Company("Universal", "Mr Universe", 50000, "+15488454", "info@universal.com"); company.setHomeAddress(new Address("World Street", "New York", "7448", "UK")); // Instantiates an individual object calendar.set(1940, 10, 9); Individual individual = new Individual("John", "Lennon", "+441909", "john@lenon.com", calendar.getTime()); individual.setHomeAddress(new Address("Abbey Road", "London", "SW14", "UK")); // Persists both customers trans.begin(); em.persist(company); em.persist(individual); trans.commit(); // Closes the entity manager and the factory em.close(); emf.close(); }

Querying a hierarchical model is also possible with JPQL. I can query the concrete classes as well as the root abstract class.

JPQL Query Comments
SELECT c FROM Customer c Returns all the customers (individuals and companies)
SELECT c FROM Company c Returns all the companies
SELECT i FROM Individual i Returns all the individuals

And I can use root class attributes on my queries. For example, the email address is defined in the Customer class. I can use this attribute in queries involving the Customer class as well as Individual and Company.

SELECT c FROM Customer c WHERE c.email LIKE '%.com' All customers with .com email address
SELECT c FROM Company c WHERE c.email LIKE '%.com' All companies with .com email address
SELECT i FROM Individual i WHERE i.email LIKE '%.com' All individuals with .com email address

Of course, I cannot use child class attributes to query the root class. For example, the attribute first name is just defined in Individual. I can't use it to query customers or companies.

SELECT c FROM Customer c WHERE c.firstname='John' Will not work because customer has no first name
SELECT c FROM Company c WHERE c.firstname='John' Will not work because company has no first name
SELECT i FROM Individual i WHERE i.firstname='John' Returns the individuals which first name is 'John'

In the address book, companies have a number of employees. JPQL has a set of arithmetic functions that you can use in a query.

SELECT MAX (c.numberOfEmployees) FROM Company c Returns the maximum number of employees
SELECT MIN (c.numberOfEmployees) FROM Company c Returns the minimum number of employees
SELECT SUM (c.numberOfEmployees) FROM Company c Returns the sum of employees

I can also use restrictions on these numbers by applying inferior, superior, or equal functions, as well as between to get a range of employees.

SELECT c FROM Company c WHERE c.numberOfEmployees > 10000 Returns companies that have more than 10000 employees
SELECT c FROM Company c WHERE c.numberOfEmployees BETWEEN 10000 AND 50000 Returns companies that between 10000 and 50000 employees



Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap