Kohei Nozaki's blog 

Entries tagged [jpa]

Just try to use OrderColumn


Posted on Thursday Jan 30, 2014 at 02:09PM in Technology


It sounds convenience so I just tried it.

Environment

  • WildFly 8.0.0.Final
  • Hibernate 4.3.1
  • PostgreSQL 9.2.4
  • postgresql-9.3-1100.jdbc41.jar

Does Hibernate require intersection entity for @OrderColumn?

According to [2], it said that @OrderColumn doesn't work with relations like that:

Parent side

    @OneToMany(mappedBy = "dept")
    @OrderColumn(name = "pos")
    private List<Employee> employees;

Child side

    @ManyToOne
    @JoinColumn(nullable = false)
    private Dept dept;

I found [1], and it said something like that it working now with recent versions.

And, like said at [2], when I delete mappedBy, it makes intersection entity (dept_employee).

Make example project

Whole project resources are available in GitHub.

Codes that I will mention later are easy to find in these files:

Entities in the project

Test data

Available in GitHub.

Data entry to empty table

log

19:51:55,703 DEBUG [org.hibernate.SQL] (default task-2) select nextval ('hibernate_sequence')
19:51:55,704 DEBUG [org.hibernate.SQL] (default task-2) select nextval ('hibernate_sequence')
19:51:55,705 DEBUG [org.hibernate.SQL] (default task-2) select nextval ('hibernate_sequence')
19:51:55,706 DEBUG [org.hibernate.SQL] (default task-2) select nextval ('hibernate_sequence')
19:51:55,712 DEBUG [org.hibernate.SQL] (default task-2) insert into Dept (deptName, id) values (?, ?)
19:51:55,714 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [VARCHAR] - [Finance]
19:51:55,714 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [BIGINT] - [1]
19:51:55,716 DEBUG [org.hibernate.SQL] (default task-2) insert into Employee (dept_id, firstName, lastName, pos, id) values (?, ?, ?, ?, ?)
19:51:55,716 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [BIGINT] - [1]
19:51:55,716 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [VARCHAR] - [Taro]
19:51:55,717 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [3] as [VARCHAR] - [Yamada]
19:51:55,717 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [4] as [INTEGER] - [null]
19:51:55,717 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [5] as [BIGINT] - [2]
19:51:55,719 DEBUG [org.hibernate.SQL] (default task-2) insert into Employee (dept_id, firstName, lastName, pos, id) values (?, ?, ?, ?, ?)
19:51:55,719 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [BIGINT] - [1]
19:51:55,719 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [VARCHAR] - [Jiro]
19:51:55,719 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [3] as [VARCHAR] - [Suzuki]
19:51:55,719 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [4] as [INTEGER] - [null]
19:51:55,719 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [5] as [BIGINT] - [3]
19:51:55,720 DEBUG [org.hibernate.SQL] (default task-2) insert into Employee (dept_id, firstName, lastName, pos, id) values (?, ?, ?, ?, ?)
19:51:55,720 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [BIGINT] - [1]
19:51:55,720 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [VARCHAR] - [Saburo]
19:51:55,720 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [3] as [VARCHAR] - [Tanaka]
19:51:55,721 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [4] as [INTEGER] - [null]
19:51:55,721 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [5] as [BIGINT] - [4]
19:51:55,721 DEBUG [org.hibernate.SQL] (default task-2) update Employee set pos=? where id=?
19:51:55,721 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [INTEGER] - [0]
19:51:55,722 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [BIGINT] - [2]
19:51:55,723 DEBUG [org.hibernate.SQL] (default task-2) update Employee set pos=? where id=?
19:51:55,723 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [INTEGER] - [1]
19:51:55,723 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [BIGINT] - [3]
19:51:55,724 DEBUG [org.hibernate.SQL] (default task-2) update Employee set pos=? where id=?
19:51:55,724 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [1] as [INTEGER] - [2]
19:51:55,724 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-2) binding parameter [2] as [BIGINT] - [4]

table

jpatest=# select * from dept ;
 id | deptname 
----+----------
  1 | Finance
(1 row)

jpatest=# select * from employee ;
 id | firstname | lastname | pos | dept_id 
----+-----------+----------+-----+---------
  2 | Taro      | Yamada   |   0 |       1
  3 | Jiro      | Suzuki   |   1 |       1
  4 | Saburo    | Tanaka   |   2 |       1
(3 rows)

jpatest=# 

Add an employee (didn't work correctly)

log

20:07:13,014 DEBUG [org.hibernate.SQL] (default task-8) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
20:07:13,015 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [1] as [BIGINT] - [1]
20:07:13,015 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-8) extracted value ([deptName2_0_0_] : [VARCHAR]) - [Finance]
20:07:13,016 TRACE [org.hibernate.type.CollectionType] (default task-8) Created collection wrapper: [org.nailedtothex.jpatest.ordercolumn.Dept.employees#1]
20:07:13,016 DEBUG [org.hibernate.SQL] (default task-8) select nextval ('hibernate_sequence')
20:07:13,022 DEBUG [org.hibernate.SQL] (default task-8) insert into Employee (dept_id, firstName, lastName, pos, id) values (?, ?, ?, ?, ?)
20:07:13,023 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [1] as [BIGINT] - [1]
20:07:13,023 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [2] as [VARCHAR] - [Jane]
20:07:13,023 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [3] as [VARCHAR] - [Doe]
20:07:13,023 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [4] as [INTEGER] - [null]
20:07:13,023 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [5] as [BIGINT] - [1]
20:07:13,024 DEBUG [org.hibernate.SQL] (default task-8) update Employee set pos=? where id=?
20:07:13,024 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [1] as [INTEGER] - [0]
20:07:13,024 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-8) binding parameter [2] as [BIGINT] - [1]

