JPA 学习总结

主键生成策略

通过annotation(注解)来映射hibernate实体的,基于annotation的hibernate主键标识为@Id, 其生成规则由**@GeneratedValue**设定的.这里的@id和@GeneratedValue都是JPA的标准用法。

JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。

  • IDENTITY:主键由数据库自动生成(主要是自动增长型),前提是底层数据库支持主键递增(MYSQL)
  • SEQUENCE:根据底层数据库的序列来生成主键,前提是底层数据库支持序列(Oracle)
  • TABLE:使用一个特定的数据库表格来保存主键
  • AUTO:主键由程序控制,由程序自动帮助我们选择主键生成策略

JPQL查询

Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造**查询条件**(JPQL的@Query中仅仅是构造查询条件),这时就可以使用@Query注解,结合JPQL的语句方式完成查询。

@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可

栗子:

public interface CustomerDao extends JpaRepository<Customer, Long>,JpaSpecificationExecutor<Customer> {    
    //@Query 使用jpql的方式查询。
    @Query(value="from Customer")
    public List<Customer> findAllCustomer();
    
    //@Query 使用jpql的方式查询。
    @Query(value="from Customer where custName = ?")
    public Customer findCustomer(String custName);
    //多个占位符,?1代表参数的占位符,其中1对应方法中的参数索引,参数顺序一致可以不写参数索引
    @Query(value="from Customer where custName = ?1 and custId= ?2")
    public Customer findCustomer(String custName,Integer custId);
}

也可以通过使用 @Query 来执行一个更新操作,为此,我们需要在使用 @Query 的同时,用 @Modifying 来将该操作标识为修改查询,这样框架最终会生成一个更新的操作,而非查询

@Query(value="update Customer set custName = ?1 where custId = ?2")
@Modifying
public void updateCustomer(String custName,Long custId);

使用SQL语句查询:

@Query(value="select * from cst_customer",nativeQuery=true)
public void findSql();

动态查询:Specifications

在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。

/**
 *	接口JpaSpecificationExecutor中定义的方法
 **/
 public interface JpaSpecificationExecutor<T> {
   	//根据条件查询一个对象
 	T findOne(Specification<T> spec);	
   	//根据条件查询集合
 	List<T> findAll(Specification<T> spec);
   	//根据条件分页查询
 	Page<T> findAll(Specification<T> spec, Pageable pageable);
   	//排序查询查询
 	List<T> findAll(Specification<T> spec, Sort sort);
   	//统计查询
 	long count(Specification<T> spec);
}

对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。

Specification接口中只定义了如下一个方法:

 /**
*	root	:Root接口,代表查询的根对象,查询的任何属性都可以从root对象中获取
*	query	:代表一个顶层查询对象,用来自定义查询(一般不用)
*	cb		:用来构建查询,此对象封装了很多的条件方法
**/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);

使用Specifications完成条件查询:

@Autowired
	private CustomerDao customerDao;	
	@Test
    //自定义查询条件:
        //1.实现Specification接口(提供泛型:查询的对象类型)
        //2.实现toPredicate方法(构造查询条件)
        //3.需要借助两个参数:
           //root:获取需要查询的对象属性
           //CriteriaBuilder:构造查询的条件,内部封装了很多查询条件(模糊匹配like,精准查询equal等)
	public void testSpecifications() {
      	//使用匿名内部类的方式,创建一个Specification的实现类,并实现toPredicate方法
		Specification <Customer> spec = new Specification<Customer>() {
			public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				//cb:构建查询,添加查询方式   like:模糊匹配
				//root:从实体Customer对象中按照custName属性进行查询
            Path<Object> custName=root.get("custName");//客户名 
            Predicate  predicate= cb.like(custName.as(String.class), "师%");
				//+++++++++++++++++++这里需要注意的是对于比较方法:gt、lt、ge、le、like,得到path对象后,要通过.as(String.class)指明比较的参数类型,再去做比较。+++++
                return predicate
			}
		};
        //查询出姓名以“师”字开头的客户
		Customer customer = customerDao.findOne(spec);
		System.out.println(customer);
	}

使用Specifications完成多条件拼接查询:

案例:根据客户名(师耀昌)和客户所在行业(IT教育)查询

public void testSpecifications() {
		Specification <Customer> spec = new Specification<Customer>() {
			public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				Path<Object> custName=root.get("custName");//客户名
                Path<Object> custJob=root.get("custJob");//客户所属行业
                //构造查询
                //1.构造客户名的精准匹配查询
                Predicate p1=cb.equal(custName,"师耀昌");
                //2.构造所属行业的精准匹配查询
                Predicate p2=cb.equal(custJob,"It教育");
                //3.将多个查询条件组合到一起(多个条件同时满足,则为与关系用cb.and(),只要满足其中一个条件即可,则用或关系用cb.or())
                Predicate and=cb.and(p1,p2);
				return and;
			}
		};
        //查询出姓名为“师耀昌”,且所在行业为“IT教育”的客户
		Customer customer = customerDao.findOne(spec);//多条数据用findAll()
		System.out.println(customer);
	}

排序:

//排序很简单,只需要创建排序对象,将其传入方法即可

//Sort对象: 
//第一个参数为排序的顺序   正序:Sort.Direction.ASC   倒序:Sort.Direction.DESC
//第二个参数为排序的属性名称
如:
    Sort sort =new Sort(Sort.Direction.ASC,"custId");
   List<Customer> list=customerDao.findAll(spec,sort);//根据spec的条件查询,并正序排列

分页:

@Test
	public void testPage() {
		//构造查询条件
		Specification<Customer> spec = new Specification<Customer>() {
			public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
				return cb.like(root.get("custName").as(String.class), "传智%");
			}
		};
		/**
		 * 构造分页参数
		 * 		Pageable : 接口
		 * 			PageRequest实现了Pageable接口,调用构造方法的形式构造
		 * 				第一个参数:页码(从0开始)
		 * 				第二个参数:每页查询条数
		 */
		Pageable pageable = new PageRequest(0, 5);
		/**
		 * 分页查询,封装为Spring Data Jpa 内部的page bean
		 * 		此重载的findAll方法为分页方法需要两个参数
		 * 			第一个参数:查询条件Specification
		 * 			第二个参数:分页参数
		 */
		Page<Customer> page = customerDao.findAll(spec,pageable);
		/**     Page中还封装了如下方法:
		 *	     获取总页数:
		 * 		int getTotalPages();
		 * 		 获取总记录数:	
		 * 		long getTotalElements();	
		 *       获取列表数据:
		 *       List<T> getContent();
		 */
             System.out.println(page.getContent()) //获取分页后的数据集合
             System.out.println(page.getTotalPages()) //获取总页数
             System.out.println(page.getTotalElements()) //获取总条数
        
	}