添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Jpa

一.Spring data jpa 简介

首先JPA是Java持久层API,由Sun公司开发, 希望整合ORM技术,实现天下归一. 诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准, 事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。

二.Spring data jpa 基本使用

1.核心方法:

  • 查询所有数据findAll()
  • 修改 添加数据 S save(S entity)
  • 分页查询 Page<S> findAll(Example<S> example, Pageable pageable)
  • 根据id查询 findOne()
  • 根据实体类属性查询: findByProperty (type Property); 例如:findByAge(int age)
  • 删除 void delete(T entity)
  • 计数 查询 long count() 或者 根据某个属性的值查询总数 countByAge(int age)
  • 是否存在 boolean existsById(ID primaryKey)

2.查询关键字

  • and

And 例如:findByUsernameAndPassword(String user, Striang pwd);

  • or

Or 例如:findByUsernameOrAddress(String user, String addr);

  • between

Between 例如:SalaryBetween(int max, int min);

  • "<"

LessThan 例如: findBySalaryLessThan(int max);

  • ">"

GreaterThan 例如: findBySalaryGreaterThan(int min);

  • is null

IsNull 例如: findByUsernameIsNull();

  • is not null

IsNotNull NotNull 与 IsNotNull 等价 例如: findByUsernameIsNotNull();

  • like

Like 例如: findByUsernameLike(String user);

  • not like

NotLike 例如: findByUsernameNotLike(String user);

  • order by

OrderBy 例如: findByUsernameOrderByNameAsc(String user);直接通过name正序排序

  • "!="

Not 例如: findByUsernameNot(String user);

  • in

In 例如: findByUsernameIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

  • not in

NotIn 例如: findByUsernameNotIn(Collection<String> userList) ,方法的参数可以是 Collection 类型,也可以是数组或者不定长参数;

  • Top/Limit

查询方法结果的数量可以通过关键字来限制,first 或者 top都可以使用。top/first加数字可以指定要返回最大结果的大小 默认为1

3.内置方法

  • 分页&排序查询
// 排序查询
Sort sort = Sort.by(Sort.Direction.ASC, "age");
userDao.findAll(Sort sort);
// 分页排序查询
Pageable pageable = PageRequest.of(0,10, sort);
userDao.findAll(pageable);
  • Example实例查询

创建一个ExampleMatcher对象,最后再用Example的of方法构造相应的Example对象并传递给相关查询方法。我们看看Spring的例子。

Person person = new Person();
person.setName("熊大");// 模板对象
ExampleMatcher matching = ExampleMatcher.matching()
   .withIgnorePaths("age");// 忽略字段,如果是基本数据类型,比如int, int默认值为0,查找相当于'select * from person where name = '熊大' and age = 0',不忽略查空值
Example<Person> example = Example.of(person, matching);//Example根据域对象和配置创建一个新的ExampleMatcher
List<Person> personList = personDao.findAll(example);
  • 概念定义

上面例子中,是这样创建“实例”的:Example<Person> ex = Example.of(person, matcher);我们看到,Example对象,由person和matcher共同创建。

A、实体对象:在持久化框架中与Table对应的域对象,一个对象代表数据库表中的一条记录,如上例中Person对象。在构建查询条件时,一个实体对象代表的是查询条件中的“数值”部分。如:要查询名字是“熊大”的人,实体对象只能存储条件值“熊大”。

B、匹配器:ExampleMatcher对象,它是匹配“实体对象”的,表示了如何使用“实体对象”中的“值”进行查询,它代表的是“查询方式”,解释了如何去查的问题。如:要查询Name是“熊大”的人.

C、实例:即Example对象,代表的是完整的查询条件。由实体对象(查询条件值)和匹配器(查询方式)共同创建。

再来理解“实例查询”,顾名思义, 就是通过一个例子来查询 。要查询的是Person对象,查询条件也是一个Person对象,通过一个现有的Person对象作为例子,查询和这个例子相匹配的对象。

  • 特点及约束(局限性)

A、支持动态查询。即支持查询条件个数不固定的情况,如:个人列表中有多个过滤条件,用户使用时在“地址”查询框中输入了值,就需要按年龄进行过滤,如果没有输入值,就忽略这个过滤条件。对应的实现是,在构建查询条件Person对象时,将age属性值置具体的条件值或置为null。

B、不支持过滤条件分组。即不支持过滤条件用 or(或) 来连接,所有的过滤查件,都是 简单一层的用 and(并且) 连接。

C、仅支持字符串的开始/包含/结束/正则表达式匹配 和 其他属性类型的精确匹配。查询时,对一个要进行匹配的属性(如:姓名 name),只能传入一个过滤条件值,如以Person为例,要查询姓“熊”的客户,“熊”这个条件值就存储在表示条件对象的Person对象的name属性中,针对于“姓名”的过滤也只有这么一个存储过滤值的位置,没办法同时传入两个过滤值。正是由于这个限制,有些查询是没办法支持的,例如要查询某个时间段内添加的person,对应的属性是 addTime,需要传入“开始时间”和“结束时间”两个条件值,而这种查询方式没有存两个值的位置,所以就没办法完成这样的查询。

  • ExampleMatcher的使用
  • 一些问题

(1)Null值的处理。当某个条件值为Null,是应当忽略这个过滤条件呢,还是应当去匹配数据库表中该字段值是Null的记录?

(2)基本类型的处理。如Person对象中的年龄age是int型的,当页面不传入条件值时,它默认是0,是有值的,那是否参与查询呢?