table

jpatest=# select * from dept;
 id  | deptname 
-----+----------
 101 | Finance
(1 row)

jpatest=# select * from employee ;
 id  | firstname | lastname | pos | dept_id 
-----+-----------+----------+-----+---------
 201 | Taro      | Yamada   |   0 |     101
 202 | Jiro      | Suzuki   |   1 |     101
 203 | Saburo    | Tanaka   |   2 |     101
   1 | Jane      | Doe      |   0 |     101
(4 rows)

jpatest=# 
  • Hibernate doesn't fetch max pos, consequently, pos of Jane Doe assigned pos 0 that duplicated.

Add an employee (revised)

  • To overcome the problem which above-mentioned, force Hibernate to fetch collections before addition of new entity through invoke size() method.

log

20:22:23,748 DEBUG [org.hibernate.SQL] (default task-6) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
20:22:23,748 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [BIGINT] - [101]
20:22:23,749 DEBUG [org.hibernate.SQL] (default task-6) select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=?
20:22:23,750 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [BIGINT] - [101]
20:22:23,752 DEBUG [org.hibernate.SQL] (default task-6) select nextval ('hibernate_sequence')
20:22:23,760 DEBUG [org.hibernate.SQL] (default task-6) insert into Employee (dept_id, firstName, lastName, pos, id) values (?, ?, ?, ?, ?)
20:22:23,760 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [BIGINT] - [101]
20:22:23,760 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [2] as [VARCHAR] - [Jane]
20:22:23,760 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [3] as [VARCHAR] - [Doe]
20:22:23,761 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [4] as [INTEGER] - [null]
20:22:23,761 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [5] as [BIGINT] - [1]
20:22:23,762 DEBUG [org.hibernate.SQL] (default task-6) update Employee set pos=? where id=?
20:22:23,762 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [INTEGER] - [0]
20:22:23,762 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [2] as [BIGINT] - [201]
20:22:23,763 DEBUG [org.hibernate.SQL] (default task-6) update Employee set pos=? where id=?
20:22:23,763 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [INTEGER] - [1]
20:22:23,763 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [2] as [BIGINT] - [202]
20:22:23,763 DEBUG [org.hibernate.SQL] (default task-6) update Employee set pos=? where id=?
20:22:23,763 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [INTEGER] - [2]
20:22:23,763 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [2] as [BIGINT] - [203]
20:22:23,764 DEBUG [org.hibernate.SQL] (default task-6) update Employee set pos=? where id=?
20:22:23,764 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [1] as [INTEGER] - [3]
20:22:23,764 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-6) binding parameter [2] as [BIGINT] - [1]

table

jpatest=# select * from employee ;
 id  | firstname | lastname | pos | dept_id 
-----+-----------+----------+-----+---------
 201 | Taro      | Yamada   |   0 |     101
 202 | Jiro      | Suzuki   |   1 |     101
 203 | Saburo    | Tanaka   |   2 |     101
   1 | Jane      | Doe      |   3 |     101
(4 rows)

jpatest=# 
  • Hibernate recognized maximum pos and Jane Doe assigned correct pos.

Delete an employee

log

20:47:53,429 DEBUG [org.hibernate.SQL] (default task-14) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
20:47:53,429 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [1] as [BIGINT] - [101]
20:47:53,430 DEBUG [org.hibernate.SQL] (default task-14) select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=?
20:47:53,430 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [1] as [BIGINT] - [101]
20:47:53,435 DEBUG [org.hibernate.SQL] (default task-14) update Employee set pos=? where id=?
20:47:53,436 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [1] as [INTEGER] - [0]
20:47:53,436 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [2] as [BIGINT] - [201]
20:47:53,436 DEBUG [org.hibernate.SQL] (default task-14) update Employee set pos=? where id=?
20:47:53,436 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [1] as [INTEGER] - [1]
20:47:53,436 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [2] as [BIGINT] - [203]
20:47:53,439 DEBUG [org.hibernate.SQL] (default task-14) delete from Employee where id=?
20:47:53,439 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-14) binding parameter [1] as [BIGINT] - [202]

table

jpatest=# select * from dept ;
 id  | deptname 
-----+----------
 101 | Finance
(1 row)

jpatest=# select * from employee ;
 id  | firstname | lastname | pos | dept_id 
-----+-----------+----------+-----+---------
 201 | Taro      | Yamada   |   0 |     101
 203 | Saburo    | Tanaka   |   1 |     101
(2 rows)

jpatest=#

Swap order

log

