問題背景
在JPA多表聯合查詢,執行JPA sql 查詢語句的時候,會查詢出多個對象所有的值。然后在內存中進行排序、重組。瞬間造成服務器內存使用量升高,影響查詢性能。
解決辦法
業務場景
一對多查詢,然后進行模糊搜索。
解決辦法
PO類
一類
@Entity @Table(name = "transactionRecords") public class TransactionPO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; /** * 添加一對多關系 一條記錄中可以有多條視頻 */ @OneToMany(mappedBy = "transactionPO", fetch = FetchType.LAZY) @BatchSize(size = 15) //使用@BatchSize(size = 15)可以指定一次最多查15條。不會造成一次查詢大量數據 private List<VideoPO> list = new ArrayList<>(); }
多類
@Entity @Table(name="VideInformation",indexes=@Index(columnList = "transactionId")) public class VideoPO { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @ManyToOne private TransactionPO transactionPO; }
Service 實現方法
package com.chinasofti.product.sc.application.jpa; import java.util.ArrayList; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Order; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import javax.transaction.Transactional; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.Direction; import org.springframework.data.jpa.repository.query.QueryUtils; import com.chinasofti.product.sc.application.jpa.entity.TransactionPO; public class TransactionRepositoryImpl implements TransactionCustomer { @PersistenceContext private EntityManager em; @SuppressWarnings("deprecation") @Transactional @Override public PageImpl<TransactionPO> searchVedioRecord(Map<String, Object> params, Pageable pageable) { // Query Count Long count = countByCondition(params); // 數據裝載 CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<TransactionPO> cq = cb.createQuery(TransactionPO.class); Root<TransactionPO> root = cq.from(TransactionPO.class); // root.fetch("list",JoinType.LEFT); //如果使用這句就會造成多表全查詢,造成性能降低 cq.select(root).distinct(true); List<Predicate> predicates = buildQueryPredicate(params, root, cb); if (!predicates.isEmpty()) { cq.where(predicates.toArray(new Predicate[0])); } // Set order rule List<Order> sortOrder = null; if (pageable != null) { sortOrder = QueryUtils.toOrders(pageable.getSort(), root, cb); } else { org.springframework.data.domain.Sort.Order order = new org.springframework.data.domain.Sort.Order( Direction.DESC, "id"); List<org.springframework.data.domain.Sort.Order> orders = new ArrayList<>(); orders.add(order); sortOrder = QueryUtils.toOrders(new Sort(orders), root, cb); } cq.orderBy(sortOrder); TypedQuery<TransactionPO> query = this.em.createQuery(cq); if (pageable != null) { query.setMaxResults(pageable.getPageSize()); query.setFirstResult((int) pageable.getOffset()); } List<TransactionPO> ts = query.getResultList(); for (TransactionPO t : ts) { t.getList().size(); //優化點 } return new PageImpl<TransactionPO>(ts, pageable, count); } @Override public Long countByCondition(Map<String, Object> params) { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Long> cq = cb.createQuery(Long.class); Root<TransactionPO> root = cq.from(TransactionPO.class); cq.select(cb.count(root)); List<Predicate> predicates = buildQueryPredicate(params, root, cb); if (!predicates.isEmpty()) { cq.where(predicates.toArray(new Predicate[0])); } Long clong = this.em.createQuery(cq).getSingleResult(); return clong; } /** * * @param params * params * @param root * root * @param cb * cb * @return list */ private List<Predicate> buildQueryPredicate(Map<String, Object> params, Root<TransactionPO> root, CriteriaBuilder cb) { List<Predicate> list = new ArrayList<Predicate>(); Iterator<Map.Entry<String, Object>> it = params.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, Object> entry = it.next(); String key = entry.getKey(); if ("startTime".equals(key)) { list.add(cb.greaterThan(root.<Date>get("createTime"), (Date) entry.getValue())); } if ("endTime".equals(key)) { list.add(cb.lessThan(root.<Date>get("createTime"), (Date) entry.getValue())); } // 不支持模糊輸入查詢 if ("transactionId".equals(key)) { list.add(cb.equal(root.get("transactionId").as(String.class), entry.getValue())); } // 支持模糊輸入查詢 if ("staffCode".equals(key)) { list.add(cb.like(root.get("staffCode").as(String.class), "%" + entry.getValue().toString() + "%")); } } return list; } }
分析
如果我們在代碼中使用 root.fetch("list",JoinType.LEFT); 這樣雖然說也可以查詢出我們的結果集,但是由於Hibernate是以對象為查詢單位,它會先查詢整個對象,再去查另外一個對象。造成內存瞬間飆升。影響性能。這樣運行之后,控制台報異常如下:
firstResult/maxResults specified with collection fetch; applying in memory!
所以我們需要進行優化。方法如下:
1、在PO類中,一端的外鍵定義查詢最大個數,用@BatchSize(size = 15)
2、在實現方法中,
for (TransactionPO t : ts) { t.getList().size(); }
至此,大功告成!