问题背景
在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();
}
至此,大功告成!