21:02:05,979 DEBUG [org.hibernate.SQL] (default task-1) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
21:02:05,980 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [BIGINT] - [101]
21:02:05,981 DEBUG [org.hibernate.SQL] (default task-1) select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=?
21:02:05,981 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [BIGINT] - [101]
21:02:05,983 FINE  [org.nailedtothex.jpatest.ordercolumn.OrderColumnTestDataManipulator] (default task-1) [Employee [id=201, firstName=Taro, lastName=Yamada, pos=0, dept=101], Employee [id=202, firstName=Jiro, lastName=Suzuki, pos=1, dept=101], Employee [id=203, firstName=Saburo, lastName=Tanaka, pos=2, dept=101]]
21:02:05,983 FINE  [org.nailedtothex.jpatest.ordercolumn.OrderColumnTestDataManipulator] (default task-1) [Employee [id=201, firstName=Taro, lastName=Yamada, pos=0, dept=101], Employee [id=203, firstName=Saburo, lastName=Tanaka, pos=2, dept=101], Employee [id=203, firstName=Saburo, lastName=Tanaka, pos=2, dept=101]]
21:02:05,984 FINE  [org.nailedtothex.jpatest.ordercolumn.OrderColumnTestDataManipulator] (default task-1) [Employee [id=201, firstName=Taro, lastName=Yamada, pos=0, dept=101], Employee [id=203, firstName=Saburo, lastName=Tanaka, pos=2, dept=101], Employee [id=202, firstName=Jiro, lastName=Suzuki, pos=1, dept=101]]
21:02:05,987 DEBUG [org.hibernate.SQL] (default task-1) update Employee set pos=? where id=?
21:02:05,987 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [INTEGER] - [0]
21:02:05,987 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [2] as [BIGINT] - [201]
21:02:05,988 DEBUG [org.hibernate.SQL] (default task-1) update Employee set pos=? where id=?
21:02:05,988 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [INTEGER] - [1]
21:02:05,988 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [2] as [BIGINT] - [203]
21:02:05,989 DEBUG [org.hibernate.SQL] (default task-1) update Employee set pos=? where id=?
21:02:05,989 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [1] as [INTEGER] - [2]
21:02:05,989 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-1) binding parameter [2] as [BIGINT] - [202]

table

jpatest=# select * from dept ;
 id  | deptname 
-----+----------
 101 | Finance
(1 row)

jpatest=# select * from employee ;
 id  | firstname | lastname | pos | dept_id 
-----+-----------+----------+-----+---------
 201 | Taro      | Yamada   |   0 |     101
 203 | Saburo    | Tanaka   |   1 |     101
 202 | Jiro      | Suzuki   |   2 |     101
(3 rows)

jpatest=# 

Retrieve from unordered table

log

21:13:55,544 DEBUG [org.hibernate.SQL] (default task-4) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
21:13:55,545 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-4) binding parameter [1] as [BIGINT] - [101]
21:13:55,546 DEBUG [org.hibernate.SQL] (default task-4) select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=?
21:13:55,546 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-4) binding parameter [1] as [BIGINT] - [101]
21:13:55,548 FINE  [org.nailedtothex.jpatest.ordercolumn.OrderColumnTestDataManipulator] (default task-4) [Employee [id=201, firstName=Taro, lastName=Yamada, pos=0, dept=101], Employee [id=202, firstName=Jiro, lastName=Suzuki, pos=1, dept=101], Employee [id=203, firstName=Saburo, lastName=Tanaka, pos=2, dept=101]]

table

jpatest=# select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=101;
 dept_id5_0_0_ | id1_1_0_ | pos4_0_ | id1_1_1_ | dept_id5_1_1_ | firstnam2_1_1_ | lastname3_1_1_ | pos4_1_1_ 
---------------+----------+---------+----------+---------------+----------------+----------------+-----------
           101 |      203 |       2 |      203 |           101 | Saburo         | Tanaka         |         2
           101 |      201 |       0 |      201 |           101 | Taro           | Yamada         |         0
           101 |      202 |       1 |      202 |           101 | Jiro           | Suzuki         |         1
(3 rows)

jpatest=# 
  • Hibernate executed a query that not contains ORDER BY, and returned unordered result set, but I got correctly ordered list (did Java sorted it?).

Reverse the order

log

22:23:11,950 DEBUG [org.hibernate.SQL] (default task-9) select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
22:23:11,950 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [1] as [BIGINT] - [101]
22:23:11,951 DEBUG [org.hibernate.SQL] (default task-9) select employees0_.dept_id as dept_id5_0_0_, employees0_.id as id1_1_0_, employees0_.pos as pos4_0_, employees0_.id as id1_1_1_, employees0_.dept_id as dept_id5_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_, employees0_.pos as pos4_1_1_ from Employee employees0_ where employees0_.dept_id=?
22:23:11,951 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [1] as [BIGINT] - [101]
22:23:11,956 DEBUG [org.hibernate.SQL] (default task-9) update Employee set pos=? where id=?
22:23:11,957 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [1] as [INTEGER] - [0]
22:23:11,957 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [2] as [BIGINT] - [203]
22:23:11,957 DEBUG [org.hibernate.SQL] (default task-9) update Employee set pos=? where id=?
22:23:11,957 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [1] as [INTEGER] - [1]
22:23:11,957 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [2] as [BIGINT] - [202]
22:23:11,958 DEBUG [org.hibernate.SQL] (default task-9) update Employee set pos=? where id=?
22:23:11,958 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [1] as [INTEGER] - [2]
22:23:11,958 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-9) binding parameter [2] as [BIGINT] - [201]

table

jpatest=# select * from employee ;
 id  | firstname | lastname | pos | dept_id 
-----+-----------+----------+-----+---------
 203 | Saburo    | Tanaka   |   0 |     101
 202 | Jiro      | Suzuki   |   1 |     101
 201 | Taro      | Yamada   |   2 |     101
(3 rows)

jpatest=# 

Conclusion

Pros

  • It brings powerful mechanism that developer can reduce some annoying codes to maintain order of the list.

Cons

  • It executes bunch of inefficient queries.
  • Through considering above queries, maybe use of unique index of order column is impossible.

Remarks

  • Not bad for such as low-traffic internal systems.
  • To OrderColumn work correctly, we have to keep all elements fetched when modify the list.

