# 多表查询

# 对象导航查询

对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。

查询一个客户,获取该客户下的所有联系人。

	@Autowired
	private CustomerDao customerDao;
	
	@Test
	//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
	@Transactional 
	public void testFind() {
		Customer customer = customerDao.findOne(5l);
		Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
		for(LinkMan linkMan : linkMans) {
  			System.out.println(linkMan);
		}
	}

查询一个联系人,获取该联系人的所有客户。

@Autowired
	private LinkManDao linkManDao;
	
	
	@Test
	public void testFind() {
		LinkMan linkMan = linkManDao.findOne(4l);
		Customer customer = linkMan.getCustomer(); //对象导航查询
		System.out.println(customer);
	}

对象导航查询的问题分析:

问题1:我们查询客户时,要不要把联系人查询出来?

分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。

解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。

配置方式:

	/**
	 * 在客户对象的@OneToMany注解中添加fetch属性
	 * 		FetchType.EAGER	:立即加载
	 * 		FetchType.LAZY	:延迟加载
	 */
	@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
	private Set<LinkMan> linkMans = new HashSet<>(0);

问题2:我们查询联系人时,要不要把客户查询出来?

分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。

解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来

配置方式

	/**
	 * 在联系人对象的@ManyToOne注解中添加fetch属性
	 * 		FetchType.EAGER	:立即加载
	 * 		FetchType.LAZY	:延迟加载
	 */
	@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
	@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
	private Customer customer;

# 使用Specification查询

/**
	 * Specification的多表查询
	 */
	@Test
	public void testFind() {
		Specification<LinkMan> spec = new Specification<LinkMan>() {
			public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				//Join代表链接查询,通过root对象获取
				//创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right)
				//JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接
				Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER);
				return cb.like(join.get("custName").as(String.class),"传智播客1");
			}
		};
		List<LinkMan> list = linkManDao.findAll(spec);
		for (LinkMan linkMan : list) {
			System.out.println(linkMan);
		}
	}

# Specification的toPredicate多条件查询

public Page<AnswerPaper> search(String searchKey, Pageable pageable) {
		return answerpaperDao.findAll(new Specification<AnswerPaper>() {
			private static final long serialVersionUID = 1L;

			public Predicate toPredicate(Root<AnswerPaper> root,
					CriteriaQuery<?> query, CriteriaBuilder builder) {
				List<Predicate> list = new ArrayList<Predicate>();
				if (StringUtils.isNotBlank(searchKey)) {
					list.add(builder.or(builder.like(root.get("namePinYin").as(String.class),
											"%" + searchKey.toUpperCase() + "%"), builder.like(root.get("name").as(String.class),
											"%" + searchKey + "%")));
				}
				Predicate[] p = new Predicate[list.size()];
				return builder.and(list.toArray(p));
			}
		}, pageable);
	}

public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder builder)

                其中 Root是查询结果的一个实体对象,也就是查询结果返回的主要对象,其中一对多OR多对一就是从这个对象开始计算的

                然后是javax.persistence.criteria.CriteriaQuery<T>,这个是JPA标准,主要是构建查询条件的,里面的方法都是各种查询方式:distinct、select、where、groupby、having、orderby这些方法,想必大家都知道这些是组件SQL语句的基本方法。

                接下来是javax.persistence.criteria.CriteriaBuilder,这个接口主要是用来进行一些函数操作,不过我们这里先关注JPA标准中Hibernate的两个实现方法:

1.and org.hibernate.ejb.criteria.CriteriaBuilderImpl.and(Predicate...) 2.or org.hibernate.ejb.criteria.CriteriaBuilderImpl.or(Predicate...) 这两个方法都有一个关键的接口:Predicate(javax.persistence.criteria.Predicate); 这个接口,同为Expression(javax.persistence.criteria.Expression)的子接口,可以肯定也是字段相关的表达式,在实际操作中,这个接口也是作为关联各种Predicate的核心操作接口,and方法是将各个条件作为and来拼接,进行查询;or是将各条件作为or来拼接,进行查询。 然后说如何生成一个Predicate接口,生成的核心类还是上面的CriteriaBuilder(javax.persistence.criteria.CriteriaBuilder),在这个接口中,很多方法都是返回Predicate接口的,其中包含between、gt(大于)、lt(小于)、not(非)等等操作,将所需要的这些查询条件都放在一个array数组(或者采用java.util.ArrayList.toArray(T[])-->cb.and(predicateList.toArray(new Predicate[predicates.size()]))),然后调用上一步说的and或者or方法进行拼接,当然,如果一个查询既有and又有or,用这种方式还没有具体尝试过,有待考证。

Root:查询哪个表 CriteriaQuery:查询哪些字段,排序是什么 CriteriaBuilder:字段之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么方式 Predicate(Expression):单独每一条查询条件的详细描述

Specification<User> specification = new Specification<User>() {
    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        List<Predicate> list = new ArrayList<>();
//               1.等于   选择name是“张三”的用户  Predicate equal(Expression<?> var1, Object var2);
        list.add(cb.equal(root.get("name").as(String.class), "张三"));
//               2.多表查询   选择teacher是“老师甲”
        list.add(cb.equal(root.join("teacher").get("name").as(String.class), "老师甲"));
//              3.不包括集合 studentId不是7、8、9
        List notInList = Arrays.asList(7,8,9);
        list.add(cb.not(root.get("studentId").in(notInList)));
//              4.包括集合  studentId是4、5、6
        List inList = Arrays.asList(4,5,6);
        list.add(root.get("studentId").in(inList));
 
        Predicate[] p = new Predicate[list.size()];
//                cb.and    “and”表示这几个条件并且的关系   Predicate and(Predicate... var1);
        return cb.and(list.toArray(p));
    }
};

Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor。Criteria 查询:是一种类型安全和更面向对象的查询 。

这个接口基本是围绕着Specification接口来定义的, Specification接口中只定义了如下一个方法:

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb); 

要理解这个方法,以及正确的使用它,就需要对JPA2.0的Criteria查询有一个足够的熟悉和理解,因为这个方法的参数和返回值都是JPA标准里面定义的对象。

关于评论

评论前请填好“昵称”、“邮箱”这两栏内容,否则不会收到回复,谢谢!