Enterprise Java Development@TOPIC@
This chapter will take you through mapping nested classes to the database. In this case we have classes within classes (within classes, etc.) that get mapped to a flat database table. Note there are cases when the embedded class contains the meat of what we want mapped to our database and the wrapping entity may only be created to provide the necessary primary key property.
In this section we will map a simple embedded object within an entity class.
Add the following classes to your src/main tree. In this case the entity class contains an instance of a Name class which contains two properties mapped to the database. The Name class is annotated as @Embeddable and the name property in the entity is annotated as @Embedded.
package myorg.entityex.annotated;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Name {
private String firstName;
private String lastName;
public String getFirstName() { return firstName; }
public Name setFirstName(String firstName) { this.firstName = firstName; return this; }
public String getLastName() { return lastName; }
public Name setLastName(String lastName) { this.lastName = lastName; return this; }
}
package myorg.entityex.annotated;
import javax.persistence.*;
@Entity
@Table(name="ENTITYEX_BEAR")
public class Bear {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
@Embedded
private Name name;
public int getId() { return id; }
public void setId(int id) {
this.id = id;
}
public Name getName() { return name; }
public void setName(Name name) {
this.name = name;
}
}
Add the new entity class to the persistence unit.
<class>myorg.entityex.annotated.Bear</class>
Build the module and note the database schema created for the entity class. Note the embedded properties are now mapped at the same level as the columns for the entity properties.
create table ENTITYEX_BEAR ( id integer generated by default as identity, firstName varchar(255), lastName varchar(255), primary key (id) );
Add custom table mappings for the firstName and lastName properties. Define the mapping for firstName from within the embedded class. Define the mapping for lastName from within the entity class.
public class Name {
@Column(name="FIRST_NAME", length=16)
private String firstName;
public class Bear {
...
@AttributeOverrides({
@AttributeOverride(name="lastName", column=@Column(name="LAST_NAME", length=16))
})
@Embedded
private Name name;
Rebuild the module and note the change in definition for the firstName and lastName columns. We were able to control the mapping from either within the embedded or entity class.
create table ENTITYEX_BEAR ( id integer generated by default as identity, FIRST_NAME varchar(16), LAST_NAME varchar(16), primary key (id) );
At this point we have shown how to map a single nested object. Note how similar this was to the @EmbeddedId case we went thru during the compound primary key chapter.
The above is an example of a single-level embedded object that has been supported since JPA 1.0. In the next step, add a nested embedded object. Support for multiple levels of nesting was added in JPA 2.0.
Add the following classes.
package myorg.entityex.annotated;
import javax.persistence.Embeddable;
@Embeddable
public class Street {
private int number;
private String name;
public int getNumber() { return number; }
public Street setNumber(int number) { this.number = number; return this; }
public String getName() { return name; }
public Street setName(String name) { this.name = name; return this; }
}
package myorg.entityex.annotated;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Column;
import javax.persistence.Embeddable;
@Embeddable
public class Address {
private Street street; //a second level of embedded
//@Column(name="CITY", length=16)
private String city;
//@Column(name="STATE", length=16)
private String state;
public Street getStreet() { return street; }
public Address setStreet(Street street) { this.street = street; return this; }
public String getCity() { return city; }
public Address setCity(String city) { this.city = city; return this; }
public String getState() { return state; }
public Address setState(String state) { this.state = state; return this; }
}
public class Bear { ... @Embedded private Address address; public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; }
Rebuild the module with the new, multi-level embedded class and note the database schema created. Both levels of the Address were flattened into the entity table.
create table ENTITYEX_BEAR ( id integer generated by default as identity, city varchar(255), state varchar(255), name varchar(255), number integer not null, FIRST_NAME varchar(16), LAST_NAME varchar(16), primary key (id) );
Define custom table mappings for the address.
Leave Street un-customized
public static class Street { private int number; private String name;
Map Street.number to the STREET_NUMBER column from the Address class.
@Embeddable public static class Address { @AttributeOverrides({ @AttributeOverride(name="number", column=@Column(name="STREET_NUMBER")), }) private Street street; //a second level of embedded
Map Street.name to a 16 character STREET_NAME column from the entity class. Note the multiple level syntax here.
@AttributeOverrides({ @AttributeOverride(name="street.name", column=@Column(name="STREET_NAME", length=16)), }) @Embedded private Address address;
Rebuild the module and note the generated database schema. Our custom database mappings are in place.
create table ENTITYEX_BEAR ( id integer generated by default as identity, CITY varchar(16), STATE varchar(16), STREET_NAME varchar(16), STREET_NUMBER integer, FIRST_NAME varchar(16), LAST_NAME varchar(16), primary key (id) );
Put the following test method within the existing JUnit test case.
@Test
public void testEmbeddedObject() {
log.info("testEmbeddedObject");
Bear bear = new Bear();
bear.setName(new Name().setFirstName("Yogi").setLastName("Bear"));
bear.setAddress(new Address()
.setCity("Jellystone Park")
.setState("???")
.setStreet(new Street().setNumber(1).setName("Picnic")));
em.persist(bear);
//flush to DB and get a new instance
em.flush(); em.detach(bear);
Bear bear2 = em.find(Bear.class, bear.getId());
assertEquals("unexpected firstName", bear.getName().getFirstName(), bear2.getName().getFirstName());
assertEquals("unexpected lastName", bear.getName().getLastName(), bear2.getName().getLastName());
assertEquals("unexpected street number",
bear.getAddress().getStreet().getNumber(), bear2.getAddress().getStreet().getNumber());
assertEquals("unexpected street name",
bear.getAddress().getStreet().getName(), bear2.getAddress().getStreet().getName());
assertEquals("unexpected city",
bear.getAddress().getCity(), bear2.getAddress().getCity());
assertEquals("unexpected state",
bear.getAddress().getState(), bear2.getAddress().getState());
}
Rebuild the module with the new test method in place.
-testEmbeddedObject Hibernate: insert into ENTITYEX_BEAR (id, CITY, STATE, STREET_NAME, STREET_NUMBER, FIRST_NAME, LAST_NAME) values (null, ?, ?, ?, ?, ?, ?) Hibernate: select bear0_.id as id7_0_, bear0_.CITY as CITY7_0_, bear0_.STATE as STATE7_0_, bear0_.STREET_NAME as STREET4_7_0_, bear0_.STREET_NUMBER as STREET5_7_0_, bear0_.FIRST_NAME as FIRST6_7_0_, bear0_.LAST_NAME as LAST7_7_0_ from ENTITYEX_BEAR bear0_ where bear0_.id=?
The above capability of mapping multi-nested classes was added in JPA 2.0 and allows us to map more complicated structures to the database.
In this chapter we mapped a nested object within the table used to host and enclosing entity class. Note there are times when the entity is created purely for persistence purposes and the embedded class is the real meat we are after. In that case, the entity class is imply providing the primary key property and embedding the rest.