References

  1. [HHH-5732] @OrderColumn not updated if @OneToMany has mappedby defined - Hibernate JIRA
  2. JPA 2.0 @OrderColumn annotation in Hibernate 3.5 - Stack Overflow
  3. JPA Best Practices


Entity Graphを使ってみる


Posted on Sunday Jan 26, 2014 at 04:05PM in Technology


JPA2.1の新機能Entity Graphを使ってみます。SELECT文を実行するときにどこまで階層を掘り下げて拾ってくるかを指定することができるようになり、むだにManaged状態のときにsize()を呼び出したりしなくてよくなる仕組みのようです。

環境

  • Hibernate 4.3.0.Final
  • WildFly8.0.0.CR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4

使い方

  1. エンティティの階層を表すEntity Graphを作ります。JPQLのような名前付きの静的なものとして作っておく他に、動的にコードから作ることも可能。Canonical MetaModelも使えるようです。
  2. Query#setHint()メソッドを使ってEntity Graphを渡してやってからクエリを実行します。ヒント名には2つあり、どちらを使うかで以下のように動作が変わります
    • javax.persistence.fetchgraph: Entity Graphに含まれるフィールドのみロードされる。含まれないフィールドはロードされない。Entity Graphに含まれないフィールドは全てLAZY扱いになる
    • javax.persistence.loadgraph: Entity Graphに含まれるフィールドはLAZYであってもロードされる。含まれないフィールドはそれぞれのフィールドの設定による

前提条件

NativeQueryで関連持ちエンティティをSELECTしてみるで使った資源を使います。エンティティはこれです

試してみる

javax.persistence.fetchgraph

Entity Graphを作る

Employeeクラスにこんなアノテーションを付けます

@NamedEntityGraph(
        name = "onlyFirstNameAndLastName",
        attributeNodes = {
                @NamedAttributeNode("firstName"),
                @NamedAttributeNode("lastName")})

orm.xmlにクエリを定義

    <named-query name="findEmployees">
        <query><![CDATA[
            SELECT
                e
            FROM
                Employee e
        ]]></query>
    </named-query>

テストメソッド

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        EntityGraph<?> entityGraph = em.getEntityGraph("onlyFirstNameAndLastName");
        dumpEmployeeList(em.createNamedQuery("findEmployees", Employee.class)
            .setHint("javax.persistence.fetchgraph", entityGraph)
            .getResultList());
    }

    protected void dumpEmployeeList(List<Employee> employees){
        for(Employee emp : employees){
            System.out.printf("        emp: id=%d, firstName=%s, lastName=%s\n", emp.getId(), emp.getFirstName(), emp.getLastName());               
        }
    }

テストメソッドを実行

javax.persistence.fetchgraphを指定して、Entity GraphではfirstNameとlastNameのみ指定しているので、deptはロードされないと思っていたら…

17:27:12,991 INFO  [stdout] (pool-2-thread-34) Hibernate: select employee0_.id as id1_1_, employee0_.dept_id as dept_id4_1_, employee0_.firstName as firstNam2_1_, employee0_.lastName as lastName3_1_ from Employee employee0_
17:27:12,993 INFO  [stdout] (pool-2-thread-34) Hibernate: select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
17:27:12,994 INFO  [stdout] (pool-2-thread-34) Hibernate: select dept0_.id as id1_0_0_, dept0_.deptName as deptName2_0_0_ from Dept dept0_ where dept0_.id=?
17:27:12,995 INFO  [stdout] (pool-2-thread-34)         emp: id=-1, firstName=Taro, lastName=Yamada
17:27:12,996 INFO  [stdout] (pool-2-thread-34)         emp: id=-2, firstName=Jiro, lastName=Suzuki
17:27:12,996 INFO  [stdout] (pool-2-thread-34)         emp: id=-3, firstName=Saburo, lastName=Tanaka

何故か普通に取ってきてますね。どうも無視されているような気が…。[2]にはエンティティグラフについても言及されているようなのだが未実装という事だろうか。あるいは、この機能はあくまでヒントであって実際にどうなるかはJPAプロバイダの実装によるとかそういう事なのか。

[1]を書いた人のサンプルを見るとEclipseLinkを使っているようなのでEclipseLinkなら期待通りの動作をするかもしれない。

[4]に起票されているが、どうもJPA仕様では強制されてないからバグではないと認識されているようだ。ううむ。

javax.persistence.loadgraph

Entity Graphを作る

Deptクラスにこんなアノテーションを付けます

@NamedEntityGraph(
name = "loadEmployees",
attributeNodes = {
        @NamedAttributeNode("employees")}
)

orm.xmlにクエリを定義

    <named-query name="findDeptsDistinct">
        <query><![CDATA[
            SELECT DISTINCT
                d
            FROM
                Dept AS d
        ]]></query>
    </named-query>

テストメソッド

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        EntityGraph<?> entityGraph = em.getEntityGraph("loadEmployees");
        dumpDeptList(em.createNamedQuery("findDeptsDistinct", Dept.class)
            .setHint("javax.persistence.loadgraph", entityGraph)
            .getResultList());
    }

    protected void dumpEmployeeList(List<Employee> employees){
        for(Employee emp : employees){
            System.out.printf("        emp: id=%d, firstName=%s, lastName=%s\n", emp.getId(), emp.getFirstName(), emp.getLastName());               
        }
    }

    protected void dumpDeptList(List<Dept> list){
        for(Dept dept : list){
            System.out.printf("dept: id=%d, deptName=%s\n", dept.getId(), dept.getDeptName());
            dumpEmployeeList(dept.getEmployees());
        }
    }

