Chapter 2. Advanced Mapping

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 concepts:
    • Instance and row
    • Annotation verses XML
    • Owning entity
    • Value mapping
    • JPA ID generation
    • Hibernate ID generation
    • Composite ID
  • Association cardinality:
    • One-to-one association
    • One-to-many association
    • Many-to-many association
    • Self-referencing tables
  • Cascade operations
  • Inheritance:
    • Single table strategy
    • Table per class strategy
    • Joined strategy
  • Custom mapped data type

Mapping concepts

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.

Instances and rows

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:

Instances and rows

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:

Instances and rows

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.

Annotation versus XML

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.

Owning entity

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.

Value mapping

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:

  • They do not have an ID
  • They shall not be shared between entities
  • They cannot be stored independently of the owning entity
  • They may be stored in a separate table
  • They can be used as a composite ID for an entity

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.

JPA 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.

Tip

Keep in mind that, when you save a new entity, you may not have an identifier until after the flush is completed.

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 ID generation

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
}

Composite ID

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.226.120