lish level up
«previous  next»


Getting Started
Associations
HQL



Joins in Hibernate Query Language

Inner Join

Earlier we saw that entities can be joined using implicit association joining. Now we join the same two entities explicitly, using the join keyword.
List<Object[]> africanContinents = session.createQuery(
      "from Continent cont join cont.countries ctry " +
      "where cont.name = 'Africa'")
      .list();
Because we haven't included a select clause in our query, an Object array is returned which consists of two objects per row; a Continent object and a Country object.

The underlying tables are joined using an inner join.
select
  COUNTRY.CTRY_ID,
  COUNTRY.AREA,
  CONTINENT.CONT_ID,
  CONTINENT.CONT_NAME,
  COUNTRY.CURRENCY,
  COUNTRY.CTRY_NAME,
  COUNTRY.POP,
  COUNTRY.POP_UPD_ON
from
  CONTINENT
inner join
  COUNTRY
    on CONTINENT.CONT_ID=COUNTRY.CONT_ID
where
  CONT_NAME='Africa'

In this next example we use projection to return just the continent names, and the distinct keyword to remove any duplicates (without this a continent name would be duplicated if it contained multiple large countries).
List<String> continentsWithBigCountries = session.createQuery(
      "select distinct cont.name " +
      "from Continent cont join cont.countries ctry " +
      "where ctry.area > 100000")
      .list();
This SQL also uses an inner join:
select
  distinct CONT_NAME
from
  CONTINENT
inner join
  COUNTRY
    on CONTINENT.CONT_ID=COUNTRY.CONT_ID
where
  AREA>100000

Outer Join

The previous join statements used inner joins, which is the default. To perform an outer join use the left join clause.
List<Object[]> allContinentsAndCountries = session.createQuery(
      "select cont.name, nvl(ctry.name, '[none]') " +
      "from Continent cont left join cont.countries ctry " +
      "with ctry.area > 100000 " +
      "order by cont.name")
      .list();
This generates a left outer join in the SQL.
select
  CONT_NAME,
  nvl(CTRY_NAME, '[none]')
from
  CONTINENT
left outer join
  COUNTRY
    on CONTINENT.CONT_ID=COUNTRY.CONT_ID
    and (
      AREA>100000
    )
order by
  CONT_NAME
We use an outer join in this example to ensure that at least one row is returned for every continent. We also include the with clause to supply an extra join condition.

An inner join would return a continent name, only if there is at least one matching country. The outer join version returns all continent names at least once, with a null country name if no matching countries are found.

Compare the output from the SQL using both join types:

Inner Join
CONT_NAME CTRY_NAME
Africa Gabon
Europe Germany
Oceania Australia
Oceania New Zealand
Outer Join
CONT_NAME CTRY_NAME
Africa Gabon
Antarctica [none]
Asia [none]
Europe Germany
North America [none]
Oceania Australia
Oceania New Zealand
South America [none]

We use the Oracle NVL function, which is defined in the Oracle 10g dialect, to convert any null country names to '[none]'.

Theta-Style Joins

Theta-style joins use a more traditional join syntax by specifying a comma separated list of classes in the from clause and the join condition in the where clause.

This query returns any continents that share a name with a country:
List<Continent> sameNames = session.createQuery(
      "select cont " +
      "from Continent cont, Country ctry " +
      "where cont.name = ctry.name")
      .list();
The from and where clauses are almost identical in the generated SQL.
select
  CONTINENT.CONT_ID,
  CONTINENT.CONT_NAME
from
  CONTINENT,
  COUNTRY
where
  CONTINENT.CONT_NAME=COUNTRY.CTRY_NAME
The main reason to use theta-style syntax is to specify a join that is not mapped with an association.

Fetch

Suppose we retrieve a Continent object using HQL knowing that we will be accessing the Country objects that belong to that Continent. We join to the Country class to retrieve the data in one SQL statement.
Continent europe = (Continent) session.createQuery(
      "select cont " +
      "from Continent cont join cont.countries " +
      "where cont.name = 'Europe'")
      .uniqueResult();
The uniqueResult() method generates SQL which includes a join to the COUNTRY table.
select
  CONTINENT.CONT_ID,
  CONTINENT.CONT_NAME
from
  CONTINENT
inner join
  COUNTRY
    on CONTINENT.CONT_ID=COUNTRY.CONT_ID
where
  CONT_NAME='Europe'
However, if we take a look at the select clause, we can see that no Country data has been returned.

Now suppose we wish to see the number of countries in Europe.
 
europe.getCountries().size()
 
When we run this statement, the following SQL is triggered:
select
  CTRY_ID,
  AREA,
  CONT_ID,
  CURRENCY,
  CTRY_NAME,
  POP,
  POP_UPD_ON
from
  COUNTRY
where
  CONT_ID=?
The join to cont.countries in the first SQL statement appears to be redundant. This is because, by default, Hibernate returns a proxy for the Country objects.

To eagerly fetch the Country objects using a single SQL statement, add the fetch keyword to the HQL query.
Continent europe = (Continent) session.createQuery(
      "select cont " +
      "from Continent cont join fetch cont.countries " +
      "where cont.name = 'Europe'")
      .uniqueResult();
This time, the following SQL is generated by uniqueResult() and all the objects are populated in the countries collection, immediately:
select
  CONTINENT.CONT_ID,
  COUNTRY.CTRY_ID ,
  CONTINENT.CONT_NAME,
  COUNTRY.AREA,
  COUNTRY.CURRENCY,
  COUNTRY.CTRY_NAME,
  COUNTRY.POP,
  COUNTRY.POP_UPD_ON
from
  CONTINENT
inner join
  COUNTRY
    on CONTINENT.CONT_ID=COUNTRY.CONT_ID
where
  CONT_NAME='Europe'
The getCountries().size() method now accesses the in memory list without executing any further SQL.
»
See Also
 
Quick Start
Get up and running in minutes with all the SQL and Java code from this section

Feedback
If you have any comments or suggestions about level up, please visit lishblog or email mark.lishman@googlemail.com