テストメソッドを実行

21:03:35,540 INFO  [stdout] (pool-2-thread-4) Hibernate: select distinct dept0_.id as id1_0_0_, employees1_.id as id1_1_1_, dept0_.deptName as deptName2_0_0_, employees1_.dept_id as dept_id4_1_1_, employees1_.firstName as firstNam2_1_1_, employees1_.lastName as lastName3_1_1_, employees1_.dept_id as dept_id4_0_0__, employees1_.id as id1_1_0__ from Dept dept0_ left outer join Employee employees1_ on dept0_.id=employees1_.dept_id
21:03:35,544 INFO  [stdout] (pool-2-thread-4) dept: id=-1, deptName=Sales
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-2, firstName=Jiro, lastName=Suzuki
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-1, firstName=Taro, lastName=Yamada
21:03:35,545 INFO  [stdout] (pool-2-thread-4) dept: id=-2, deptName=Legal
21:03:35,545 INFO  [stdout] (pool-2-thread-4)         emp: id=-3, firstName=Saburo, lastName=Tanaka

めでたくSELECT文が一回になりました。こっちは大丈夫そう。

その他

Entity Graphをorm.xmlで定義しようとすると例外が起きてしまい動作しません。「named-attribute-node.value is mandatory in XML overriding.」と言われます。書き方が悪いのか、あるいは他の原因があるのかは不明。またそのうち調べる。

参考文献

  1. Forward Everyday: JPA 2.1: Entity Graph
  2. ORMツール Hibernare 4.3がリリース,JPA 2.1仕様を実装
  3. FETCH JOIN is still a JOIN « Piotr Nowicki's Homepage
  4. [HHH-8776] Ability for JPA entity-graphs to handle non-lazy attributes as lazy - Hibernate JIRA


NativeQueryで複数のエンティティを同時にSELECTしつつconstructor-resultを使ってみる


Posted on Sunday Jan 26, 2014 at 03:18PM in Technology


NativeQueryでconstructor-resultを使ってSELECTしてみるの続きです。2つのエンティティ分のデータを取得するSELECT文から、エンティティ2つに加えて、DTOクラスのオブジェクト1つをconstructor-resultを使って取得してみます。だからなんだって感じもしますが。

環境

  • Hibernate 4.3.0.Final
  • WildFly8.0.0.CR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4

何をするか

エンティティと関連の構造は前回と同じです。

今回は以下をSELECT一回で取ってきます

  • Employeeエンティティ
  • Deptエンティティ
  • deptName, firstName, lastNameをフィールドにもつDTO

準備

配置図

図中で選択されている資源を作成または編集します。

  • orm.xmlにNamedNativeQuery “findVarious” とResultSetMapping “variousResult” を追加します
  • テストクラスVariousResultSelectTestを作ります
  • DTOクラスEmployeeProfileを作ります
  • テストデータは前回のを流用します

EmployeeProfile.java

package org.arquillian.example;

public class EmployeeProfile {

    private String deptName;
    private String firstName;
    private String lastName;

    public EmployeeProfile(String deptName, String firstName, String lastName) {
        super();
        this.deptName = deptName;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "EmployeeProfile [deptName=" + deptName + ", firstName=" + firstName + ", lastName=" + lastName + "]";
    }

}

orm.xml

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">

    <named-native-query name="findVarious" result-set-mapping="variousResult">
        <query><![CDATA[
            SELECT
                emp.id,
                emp.firstName,
                emp.lastName,
                emp.dept_id,
                dep.deptName
            FROM
                Employee AS emp,
                Dept AS dep
            WHERE
                emp.dept_id = dep.id
        ]]></query>
    </named-native-query>

    <sql-result-set-mapping name="variousResult">
        <entity-result entity-class="org.arquillian.example.Employee"/>
        <entity-result entity-class="org.arquillian.example.Dept">
            <field-result name="id" column="dept_id"/>
            <field-result name="deptName" column="deptName"/>
        </entity-result>
        <constructor-result target-class="org.arquillian.example.EmployeeProfile">
            <column name="deptName" class="java.lang.String" />
            <column name="firstName" class="java.lang.String" />
            <column name="lastName" class="java.lang.String" />
        </constructor-result>
    </sql-result-set-mapping>   

</entity-mappings>

VariousResultSelectTest.java

package org.arquillian.example;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class VariousSelectTest {
    @Deployment
    public static Archive<?> createDeployment() {
        Archive<?> a = ShrinkWrap.create(WebArchive.class, "test.war")
            .addPackage(Employee.class.getPackage())
            .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
            .addAsResource("META-INF/orm.xml", "META-INF/orm.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
            .addAsWebInfResource("jbossas-ds.xml");
        return a;
    }

    @PersistenceContext
    EntityManager em;

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        dumpEntityList(em.createNamedQuery("findVarious").getResultList());
    }

    protected void dumpEntityList(List<?> list){
        for(Object o : list){
            Object[] array = (Object[])o;
            Employee employee = (Employee) array[0];
            Dept dept = (Dept) array[1];
            EmployeeProfile employeeProfile = (EmployeeProfile) array[2];
            System.out.println("");
            System.out.println("---------------------------------");
            System.out.printf("id=%d, firstName=%s, lastName=%s, dept_id=%s, deptName=%s, employee=%s, dept=%s\n",
                    employee.getId(), employee.getFirstName(), employee.getLastName(),
                    dept.getId(), dept.getDeptName(),
                    em.contains(employee), em.contains(dept));
            System.out.println(employeeProfile);
            System.out.println("---------------------------------");
        }
    }
}

