例えば次のような1:Nのモデルがあったとします。
エンティティクラスは次のような感じですかね。
@Entity public class Shop { @Id private long id; private String shopName; @OneToMany private Set<Employee> employees; ... } @Entity public class Employee { @Id private long id; private String status; @ManyToOne private Shop shop; ... }
このとき、例えば
- 店舗名(Shop#shopName)が"東京"から始まる店舗エンティティを取得したい
- でも、従業員(Shop#employees)は状態(Employee#status)が"ACTIVE"であるものだけをセットしたい
というニーズがあった場合にどうするか。
JPAの標準機能では、fetch joinするエンティティについて条件を指定することはできません。要するに、次のような構文は存在しない、ということです。
SELECT OBJECT(shop) FROM Shop AS shop FETCH JOIN shop.employees emp OF emp.status = 'ACTIVE' ← こんな構文はない WHERE shop.shopName LIKE '東京%'
ちょっと悩んでいたのですが、ここでヒントを得てやり方が判りました。
ここは逆転の発想で、Employeeを検索しに行けばいいのです。
つまりこういうコトです。
SELECT OBJECT(emp) FROM Employee AS emp FETCH JOIN employee.shop shop WHERE emp.status = 'ACTIVE' AND shop.shopName LIKE '東京%'
でもって、結果セットをJavaで再構成すればOK。
private static final String QL_STRING = "SELECT OBJECT(emp) FROM Employee AS emp " + "FETCH " + "JOIN employee.shop shop " + "WHERE " + "emp.status = 'ACTIVE' AND " + "shop.shopName LIKE '東京%'"; public List<Shop> selectShops() { final Query query = getEntityManager().createQuery(QL_STRING); // 従業員を店舗で名寄せする final Map<Long, Shop> foundShops = new HashMap<Long, Shop>(); for (Employee emp : query.getResultList()) { final Shop shop = emp.getShop(); if (!foundShops.containsKey(shop.getId())) { //アタッチされたエンティティをいじると色々面倒なのでコピーしたものを作成。 final Shop foundShop = createCopyOfShop(shop); foundShop.setEmployees(new HashSet<Employee>()); foundShops.put(shop.getId(), foundShop); } final Shop foundShop = foundShops.get(shop.getId()); foundShop.getEmployees().add(emp); } return new ArrayList<Shop>(foundShops.values()); }
あまりスマートじゃないけど、まあこれでいいか、という感じではありますが。
もっと良いやり方をご存じの方がいたら、ぜひ教えてください。