(3)忽略某些属性值。一个实体对象,有许多个属性,是否每个属性都参与过滤?是否可以忽略某些属性?

(4)不同的过滤方式。同样是作为String值,可能“姓名”希望精确匹配,“地址”希望模糊匹配,如何做到?

(5)大小写匹配。字符串匹配时,有时可能希望忽略大小写,有时则不忽略,如何做到?

  • 一些方法

1、关于基本数据类型。

实体对象中,避免使用基本数据类型,采用包装器类型。如果已经采用了基本类型,

而这个属性查询时不需要进行过滤,则把它添加到忽略列表(ignoredPaths)中。

2、Null值处理方式。

默认值是 IGNORE(忽略),即当条件值为null时,则忽略此过滤条件,一般业务也是采用这种方式就可满足。当需要查询数据库表中属性为null的记录时,可将值设为INCLUDE,这时,对于不需要参与查询的属性,都必须添加到忽略列表(ignoredPaths)中,否则会出现查不到数据的情况。

3、默认配置、特殊配置。

默认创建匹配器时,字符串采用的是精确匹配、不忽略大小写,可以通过操作方法改变这种默认匹配,以满足大多数查询条件的需要,如将“字符串匹配方式”改为CONTAINING(包含,模糊匹配),这是比较常用的情况。对于个别属性需要特定的查询方式,可以通过配置“属性特定查询方式”来满足要求。

4、非字符串属性

如约束中所谈,非字符串属性均采用精确匹配,即等于。

5、忽略大小写的问题。

忽略大小的生效与否,是依赖于数据库的。例如 MySql 数据库中,默认创建表结构时,字段是已经忽略大小写的,所以这个配置与否,都是忽略的。如果业务需要严格区分大小写,可以改变数据库表结构属性来实现,具体可百度。

三.Spring data jpa 注解

1. Repository注解

@Modifying //做update操作时需要添加

@Query // 自定义Sql

@Query(value = "SELECT * FROM USERS WHERE X = ?1", nativeQuery = true)
User findByEmailAddress(String X);
@Query("select u from User u where u.firstname = :firstname") //不加nativeQuery应使用HQL
User findByLastnameOrFirstname(@Param("lastname") String lastname);

@Transactional //事务

@Async //异步操作

2.Entity注解

@Entity //不写@Table默认为user
@Table(name="t_user") //自定义表
public class user {
    @Id //主键
    @GeneratedValue(strategy = GenerationType.AUTO)//采用数据库自增方式生成主键
    //JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO.
    //TABLE:使用一个特定的数据库表格来保存主键。
    //SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
    //IDENTITY:主键由数据库自动生成(主要是自动增长型)
    //AUTO:主键由程序控制。
    @Transient //此字段不与数据库关联
    @Version//此字段加上乐观锁
    //字段为name,不允许为空,用户名唯一
    @Column(name = "name", unique = true, nullable = false)
    private String name;
    @Temporal(TemporalType.DATE)//生成yyyy-MM-dd类型的日期
    //出参时间格式化
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    //入参时,请求报文只需要传入yyyymmddhhmmss字符串进来,则自动转换为Date类型数据
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    private Date createTime;
    public String getName() {
        return name;
    public void setName(String name) {
        this.name = name;
}

四.继承JpaSpecificationExecutor接口进行复杂查询

spring data jpa 通过创建方法名来做查询,只能做简单的查询,那如果我们要做复杂一些的查询呢,多条件分页怎么办,这里,spring data jpa为我们提供了JpaSpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询

1.首先让我们的接口继承于JpaSpecificationExecutor

public interface PersonDao extends JpaRepository<Person, Integer>, JpaSpecificationExecutor<Person> {
}

2. 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);
//其中Specification就是需要我们传入查询方法的参数,它是一个接口
public interface Specification<T> {
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}

3.什么是Specification

Specification是springDateJpa中的一个接口,他是用于当jpa的一些基本CRUD操作的扩展,可以把他理解成一个spring jpa的复杂查询接口。其次我们需要了解Criteria 查询,这是是一种类型安全和更面向对象的查询。而Spring Data JPA支持JPA2.0的Criteria查询,相应的接口是JpaSpecificationExecutor。
而JpaSpecificationExecutor这个接口基本是围绕着Specification接口来定义的, Specification接口中只定义了如下一个方法:

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

Criteria查询基本概念

Criteria 查询是以元模型的概念为基础的,元模型是为具体持久化单元的受管实体定义的,这些实体可以是实体类,嵌入类或者映射的父类。

CriteriaQuery接口
代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select 、from、where、group by、order by等注意:CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用。
Root:
代表Criteria查询的根对象,Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似。 Root实例是类型化的,且定义了查询的FROM子句中能够出现的类型。root代表查询的实体类,query可以从中得到root对象,告诉jpa查询哪一个实体类,还可以添加查询条件,还可以结合EntityManager对象 得到最终查询的 TypedQuery对象。
CriteriaBuilder接口

用来构建CritiaQuery的构建器对象Predicate:一个简单或复杂的谓词类型,其实就 相当于条件或者是条件组合 (sql中 的where组合) 可通过 EntityManager.getCriteriaBuilder 而得。

精确查询

Specification<Person> specification = (root, criteriaQuery, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(cb.equal(root.get("name"), "熊大"));
            return cb.and(predicates.toArray(new Predicate[0]));
        List<Person> list= personDao.findAll(specification);