実行結果

15:15:55,405 INFO  [stdout] (pool-2-thread-20) Hibernate: SELECT emp.id, emp.firstName, emp.lastName, emp.dept_id, dep.deptName FROM Employee AS emp, Dept AS dep WHERE emp.dept_id = dep.id
15:15:55,409 INFO  [stdout] (pool-2-thread-20) 
15:15:55,409 INFO  [stdout] (pool-2-thread-20) ---------------------------------
15:15:55,409 INFO  [stdout] (pool-2-thread-20) id=-1, firstName=Taro, lastName=Yamada, dept_id=-1, deptName=Sales, employee=true, dept=true
15:15:55,409 INFO  [stdout] (pool-2-thread-20) EmployeeProfile [deptName=Sales, firstName=Taro, lastName=Yamada]
15:15:55,410 INFO  [stdout] (pool-2-thread-20) ---------------------------------
15:15:55,410 INFO  [stdout] (pool-2-thread-20) 
15:15:55,410 INFO  [stdout] (pool-2-thread-20) ---------------------------------
15:15:55,410 INFO  [stdout] (pool-2-thread-20) id=-2, firstName=Jiro, lastName=Suzuki, dept_id=-1, deptName=Sales, employee=true, dept=true
15:15:55,410 INFO  [stdout] (pool-2-thread-20) EmployeeProfile [deptName=Sales, firstName=Jiro, lastName=Suzuki]
15:15:55,410 INFO  [stdout] (pool-2-thread-20) ---------------------------------
15:15:55,410 INFO  [stdout] (pool-2-thread-20) 
15:15:55,410 INFO  [stdout] (pool-2-thread-20) ---------------------------------
15:15:55,411 INFO  [stdout] (pool-2-thread-20) id=-3, firstName=Saburo, lastName=Tanaka, dept_id=-2, deptName=Legal, employee=true, dept=true
15:15:55,411 INFO  [stdout] (pool-2-thread-20) EmployeeProfile [deptName=Legal, firstName=Saburo, lastName=Tanaka]
15:15:55,411 INFO  [stdout] (pool-2-thread-20) ---------------------------------

あんまり意味ないかもしれませんが柔軟に出来ているなぁと。

備考

今回使ったsql-result-set-mappingに、さらに以下を追加してもXML的には不正ではないのですが、

<column-result name="deptName"/>

動かそうとすると何故か以下のような例外が出て動きません。まああまり困ることもなさそうですが。

15:26:45,836 SEVERE [org.jboss.arquillian.protocol.jmx.JMXTestRunner] (pool-2-thread-21) Failed: org.arquillian.example.VariousSelectTest.select: javax.persistence.PersistenceException: org.hibernate.loader.custom.NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias [deptName] during auto-discovery of a native-sql query

参考文献

  1. JavaEE 7 JPA 2.1の新機能 ネイティブクエリの改善 - しんさんの出張所 はてな編


NativeQueryでconstructor-resultを使ってSELECTしてみる


Posted on Sunday Jan 26, 2014 at 02:35PM in Technology


NativeQueryで複数のエンティティを1度にSELECTしてみるの続きです。JPA2.1のNativeQueryの新機能constructor-resultを使ってSELECTしてみます。

constructor-resultはJPQLのNEW句のようなものです。エンティティを拾ってくるクエリでなく、レポート的なのを出すクエリの場合とかに使うとキャストが要らなくなるので便利っぽいです。

環境

  • Hibernate 4.3.0.Final
  • WildFly8.0.0.CR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4

何をするか

エンティティと関連の構造は前回と同じです。

今回はDeptエンティティのdeptNameと、EmployeeのカウントをSELECTしてみます。

準備

配置図

図中で選択されている資源を作成または編集します。

  • orm.xmlにNamedNativeQuery “findDeptNameAndEmployeeCount” とResultSetMapping “deptNameAndEmployeeCountResult” を追加します
  • テストクラスConstructorResultSelectTestを作ります
  • DTOクラスDeptNameAndEmployeeCountを作ります
  • テストデータは前回のを流用します

DeptNameAndEmployeeCount.java

package org.arquillian.example;

public class DeptNameAndEmployeeCount {

    private String deptName;
    private Number employeeCount;

    public DeptNameAndEmployeeCount(String deptName, Number employeeCount) {
        super();
        this.deptName = deptName;
        this.employeeCount = employeeCount;
    }

    @Override
    public String toString() {
        return "DeptNameAndEmployeeCount [deptName=" + deptName + ", employeeCount=" + employeeCount + "]";
    }
}

orm.xml

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">

    <named-native-query name="findDeptNameAndEmployeeCount" result-set-mapping="deptNameAndEmployeeCountResult">
        <query><![CDATA[
            SELECT
                dep.deptName,
                COUNT(dep.deptName) AS employeeCount
            FROM
                Dept AS dep,
                Employee AS emp
            WHERE
                dep.id = emp.dept_id
            GROUP BY
                dep.deptName
        ]]></query>
    </named-native-query>

    <sql-result-set-mapping name="deptNameAndEmployeeCountResult">
        <constructor-result target-class="org.arquillian.example.DeptNameAndEmployeeCount">
            <column name="deptName" class="java.lang.String" />
            <column name="employeeCount" class="java.lang.Number" />
        </constructor-result>
    </sql-result-set-mapping>   

