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);