In this chapter, we will explore mapping concepts. This is at the core of the Object-Relational Mapping challenge to correctly map Java objects and classes to their persistence representation. As discussed in the previous chapter, the object-oriented world has a lot in common with the relational world, attributes, types, and so on. The ORM solutions, such as Hibernate, were created to address their notable differences, inheritance, composition, object reference and identification, and much more. After reading this chapter, you will be able to correctly map database identification to a class attribute using primitive data types or a more complex Java class. You will also see how to create associations between objects and annotate them correctly so that Hibernate has enough information about the relationships between table data. Furthermore, you will be able to instruct Hibernate to perform cascaded operations and map classes as part of an inheritance model. Finally, you will see how to work with enumerated types and create your own custom type.
In this chapter, we will cover the following topics:
Mapping objects to relations is the core mission of Hibernate, and as we discussed previously, this can be a difficult challenge that can only be overcome by understanding the fundamental concepts.
Let's explore mapping concepts by reviewing some of the fundamentals of relational databases, such as relational algebra, simple and composite IDs, join tables, and foreign keys.
The origin of relational database concepts is Relational Algebra, which is based on Set Theory. Just as we think of objects as instances of classes, you should also think of a database row (a tuple) as an element of a set (the relation) that is defined by the database tab.
For example, if your object graph contains an instance of the Person
entity class that represents John
and another instance that represents Sara
, you should think of these as two rows in the Person
table.
In both cases, by being an instance of a class or a row of a table, they agree to possess certain attributes, such as identification, first name, and last name:
Obviously, in real-life scenarios, we always have more than one table, which are almost always associated with each other, whose corresponding object graph has a hierarchy of objects that belong to the same or different classes. For example, in the following figure, the instance of the Person
class with id:2
is the owner of two other instances of the same class; Alex
is the parent of both John
and Sara
. It also co-owns the "m" address instance whose other owner is the John
instance, indicating that Alex
and John
both live at the same house and Sara
lives at a different location. The following figure shows how an object graph compares with a relational model:
In this example, we see an additional table that defines the association between a parent and a child. The PARENT_CHILD
table only defines the hierarchal association and on its own doesn't have any significance. This is called a join table
or an association table
. This table doesn't represent an entity, and it only exists to define the association between the entities. This association is declared in Java, using an annotation or a mapping configuration.
You may have noticed that the PERSON
and PARENT_CHILD
tables seem unnecessarily normalized; that is, you can simply add a PARENT
column to the PERSON
table to create a self-referencing table, which will perform the same trick. (You'll see a mapping example of self-referencing tables later in this chapter). This is simply a design decision and the correct design depends on the situation. For example, if the entity in the object graph can have more than one parent, for example, in case of associating a father and mother, or with many students having many teachers, a join
table is clearly a better solution and sometimes unavoidable. In other cases where you may only have a small percentage of data tuples in your set that are associated with other tuples of the same set, using a self-referencing table will leave holes in the underlying data structure. (Many rows will have a NULL
value for the PARENT
column.) Use your best judgment.
In Hibernate, any Plain Ole Java Object
(POJO
) can be treated as an entity. However, you still have to provide a mapping configuration between the class attributes or methods and database columns and rows. This configuration is done using an annotation or a Hibernate mapping XML file, hbm.xml
.
Consider an Address
entity:
public class Address { private int id; private String street; private String city; private String state; private String zipCode; // getters and setters }
The following shows a sample Hibernate mapping XML file, called Address.hbm.xml
, to map this entity to the corresponding database relation:
<hibernate-mapping> <class name="com.packt.hibernate.ch2.listing01.Address" table="ADDRESS"> <id name="id" type="int" column="id"> <generator class="native" /> </id> <property name="street" column="STREET" type="string"/> <property name="city" column="CITY" type="string"/> <property name="state" column="STATE" type="string"/> <property name="zipCode" column="ZIP_CODE" type="string"/> </class> </hibernate-mapping>
You need to add the Address.hbm.xml
file to the main Hibernate configuration, and as long as the XML file is located in the class path, for example, in the resources directory, then Hibernate can find it:
<hibernate-configuration> <session-factory> <property name="connection.driver_class"> org.postgresql.Driver </property> <property …/> <property …/> <mapping resource="Address.hbm.xml"/> </session-factory> </hibernate-configuration>
Alternatively, you can use an annotation to create this mapping. Consider the following entity:
@Entity public class Person { @Id @GeneratedValue private long id; private String firstname; private String lastname; private String ssn; private Date birthdate; @ManyToOne private Address address; // getters and setters }
As using an annotation is the preferred method for developers, we will mainly use annotations throughout this book.
When you read through JPA or Hibernate JavaDocs, you may come across the term owning entity. It is important to understand this when you deal with the various types of associations (one-to-one, one-to-many, and so on) as well as Cascade operations. One way to think about an owning entity, in an association, is to think about the entity that directly knows of all the other entities in any association. For example, in the object graph earlier, the Object with ID 2 (Alex
) is clearly the owning entity of the other person instances because it knows about Object ID 5 (John
) and 128 (Sara
), but the children don't know about each other without going through the parent. Furthermore, in the person-address association, the person instance is the owning entity as the address by itself is meaningless.
Another way to interpret an owning entity is by the directionality of the association. If an entity A is fully informed of an association, for example, by having a reference to another entity B, and B has no knowledge of the association, then their association is unidirectional and A is the owning entity.
The definition of an owning entity is even more evident when we discuss value objects, which we'll do in the next section.
Not every Java object that is persisted or fetched has to be declared as an Entity. You may have a class that represents an encapsulation of a set of data (values) that independently doesn't have any significance. However, if you associate this with an entity that can be referenced by an ID, then it becomes significant. This is known as a value type.
Value types have the following properties:
A good example of a value type is the address
data, house, street, city, state, and so on. You can create a class called Address
with these properties and use the Address
type as a property in your entity class. Another good example is metrics and analytics—for instance GPS coordinates or daily statistics, such as the opening and closing price, the day's high and low, volume, and so on.
You do have to carefully decide what should be treated as a value type in your design as it depends on the business domain. For example, in a problem domain where people are customers and goods are items being sold, it's very likely that address can be treated as a value type. However, in a different domain, for example a real estate search engine, the address is most likely the most important Entity in the database.
You can use the @Embeddable
annotation to declare a value type, and then, in your entity, when you use the value type to declare a property, you can use the @Embedded
annotation. If you don't use annotation, use the <component/>
tag in your mapping configuration.
Consider the following example, where DailyStats
is declared as a value type. Note that this class has no ID:
@Embeddable public class DailyStats { private double openingPrice; private double closingPrice; private double high; private double low; private long volume; // getters and setters }
This is then used as a type to declare a property in another class:
@Entity public class DailyStockPrice { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private Date date; @Embedded private DailyStats dailyStats; // getters and setters public DailyStats getDailyStats() { if (dailyStats == null) { return null; } DailyStats copy = new DailyStats(); copy.setOpeningPrice(dailyStats.getOpeningPrice()); copy.setClosingPrice(dailyStats.getClosingPrice()); copy.setHigh(dailyStats.getHigh()); copy.setLow(dailyStats.getLow()); copy.setVolume(dailyStats.getVolume()); return copy; } }
As you can see, the getter and setter methods are not shown here, except one: getDailyStats()
. This method doesn't return the instance that is owned by this entity, it returns a copy. This is to ensure that the value type is not accidentally shared between entities.
If you wish to store the value type in its own table, you can do this by declaring a secondary table and overriding the attributes of the embedded value type to reference the columns of the secondary table, as shown here:
@Entity @SecondaryTable( name = "daily_stats", pkJoinColumns = { @PrimaryKeyJoinColumn(name="id") }) public class DailyStockPrice { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private Date date; @Embedded @AttributeOverrides({ @AttributeOverride(name="openingPrice", column=@Column(name="openingPrice", table="daily_stats")), @AttributeOverride(name="closingPrice", column=@Column(name="closingPrice", table="daily_stats")), @AttributeOverride(name="high", column=@Column(name="high", table="daily_stats")), @AttributeOverride(name="low", column=@Column(name="low", table="daily_stats")), @AttributeOverride(name="volume", column=@Column(name="volume", table="daily_stats")) }) private DailyStats dailyStats; // getters and setters }
This will cause Hibernate to perform a left outer join operation between the primary and the secondary table when it fetches an entity that has a secondary table. (You can also use this trick to force Hibernate to perform arbitrary joins between tables.)
Later in this chapter, we will see how to use value types as composite keys for an entity when we discuss ID generation.
As mentioned in the previous chapter, all entities require an identifier. There are two main concepts to consider when dealing with entity identifiers: the structure and generator strategies.
Most of the time, your table has a single primary key column that can simply be mapped to a class field or property of any primitive type, as well as the String
and date
types. If the primary key is defined by more than one column, you need to use a composite ID structure, which is discussed in a later section.
When your table has a single column as the primary key, simply use the @Id
annotation and tell Hibernate which strategy to use to generate identifiers.
In the JPA world, there are only four types of generation strategy: AUTO
, IDENTITY
, SEQUENCE
, and TABLE
. All of these are well explained in the JPA JavaDoc, except the TABLE
strategy, which might require a bit of an explanation.
The TABLE
generation strategy is used in conjunction with the @TableGenerator
annotation. You can allocate a table in your schema to hold all of your sequences and then instruct JPA (in this case, Hibernate) to use this table. Consider the following classes:
@Entity public class Player { @Id @TableGenerator(name = "TABLE_GENERATOR", table = "ALL_SEQUENCES", pkColumnName = "TABLE_NAME", valueColumnName = "NEXT_ID", allocationSize=1) @GeneratedValue(strategy = GenerationType.TABLE, generator = "TABLE_GENERATOR") private long id; private String name; // getters and setters } @Entity public class Game { @Id @TableGenerator(name = "TABLE_GENERATOR", table = "ALL_SEQUENCES", pkColumnName = "TABLE_NAME", valueColumnName = "NEXT_ID", allocationSize=1) @GeneratedValue(strategy = GenerationType.TABLE, generator = "TABLE_GENERATOR") private long id; private String name; // getters and setters }
They both use the same generator, defined by the @TableGenerator
annotation, which points to the same sequence table, called ALL_SEQUENCES
. After you insert two new players (in the Player
table) and three new games (in the Game
table), this is what ALL_SEQUENCES
table will look like:
TABLE_NAME |
NEXT_ID |
---|---|
Player |
3 |
Game |
4 |
Clearly, additional SQL will be executed when you use the table generation strategy. This has little performance impact if you are not inserting new entities at a high volume; otherwise, you should be mindful of this in cases where a large number of entities are being inserted.
Hibernate provides additional strategies to generate values for ID columns. Each one has a special feature that can be used to solve specific problems. To use the strategies that are provided by Hibernate, first you need to create a custom generator using the @GenericGenerator
annotation in Hibernate and then, in the @GeneratedValue
, refer to your custom generator:
@Entity @org.hibernate.annotations.GenericGenerator ( name= "custom-generator", strategy="sequence" ) public class ShoppingCart { @Id @GeneratedValue(generator="custom-generator") private long id; private Date createdTime; // getters and setters }
The generator strategies that are only available in Hibernate are increment
, hilo
, seqhilo
, uuid
, uuid2
, guid
, native
, assigned
, select
, foreign
, and sequence-identity
. We will only discuss the UUID and foreign generation strategy.
The UUID strategy implements the specifications in RFC 4122. This is an ID that is presumably unique universally. However, in reality this is only unique within a domain and it is designed so that unique ID generation is not a central task and can be accomplished by any node in a distributed environment.
Another very useful strategy is the use of foreign keys. In a one-to-one association, the entities are tightly connected and it makes sense to pick the same identification for both ends of the association. The code here shows an example of how to achieve this:
@Entity public class Circle { @Id @GeneratedValue private long id; private float radius; @OneToOne @PrimaryKeyJoinColumn private Center center; // getters and setters } @Entity @org.hibernate.annotations.GenericGenerator(name = "custom-generator", strategy = "foreign", parameters = { @Parameter(name = "property", value = "circle") }) public class Center { @Id @GeneratedValue(generator = "custom-generator") private long id; private float xPos; private float yPos; @OneToOne @PrimaryKeyJoinColumn private Circle circle; // getters and setters }
We will discuss the @OneToOne
association later in this chapter. There is actually an easy way to accomplish the foreign key strategy in JPA:
@Entity public class Car implements Serializable{ @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String model; // getters and setters } @Entity public class Engine implements Serializable { @Id @OneToOne @JoinColumn(name = "car_id") private Car car; private long size; // getters and setters }
In cases where more than one attribute is required to uniquely identify an entity, you'll need to use a composite ID. Hibernate allows you to annotate multiple properties with the @Id
annotation to create a composite ID. JPA requires the use of a component (embeddable) and the @EmbeddedId
annotation. You must always implement equals
and hashCode
for composite IDs (refer to the identity discussion in the previous chapter):
@Embeddable public class RoomId implements Serializable { private long roomNumber; private long buildingNumber; // getters and setters @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (buildingNumber ^ (buildingNumber >>> 32)); result = prime * result + (int) (roomNumber ^ (roomNumber >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; RoomId other = (RoomId) obj; if (buildingNumber != other.buildingNumber) return false; if (roomNumber != other.roomNumber) return false; return true; } } @Entity public class Room implements Serializable { @EmbeddedId private RoomId id; private double length; private double width; // getters and setters }
As mentioned earlier, with Hibernate, you don't have to use an embedded ID. You can simply annotate the fields that you would like to include in your composite ID. You still have to implement equals
and hashCode
:
@Entity public class Room implements Serializable { @Id private long roomNumber; @Id private long buildingNumber; private double length; private double width; // getters and setters // don't forget equals and hashCode }
Later, you'll see how to create a more complex composite ID for an entity that is associated with another entity whose ID is also used in the composition.
3.15.226.120