</entity-mappings>
  • column要素のclass属性は省略可みたい(少なくともHibernate4.3.1では動いた)
  • ただcolumn要素自体は省略不可っぽい

ConstructorResultSelectTest.java

package org.arquillian.example;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class ConstructorResultSelectTest {
    @Deployment
    public static Archive<?> createDeployment() {
        Archive<?> a = ShrinkWrap.create(WebArchive.class, "test.war")
            .addPackage(Employee.class.getPackage())
            .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
            .addAsResource("META-INF/orm.xml", "META-INF/orm.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
            .addAsWebInfResource("jbossas-ds.xml");
        return a;
    }

    @PersistenceContext
    EntityManager em;

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        dumpEntityList(em.createNamedQuery("findDeptNameAndEmployeeCount").getResultList());
    }

    protected void dumpEntityList(List<?> list){
        for(Object o : list){
            System.out.println(o);
        }
    }
}

実行結果

14:42:45,278 INFO  [stdout] (pool-2-thread-17) Hibernate: SELECT dep.deptName, COUNT(dep.deptName) AS employeeCount FROM Dept AS dep, Employee AS emp WHERE dep.id = emp.dept_id GROUP BY dep.deptName
14:42:45,281 INFO  [stdout] (pool-2-thread-17) DeptNameAndEmployeeCount [deptName=Legal, employeeCount=1]
14:42:45,282 INFO  [stdout] (pool-2-thread-17) DeptNameAndEmployeeCount [deptName=Sales, employeeCount=2]

キャストが要らないのがいいですね。

続き

NativeQueryで複数のエンティティを同時にSELECTしつつconstructor-resultを使ってみる

参考文献

  1. JavaEE 7 JPA 2.1の新機能 ネイティブクエリの改善 - しんさんの出張所 はてな編


NativeQueryで複数のエンティティを1度にSELECTしてみる


Posted on Sunday Jan 26, 2014 at 01:51PM in Technology


NativeQueryでエンティティとスカラ値を同時にSELECTしてみるの続きです。今回は2つのエンティティを1回のSELECT文で取得してみます。

環境

  • Hibernate 4.3.0.Final
  • WildFly8.0.0.CR1
  • Oracle JDK7u51
  • postgresql-9.3-1100.jdbc41.jar
  • PostgreSQL 9.2.4

何をするか

エンティティと関連の構造は前回と同じです。

今回はEmployeeエンティティとDeptエンティティを同時にSELECTしてみます。

準備

配置図

図中で選択されている資源を作成または編集します。

  • orm.xmlにNamedNativeQuery “findEmployeeWithDeptAtOnce” とResultSetMapping “employeeWithDeptResult” を追加します
  • MultiEntitySelectTestを作ります
  • テストデータは前回のを流用します

orm.xml

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.1"
    xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd">

    <named-native-query name="findEmployeeWithDeptAtOnce" result-set-mapping="employeeWithDeptResult">
        <query><![CDATA[
            SELECT
                emp.id,
                emp.firstName,
                emp.lastName,
                emp.dept_id,
                dep.deptName
            FROM
                Employee AS emp,
                Dept AS dep
            WHERE
                emp.dept_id = dep.id
        ]]></query>
    </named-native-query>

    <sql-result-set-mapping name="employeeWithDeptResult">
        <entity-result entity-class="org.arquillian.example.Employee"/>
        <entity-result entity-class="org.arquillian.example.Dept">
            <field-result name="id" column="dept_id"/>
            <field-result name="deptName" column="deptName"/>
        </entity-result>
    </sql-result-set-mapping>   

</entity-mappings>

ResultSetMappingのfield-result要素で、Deptエンティティのidフィールドの値はクエリ中のdept_idだよ、と教えてやります

MultiEntitySelectTest.java

package org.arquillian.example;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;

import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.junit.Arquillian;
import org.jboss.arquillian.persistence.UsingDataSet;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.EmptyAsset;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(Arquillian.class)
public class MultiEntitySelectTest {
    @Deployment
    public static Archive<?> createDeployment() {
        Archive<?> a = ShrinkWrap.create(WebArchive.class, "test.war")
            .addPackage(Employee.class.getPackage())
            .addAsResource("test-persistence.xml", "META-INF/persistence.xml")
            .addAsResource("META-INF/orm.xml", "META-INF/orm.xml")
            .addAsWebInfResource(EmptyAsset.INSTANCE, "beans.xml")
            .addAsWebInfResource("jbossas-ds.xml");
        return a;
    }

    @PersistenceContext
    EntityManager em;

    @Test
    @Transactional
    @UsingDataSet({"datasets/relativeSelect/dept.yml", "datasets/relativeSelect/employees.yml"})
    public void select() throws Exception {
        dumpEntityList(em.createNamedQuery("findEmployeeWithDeptAtOnce").getResultList());
    }

    protected void dumpEntityList(List<?> list){
        for(Object o : list){
            Object[] array = (Object[])o;
            Employee employee = (Employee) array[0];
            Dept dept = (Dept) array[1];
            System.out.println("");
            System.out.printf("id=%d, firstName=%s, lastName=%s, dept_id=%s, deptName=%s, employee=%s, dept=%s\n",
                    employee.getId(), employee.getFirstName(), employee.getLastName(),
                    dept.getId(), dept.getDeptName(),
                    em.contains(employee), em.contains(dept));
            System.out.println("---------------------------------");
            System.out.println("checking relation references");
            System.out.println(dept.getEmployees());
            System.out.println(employee.getDept());
            System.out.println("---------------------------------");
        }
    }
}

