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