• No results found

Making classes embeddable

Mapping value types

5.2 Mapping embeddable components

5.2.2 Making classes embeddable

Java has no concept of composition—a class or property can’t be marked as a compo- nent or composition life cycle. The only difference from an entity is the database iden- tifier: a component class has no individual identity; hence, the component class requires no identifier property or identifier mapping. It’s a simple POJO, as you can see in the following listing.

@Embeddable

public class Address { @NotNull

@Column(nullable = false) protected String street; @NotNull

@Column(nullable = false, length = 5) protected String zipcode;

@NotNull

@Column(nullable = false) protected String city; protected Address() { }

public Address(String street, String zipcode, String city) { this.street = street;

this.zipcode = zipcode; this.city = city; }

public String getStreet() { return street;

}

public void setStreet(String street) { this.street = street;

}

Listing 5.6 Address class: an embeddable component

PATH: /model/src/main/java/org/jpwh/model/simple/Address.java @Embeddable instead of @Entity

B

Ignored for DDL

generation Used for DDL generation

Overrides VARCHAR(255)

No-args constructor

C

public String getZipcode() { return zipcode;

}

public void setZipcode(String zipcode) { this.zipcode = zipcode;

}

public String getCity() { return city;

}

public void setCity(String city) { this.city = city;

} }

B

Instead of @Entity, this component POJO is marked with @Embeddable. It has no iden- tifier property.

C

Hibernate calls this no-argument constructor to create an instance and then popu- lates the fields directly.

D

You can have additional (public) constructors for convenience.

The properties of the embeddable class are all by default persistent, just like the prop- erties of a persistent entity class. You can configure the property mappings with the same annotations, such as @Column or @Basic. The properties of the Address class map to the columns STREET, ZIPCODE, and CITY and are constrained with NOT NULL.

That’s the entire mapping. There’s nothing special about the User entity:

@Entity

@Table(name = "USERS")

public class User implements Serializable { @Id

@GeneratedValue(generator = Constants.ID_GENERATOR) protected Long id;

public Long getId() { return id;

PATH: /model/src/main/java/org/jpwh/model/simple/User.java

Issue: Hibernate Validator doesn’t generate NOT NULL constraints At the time of writing, an open issue remains with Hibernate Validator: Hibernate won’t map @NotNull constraints on embeddable component properties to NOT NULL constraints when generating your database schema. Hibernate will only use @Not- Null on your components’ properties at runtime, for Bean Validation. You have to map the property with @Column(nullable = false) to generate the constraint in the schema. The Hibernate bug database is tracking this issue as HVAL-3.

}

protected Address homeAddress; public Address getHomeAddress() { return homeAddress;

}

public void setHomeAddress(Address homeAddress) { this.homeAddress = homeAddress;

}

// ...

}

Hibernate detects that the Address class is annotated with @Embeddable; the STREET, ZIPCODE, and CITY columns are mapped on the USERS table, the owning entity’s table. When we talked about property access earlier in this chapter, we mentioned that embeddable components inherit their access strategy from their owning entity. This means Hibernate will access the properties of the Address class with the same strategy as for User properties. This inheritance also affects the placement of mapping annota- tions in embeddable component classes. The rules are as follows:

 If the owning @Entity of an embedded component is mapped with field access, either implicitly with @Id on a field or explicitly with @Access(AccessType .FIELD) on the class, all mapping annotations of the embedded component class are expected on fields of the component class. Hibernate expects annota- tions on the fields of the Address class and reads/writes the fields directly at runtime. Getter and setter methods on Address are optional.

 If the owning @Entity of an embedded component is mapped with property access, either implicitly with @Id on a getter method or explicitly with @Access(AccessType.PROPERTY) on the class, all mapping annotations of the embedded component class are expected on getter methods of the component class. Hibernate then reads and writes values by calling getter and setter meth- ods on the embeddable component class.

 If the embedded property of the owning entity class—User#homeAddress in the last example—is marked with @Access(AccessType.FIELD), Hibernate expects annotations on the fields of the Address class and access fields at runtime.

 If the embedded property of the owning entity class—User#homeAddress in the last example—is marked with @Access(AccessType.PROPERTY), Hibernate expects annotations on getter methods of the Address class and access getter and setter methods at runtime.

 If @Access annotates the embeddable class itself, Hibernate will use the selected strategy for reading mapping annotations on the embeddable class and run- time access.

The Address is @Embeddable; no annotation is needed here.

There’s one more caveat to remember: there’s no elegant way to represent a null ref- erence to an Address. Consider what would happen if the columns STREET, ZIPCODE, and CITY were nullable. When Hibernate loads a User without any address informa- tion, what should be returned by someUser.getHomeAddress()? Hibernate returns a null in this case. Hibernate also stores a null embedded property as NULL values in all mapped columns of the component. Consequently, if you store a User with an “empty” Address (you have an Address instance but all its properties are null), no Address instance will be returned when you load the User. This can be counterintui- tive; on the other hand, you probably shouldn’t have nullable columns anyway and avoid ternary logic.

You should override the equals() and hashCode() methods of Address and com- pare instances by value. This isn’t critically important as long as you don’t have to compare instances: for example, by putting them in a HashSet. We’ll discuss this issue later, in the context of collections; see section 7.2.1.

In a more realistic scenario, a user would probably have separate addresses for dif- ferent purposes. Figure 5.1 showed an additional composition relationship between User and Address: the billingAddress.