実行結果

4,688 INFO  [stdout] (pool-2-thread-15) Hibernate: SELECT emp.id, emp.firstName, emp.lastName, emp.dept_id, dep.deptName FROM Employee AS emp, Dept AS dep WHERE emp.dept_id = dep.id
14:11:54,691 INFO  [stdout] (pool-2-thread-15) 
14:11:54,692 INFO  [stdout] (pool-2-thread-15) id=-1, firstName=Taro, lastName=Yamada, dept_id=-1, deptName=Sales, employee=true, dept=true
14:11:54,692 INFO  [stdout] (pool-2-thread-15) ---------------------------------
14:11:54,692 INFO  [stdout] (pool-2-thread-15) checking relation references
14:11:54,693 INFO  [stdout] (pool-2-thread-15) [Employee [id=-1, firstName=Taro, lastName=Yamada, dept=Dept [id=-1, deptName=Sales, employees=2]], Employee [id=-2, firstName=Jiro, lastName=Suzuki, dept=Dept [id=-1, deptName=Sales, employees=2]]]
14:11:54,693 INFO  [stdout] (pool-2-thread-15) Dept [id=-1, deptName=Sales, employees=2]
14:11:54,693 INFO  [stdout] (pool-2-thread-15) ---------------------------------
14:11:54,693 INFO  [stdout] (pool-2-thread-15) 
14:11:54,693 INFO  [stdout] (pool-2-thread-15) id=-2, firstName=Jiro, lastName=Suzuki, dept_id=-1, deptName=Sales, employee=true, dept=true
14:11:54,694 INFO  [stdout] (pool-2-thread-15) ---------------------------------
14:11:54,694 INFO  [stdout] (pool-2-thread-15) checking relation references
14:11:54,694 INFO  [stdout] (pool-2-thread-15) [Employee [id=-1, firstName=Taro, lastName=Yamada, dept=Dept [id=-1, deptName=Sales, employees=2]], Employee [id=-2, firstName=Jiro, lastName=Suzuki, dept=Dept [id=-1, deptName=Sales, employees=2]]]
14:11:54,694 INFO  [stdout] (pool-2-thread-15) Dept [id=-1, deptName=Sales, employees=2]
14:11:54,694 INFO  [stdout] (pool-2-thread-15) ---------------------------------
14:11:54,694 INFO  [stdout] (pool-2-thread-15) 
14:11:54,694 INFO  [stdout] (pool-2-thread-15) id=-3, firstName=Saburo, lastName=Tanaka, dept_id=-2, deptName=Legal, employee=true, dept=true
14:11:54,695 INFO  [stdout] (pool-2-thread-15) ---------------------------------
14:11:54,695 INFO  [stdout] (pool-2-thread-15) checking relation references
14:11:54,695 INFO  [stdout] (pool-2-thread-15) [Employee [id=-3, firstName=Saburo, lastName=Tanaka, dept=Dept [id=-2, deptName=Legal, employees=1]]]
14:11:54,696 INFO  [stdout] (pool-2-thread-15) Dept [id=-2, deptName=Legal, employees=1]
14:11:54,696 INFO  [stdout] (pool-2-thread-15) ---------------------------------

SELECT文が1回になったので状況によってはクエリを軽くするのに寄与するかもしれません。また、こういう取り方をしてもお互いの参照はちゃんと設定されていて、実際にアクセスしてもよけいなクエリを実行する事もないようです。賢い。

と思ったのだがorg.hibernate.SQLのログレベルを上げて見ると、別途SELECT文が実行されている気配。persistence.xmlにhibernate.show_sqlを設定しただけでは全てのSQLは出ないという事なのか、あるいは見落としたのか…。PostgreSQL側にSQLをログ出力させる設定を加えて見てみるとこんなのが出ている。

Feb  2 11:35:46 kyle-no-MacBook.local postgres[1120]: [877-1] 2014-02-02 11:35:46 JST LOG:  execute <unnamed>: SELECT emp.id, emp.firstName, emp.lastName, emp.dept_id, dep.deptName FROM Employee AS emp,
 Dept AS dep WHERE emp.dept_id = dep.id
Feb  2 11:35:46 kyle-no-MacBook.local postgres[1120]: [878-1] 2014-02-02 11:35:46 JST LOG:  execute <unnamed>: select employees0_.dept_id as dept_id4_0_0_, employees0_.id as id1_1_0_, employees0_.id as 
id1_1_1_, employees0_.dept_id as dept_id4_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_ from Employee employees0_ where employees0_.dept_id=$1
Feb  2 11:35:46 kyle-no-MacBook.local postgres[1120]: [878-2] 2014-02-02 11:35:46 JST DETAIL:  parameters: $1 = '-1'
Feb  2 11:35:46 kyle-no-MacBook.local postgres[1120]: [879-1] 2014-02-02 11:35:46 JST LOG:  execute <unnamed>: select employees0_.dept_id as dept_id4_0_0_, employees0_.id as id1_1_0_, employees0_.id as 
id1_1_1_, employees0_.dept_id as dept_id4_1_1_, employees0_.firstName as firstNam2_1_1_, employees0_.lastName as lastName3_1_1_ from Employee employees0_ where employees0_.dept_id=$1
Feb  2 11:35:46 kyle-no-MacBook.local postgres[1120]: [879-2] 2014-02-02 11:35:46 JST DETAIL:  parameters: $1 = '-2'

続き

NativeQueryでconstructor-resultを使ってSELECTしてみる

参考文献

  1. Pro JPA 2