Querying
★クエリの種類(4種)
動的クエリ 実行時に動的に指定するJPQLクエリ文字列
Query query = em.createQuery("SELECT b FROM book b WHERE b.title = :title");
TypedQuery<Book> query
= em.createQuery("SELECT b FROM book b = ?1", Book.class);
query.setParameter("title", "xxx");
query.setParameter(1, "xxx");
//query.setParameter("date", new java.util.Date(), TemporalType.DATE);
query.setMaxResults(10);
List<Book> books = query.getResultList();
Book book = query.getSingleResult();
Query query = em.createQuery(...).setParameter(...).setMaxResults(10);
名前付きクエリ 静的な名前付きクエリで実行時に変更不可
@Entity
@NamedQuery(name-="book.findAll", query="SELECT b FROM book b")
@NamedQueries({
@NamedQuery(name-="book.findAll", query="SELECT b FROM book b"),
@NamedQuery(name-="book.findByTitle", query="SELECT b FROM book b WHERE b.title = :title")
})
public class Book {
...
}
Query query = em.createNamedQuery("book.findAll");
Query query = em.createNamedQuery("SELECT b FROM book b");
TypedQuery<Book> query = em.createNamedQuery("book.findAll", Book.class);
List<Book> books = query.getResultList();
ネイティブクエリ ネイティブSQL文を実行
Query query = em.createNativeQuery("SELECT * FROM book b", Book.class);
List<Book> books = query.getResultList();
@Entity
@NamedNativeQuery(name-="book.findAll", query="SELECT * FROM book b")
public class Book {
...
}
Query query = em.createNativeQuery("book.findAll");
Criteria API オブジェクト指向のプログラミングによるクエリ
>CriteriaBuilder
createQuery()
createQuery(Class)
createTupleQuery()
createCriteriaDelete(Class) JPA 2.1から
createCriteriaUpdate(Class) JPA 2.1から
>Query clauses
SELECT clause select, distinct, multiselect, array, tuple, construct
FROM clause from, join, fetch
WHERE clause where
GROUP BY / HAVING clauses groupBy, having, count, sum, avg, min, max, ...
ORDER BY clause orderBy, Order, asc, desc
>Query expressions
Literals and Dates literal, nullLiteral, currentDate, ...
Paths, navigation and types get, type
Arithmetic expressions sum, diff, prod, quot, mod, abs, neg, sqrt
String expressions like, length, locate, lower, upper, concat, substring, ...
Collection expressions isEmpty, isNotEmpty, isMember, isNotMember, size
Comparison expressions equal, notEqual, gt, ge, lt, le, between, isNull, ...
Logical expressions and, or, not, isTrue
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Book> query = builder.createQuery(Book.class);
Root<Book> c = query.from(Book.class);
query.select(c).where(builder.equal(c.get("title"), "xxx"));
query.select(c).where(builder.greaterThan(c.get("id").as(Integer.class), 1001));
サブクエリの例
TypedQuery<Movie> query = entityManager.createQuery(
"SELECT m FROM movie m JOIN FETCH m.product" +
" WHERE m.product.category IN" +
" (SELECT s.xxx FROM sub s WHERE s.yyy >= 300)", Movie.class);
List<Movie> list = query.getResultList();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Movie> criteriaQuery =
criteriaBuilder.createQuery(Movie.class);
Root<Movie> from = criteriaQuery.from(Movie.class);
Path<Long> path = from.join("product").get("category");
from.fetch("product");
CriteriaQuery<Movie> select = criteriaQuery.select(from);
Subquery<Long> subquery = criteriaQuery.subquery(Long.class);
Root<Sub> fromSub = subquery.from(Sub.class);
subquery.select(fromSub.get("xxx").as(Long.class));
subquery.where(
criteriaBuilder.ge(fromSub.get("yyy").as(Integer.class), 300));
select.where(criteriaBuilder.in(path).value(subquery));
TypedQuery<Movie> typedQuery = entityManager.createQuery(select);
List<Movie> resultList = typedQuery.getResultList();
assertEquals(list, resultList);
クエリの方法
CriteriaBuilder cb = ...;
// 方法1
CriteriaQuery<CustomerDetails> cq = cb.createQuery(CustomerDetails.class);
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.select(cb.construct(
CustomerDetails.class,
c.get(Customer_.name),
c.get(Customer_.address),
c.get(Customer_.age)));
TypedQuery<CustomerDetails> tq = em.createQuery(cq);
CustomerDetails result = tq.getSingleResult();
// 方法2
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
//CriteriaQuery<Tuple> cq = cb.createQuery(Tuple.class);
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.select(cb.tuple(
c.get(Customer_.name),
c.get(Customer_.address),
c.get(Customer_.age)));
あるいは
cq.multiselect(
c.get(Customer_.name),
c.get(Customer_.address),
c.get(Customer_.age));
TypedQuery<Tuple> tq = em.createQuery(cq);
Tuple result = tq.getSingleResult();
String name = result.get(0, String.class);
Address address = result.get(1, Address.class);
Integer age = result.get(2, Integer.class);
あるいは
cq.multiselect(
c.get(Customer_.name).alias("customerName"),
c.get(Customer_.address).alias("customerAddress"),
c.get(Customer_.age).alias("customerAge"));
TypedQuery<Tuple> tq = em.createQuery(cq);
Tuple result = tq.getSingleResult();
String name = result.get("customerName", String.class);
Address address = result.get("customerAddress", Address.class);
Integer age = result.get("customerAge", Integer.class);
// 方法3
CriteriaBuilder cb = ...;
CriteriaQuery<Object[]> cq = cb.createQuery(Object[].class);
Root<Customer> c = cq.from(Customer.class);
cq.where(cb.equal(c.get(Customer_.id), 101));
cq.multiselect(
c.get(Customer_.name),
c.get(Customer_.address),
c.get(Customer_.age));
TypedQuery<Object[]> tq = em.createQuery(cq);
Object[] result = tq.getSingleResult();
★大量データに対する処理
import org.hibernate.*;
import org.hibernate.internal.AbstractQueryImpl;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
@Inject
private EntityManager em;
public <T> void fetchLargeData(
String queryName,
Map<String, Object> parameters,
Class<T> clazz) {
TypedQuery<T> typedQuery = em.createNamedQuery(queryName, clazz);
// HibernateのAPIを使用するためにダウンキャスト
AbstractQueryImpl queryImpl = typedQuery.unwrap(AbstractQueryImpl.class);
String namedSql = queryImpl.getQueryString();
// NOTE: StatelessSessionは関連する永続コンテキストを持たず、一次キャッシュを実装していない。
// (JPA)PersistenceContextにEntityオブジェクトをキャッシュしないDetachedなEntityなので、
// EntityManager#clear()を呼ぶ必要がない。つまり、Entityオブジェクトがメモリに蓄積されない。
Session session = em.unwrap(Session.class);
StatelessSession statelessSession = session.getSessionFactory().openStatelessSession();
// Queryを再生成
SQLQuery sqlQuery = statelessSession.createSQLQuery(namedSql);
for (Map.Entry<String, Object> paremeter : parameters.entrySet()) {
sqlQuery.setParameter(paremeter.getKey(), paremeter.getValue());
}
// メモリ使用量と応答時間を考慮
ScrollableResults results = sqlQuery
.setReadOnly(true)
.setCacheable(false)
.scroll(ScrollMode.FORWARD_ONLY);
while (results.next()) {
T entity = (T) results.get(0);
// NOTE: メモリの解放が不要
}
results.close();
statelessSession.close();
}
★クエリの結果
Query#getSingleResult
Query#getResultList
Query#executeUpdate DELETE and UPDATE
TypedQuery#getSingleResult
TypedQuery#getResultList
TypedQuery<Country> query =
em.createQuery("SELECT c FROM Country c", Country.class);
List<Country> results = query.getResultList();
TypedQuery<Long> query = em.createQuery(
"SELECT COUNT(c) FROM Country c", Long.class);
long countryCount = query.getSingleResult();
int count = em.createQuery("DELETE FROM Country").executeUpdate();
List<Country> results =
query.setFirstResult(pageIndex * pageSize)
.setMaxResults(pageSize)
.getResultList();
★Metamodel API
エンティティクラス
@Entity
public class Order {
@Id
@GeneratedValue
private Integer id;
@ManyToOne
private Customer customer;
@OneToMany
Set<Item> items;
...
}
メタモデルクラス
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.StaticMetamodel;
@StaticMetamodel(Order.class)
public class Order_ {
public static volatile SingularAttribute<Order, Integer> id;
public static volatile SingularAttribute<Order, Customer> customer;
public static volatile SetAttribute<Order, Item> items;
}
補足:Eclipseでメタモデルクラスを自動生成する設定
1.プロジェクトのプロパティを開く
2.Project FacetsでJPAにチェックを付けて、プロジェクトにJPAサポートを追加
3.JPA欄の一番上「Platform」で適切な対象項目を選択
4.JPA欄の一番下「Canonical metamodel(JPA 2.0)」でソースフォルダを設定
※「Discover annotated classes automatically」にチェックすると、エンティティを追加した時に便利
利用例
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
SetJoin<Order, Item> itemNode = cq.from(Order.class).join(Order_.items);
cq.where( cb.equal(itemNode.get(Item_.id), 3 ) ).distinct(true);
Hibernate Metamodel Generator
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-jpamodelgen</artifactId>
<version>...</version>
</dependency>
★Native SQL vs JPQL vs Criteria
全件検索
SELECT b FROM Book b
public List<Book> allEntries() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Book> cq = cb.createQuery(Book.class);
Root<Book> rootEntry = cq.from(Book.class);
CriteriaQuery<Book> all = cq.select(rootEntry);
TypedQuery<Book> allQuery = em.createQuery(all);
return allQuery.getResultList();
}
public class CompoundElement {
...
public CompoundElement(String name, Double value) {
this.name = name;
this.value = value;
}
//Needed for Criteria
public CompoundElement(String name, Object value) {
this.name = name;
this.value = (Double) value;
}
...
}
Native SQL
SELECT cmf.name, AVG(c.mileage)
FROM Car c
INNER JOIN CarModel cm
ON c.carModel = cm.uuid
INNER JOIN CarManufacturer cmf
ON cm.manufacturer = cmf.uuid
GROUP BY cmf.name
JPQL
public List<CompoundElement> find() {
Query query = em.createQuery(
"SELECT NEW xxx.CompoundElement(cmf.name, AVG(c.mileage)) " +
"FROM Car c " +
"JOIN c.carModel cm " +
"JOIN cm.manufacturer cmf " +
"GROUP BY cmf.name"
);
return (List<CompoundElement>)query.getResultList();
}
Criteria
public List<CompoundElement> find() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CompoundElement> q =
cb.createQuery(CompoundElement.class);
Root<Car> c = q.from(Car.class);
Join<Car, CarModel> cmJoin = c.join(Car_.carModel);
Join<CarModel, CarManufacturer> cmfJoin =
cmJoin.join(CarModel_.manufacturer);
CompoundSelection<CompoundElement> cSelect =
cb.construct(CompoundElement.class,
cmfJoin.get(CarManufacturer_.name),
cb.avg(c.get(Car_.mileage)));
q.select(cSelect);
q.groupBy(cmfJoin.get(CarManufacturer_.name));
TypedQuery<CompoundElement> query = em.createQuery(q);
return (List<CompoundElement>)query.getResultList();
}
★Initialize lazy relations
Call a method on the mapped relation
Should be avoided because of an additional query
Order order = em.find(Order.class, orderId);
order.getItems().size();
Fetch Join in JPQL
Query q = em.createQuery("SELECT o FROM Order o JOIN FETCH o.items i WHERE o.id = :id");
q.setParameter("id", orderId);
Order order = (Order) q.getSingleResult();
Fetch Join in Criteria API
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery q = cb.createQuery(Order.class);
Root o = q.from(Order.class);
o.fetch("items", JoinType.INNER);
q.select(o);
q.where(cb.equal(o.get("id"), orderId));
Order order = (Order) em.createQuery(q).getSingleResult();
Named Entity Graph (since JPA 2.1)
@Entity
@NamedEntityGraph(name = "graph.Order.items",
attributeNodes = @NamedAttributeNode("items"))
public class Order implements Serializable { ... }
Dynamic Entity Graph (since JPA 2.1)
EntityGraph graph = em.createEntityGraph(Order.class);
Subgraph itemGraph = graph.addSubgraph("items");
Map hints = new HashMap();
hints.put("javax.persistence.loadgraph", graph);
Order order = em.find(Order.class, orderId, hints);
★Criteria Update/Delete (JPA2.1)
public void updateOrder(Double oldAmount, Double newAmount) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaUpdate<Order> update = cb.createCriteriaUpdate(Order.class);
Root e = update.from(Order.class);
update.set("amount", newAmount);
update.where(cb.greaterThanOrEqualTo(e.get("amount"), oldAmount));
em.createQuery(update).executeUpdate();
}
public void deleteOrder(Double amount) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaDelete<Order> delete = cb.createCriteriaDelete(Order.class);
Root e = delete.from(Order.class);
delete.where(cb.lessThanOrEqualTo(e.get("amount"), amount));
em.createQuery(delete).executeUpdate();
}
★Get JPQL/SQL String From a CriteriaQuery
Hibernate
TypedQuery<Book> query = em.createQuery(...);
query.unwrap(org.hibernate.Query.class).getQueryString() // return SQL string (not JPQL String)
★UML of Criteria API
※他サイトを参照