lishman levelup
«previous  next»


Getting Started
Associations
HQL



Many-To-One Association


So far, our association is unidirectional; we can access the countries set from a Continent object but we cannot directly access the owning Continent for a given Country. We make the association bidirectional by modifying the Country class and adding a many-to-one reference back to the Continent.

@Entity
@Table(name="COUNTRY")
public class Country {

  @Id
  @GeneratedValue
  @Column(name="CTRY_ID")
  private Integer id;

  @Column(name="CTRY_NAME")
  private String name;

  private int area;

  @Transient
  private int rank;

  @ManyToOne
  @JoinColumn(name = "CONT_ID")
  private Continent continent;

  // Accessors

}
The Continent is now reachable directly from the Country object.
Country georgia = (Country) session.load(Country.class, 122);
System.out.println(georgia.getName() + " is in " +
           georgia.getContinent().getName());
This SQL is executed when the continent field is accessed for the first time:
select
  CONTINENT.CONT_ID,
  CONTINENT.CONT_NAME,
  COUNTRY.CTRY_ID,
  COUNTRY.CTRY_NAME,
  COUNTRY.AREA
from
  COUNTRY
left outer join
  CONTINENT
    on COUNTRY.CONT_ID = CONTINENT.CONT_ID
where
  COUNTRY.CTRY_ID=?

Fetching Strategy

We can see that, in this case, all the required data is automatically retrieved with a single SQL statement. This is because the default fetch method for a @OneToOne or a @ManyToOne association is EAGER. We can always change this to LAZY if this is appropriate for our application (for example, if a Continent is rarely accessed from a Country).
@ManyToOne (fetch=FetchType.LAZY)
@JoinColumn(name = "CONT_ID")
private Continent continent;

Maintaining a Bidirectional Association

Now that we have a bidirectional association between Continent and Country, both sides of the relationship will need to be maintained if a new Country is added. It therefore makes sense to include a convenience method on the Continent class to manage the relationship.
public void addCountry(Country country) {
  country.setContinent(this);
  countries.add(country);
}
We add a new country to Europe like this:
session.beginTransaction();

Country serbia = new Country();
serbia.setName("Serbia");
serbia.setArea(34116);

Continent europe = (Continent) session.load(Continent.class, 3);
europe.addCountry(serbia);
session.save(serbia);

session.getTransaction().commit();
However, if we look at the generated SQL we can see a slight problem:
insert into COUNTRY (CONT_ID, CTRY_NAME, AREA, CTRY_ID) values (?, ?, ?, ?)

update COUNTRY set CONT_ID=? where CTRY_ID=?
Because we update both sides of the relationship in our convenience method, Hibernate generates SQL on the COUNTRY table twice, once when we set the Continent on the Country (the insert) and once when we add the Country to the Continent (the update). We need to tell Hibernate that only one side of our bidirectional association is responsible for maintaining the relationship.

The mappedBy attribute on the @OneToMany annotation does just that.
@OneToMany (mappedBy="continent")
@JoinColumn(name = "CONT_ID")
private Set<Country> countries = new HashSet<Country>();
This tells Hibernate that the other side of the association is responsible for managing the relationship, in other words the side where the entity represents the table that contains the foreign key value.

Now only the insert statement will be performed by Hibernate and the redundant update is removed.

Cascading Object State

State changes made to persistent objects, such as saving or deleting, can be automatically propagated to associated objects. This is known as transitive persistence. For example, when a Continent is saved, all the related Country objects are automatically saved too. When a Continent is deleted, all the Country entities belonging to that Continent are removed also.
@OneToMany (mappedBy="continent", cascade={CascadeType.PERSIST, CascadeType.REMOVE})
@org.hibernate.annotations.Cascade( {org.hibernate.annotations.CascadeType.SAVE_UPDATE})
@JoinColumn(name = "CONT_ID")
private Set<Country> countries = new HashSet<Country>();
In this example, the cascade attribute of the @OneToMany annotation specifies that persistence and removal operations on a Continent object are to be cascaded to the associated Country objects. However, when using this annotation, persistence is only applied to related classes when the session.persist() method is used but has no effect on the session.save() method we have used so far.

To enable persistence cascading with session.save() we must include the Hibernate specific @Cascade annotation as shown above. Now persistence cascading is enabled with both the session.persist() and session.save() methods.

This example shows that standard JPA annotations (@OneToMany) and Hibernate extensions (@Cascade) can be mixed and matched in the same application. It is considered good practice to prefix Hibernate annotations with the full package name (@org.hibernate.annotations.Cascade) as it clearly identifies that a vendor extension is being used.

Now this Country instance is automatically saved to the database because it is associated with a Continent class with cascading persistence. There is no need to save the Country object explicitly.
session.beginTransaction();

Country serbia = new Country();
serbia.setName("Serbia");
serbia.setArea(34116);

Continent europe = (Continent) session.load(Continent.class, 3);
europe.addCountry(serbia);

session.getTransaction().commit();
This SQL is automatically executed by Hibernate when the transaction is committed:
insert into COUNTRY (AREA, CONT_ID, CTRY_NAME, CTRY_ID)
  values (?, ?, ?, ?)
The removal of a Continent is also cascaded down to the countries collection and the appropriate rows are deleted from the COUNTRY table.
session.beginTransaction();

Continent oceania = (Continent) session.load(Continent.class, 6);
session.delete(oceania);

session.getTransaction().commit();
delete from COUNTRY where CTRY_ID=?
delete from COUNTRY where CTRY_ID=?
delete from CONTINENT where CONT_ID=?
»