Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Persistence Pays Offs: Advanced Mapping with JPA : Page 2

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.

One-to-Many Relationships
Now that I've mapped the inheritance, I'll focus on the delivery addresses. In Figure 1 a customer has one home address (mapped in the previous article) and zero or more delivery addresses. This is represented by a unidirectional, one-to-many relationship between Customer and Address.

There are two different strategies you can use to have a one-to-many relationship in a relational database. The first one is to place a foreign key in the table representing the many (t_address), pointing to the primary key of the table representing the one (t_customer). The second is to use a third table (join table) to serve as a link between the two. Unlike inheritance where the developer can choose between several strategies, one-to-many relationships in JPA use a join table.

The code below shows how to manage a one-to-many relationship. Again, I could leave the defaults of coding-by-exception and let JPA map the collection for me, but I've decided to use annotations to customize the mapping.

@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; @OneToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REMOVE}) @JoinColumn(name = "home_address_fk", nullable = false) private Address homeAddress; @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinTable(name = "t_delivery_addresses", joinColumns = {@JoinColumn(name = "customer_fk")}, inverseJoinColumns = {@JoinColumn(name = "address_fk")}) private List

deliveryAddresses = new ArrayList
(); public void addDeliveryAddress(Address deliveryAddress) { deliveryAddresses.add(deliveryAddress); } // constructors, getters, setters }
The @javax.persistence.OneToMany annotation is used for one-to-many relationships and has the same attributes as the @OneToOne (see the previous article). What I'm doing in the code above is notifying JPA to lazy load the delivery addresses (fetch attribute) and to cascade all kind of events to them (persist, remove…). As I said, JPA maps a one-to-many relationship using a join table. To customize the name and columns of this join table, I can use the @javax.persistence.JoinTable annotation. According to my code, the join table will be called t_delivery_addresses. To change the names of the foreign keys I have to use the @JoinColumn annotation.

With all these annotations, JPA will create the adequate tables and integrity constraints. Table 3 shows the DDLs of the three tables involved in the relation.

Table 3. DDLs of t_customer, t_address, and the join table

Customer Join Table Address

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_delivery_addresses

| Field | Type | Null |
| customer_fk | bigint(20) | NO |
| address_fk | bigint(20) | NO |

mysql> desc t_address
| Field | Type |
| ID | bigint(20) |
| CITY | varchar(100) |
| zip_code | varchar(10) |
| STREET | varchar(255) |
| COUNTRY | varchar(50) |

Many-to-Many Relationship
To help classify the different addresses a customer may have, Watermelon wants to tag them. A tag is just a label (a String) that can be added to a collection of addresses. Tag and Address have a bi-directional, many-to many relationship. In the database, this information will be stored exactly the same way the one-to-many relationships are: by using a third table that joins the primary keys. In Table 4 you will find the @ManyToMany annotation used in conjunction with @JoinTable and @JoinColumn.

Table 4. Many-to-many relationship between the classes Address and Tag are shown.

Address Tag

@Table(name = "t_address")
public class Address {
@Id @GeneratedValue
private Long id;
private String street;
@Column(length = 100)
private String city;
@Column(name = "zip_code", length = 10)
private String zipcode;
@Column(length = 50)
private String country;
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "t_address_tag",
joinColumns = {
@JoinColumn(name = "address_fk")},
inverseJoinColumns = {
@JoinColumn(name = "tag_fk")})

private List<Tag> tags = new ArrayList<Tag>();
// constructors, getters, setters}

@Table(name = "t_tag")
public class Tag {
@Id private String name;
private List<Address>
// constructors, getters, setters}

The identifier (@Id) of the Tag class is the attribute name of type String. The value is set manually, that's why there is no @GeneratedValue annotation. Both classes use the @ManyToMany annotation meaning that the relationship is bi-directional. Any of these classes could have defined the join table attributes (@JoinTable) but I've decided that the Address class will. The join table between addresses and tags is called t_address_tag. Note that I could have omitted the @ManyToMany annotation in the Tag class because it has no specific parameters (coding-by-exception has the same effect).

@OneToMany and @ManyToMany deal with collections of objects. To query collections, JPQL has a set of keywords such as EMPTY, that checks if a collection is empty or not, or MEMBER OF that checks if an object is a member of the collection. The code below shows you how to persist an address and the related tags, as well as doing some queries.

Tag tag1 = new Tag("24/7"); Tag tag2 = new Tag("working hours"); Tag tag3 = new Tag("week-ends"); Address address = new Address("Central Side Park", "New York", "7845", "US"); address.addTag(tag1); address.addTag(tag2); address.addTag(tag3); // Perists the address and its tags trans.begin(); em.persist(address); trans.commit(); Query query; List<Address> addresses; // Finds all the addresses either in London or New York query = em.createQuery("SELECT a FROM Address a WHERE a.city IN ('London', 'New York') "); addresses = query.getResultList(); // Finds all the addresses that do not have a tag query = em.createQuery("SELECT a FROM Address a WHERE a.tags IS EMPTY"); addresses = query.getResultList(); // Finds all the addresses that have at least one tag query = em.createQuery("SELECT a FROM Address a WHERE a.tags IS NOT EMPTY"); addresses = query.getResultList(); // Finds all the addresses that have a "week-ends" tag query = em.createQuery("SELECT a FROM Address a WHERE :param MEMBER OF a.tags"); query.setParameter("param", new Tag("week-ends")); addresses = query.getResultList();

Antonio Goncalves is a senior architect specialized in Java/J2EE. Former BEA consultant he now helps insurance, finance and telecommunication clients set up their architectures. He also teaches J2EE at CNAM University in Paris.
Comment and Contribute






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