Spring Data JPA知识汇总

文章首发于公众号,欢迎订阅
在这里插入图片描述

一、基本注解

1.@Entity

由@Entity定义的对象会成为被JPA管理的实体,将映射到指定的数据库表中。

源码如下:

public @interface Entity {
	//可选,默认是此实体类的名字,全局唯一
    String name() default "";
}

2.@Table

用于指定数据库的表名。

源码如下:

public @interface Table {
	//表的名字,如果不写,则默认与实体的名字一样
    String name() default "";
	//此表的catalog
    String catalog() default "";
	//此表的schema
    String schema() default "";
	//唯一性约束,只有创建表的时候用,默认不需要
    UniqueConstraint[] uniqueConstraints() default {};
	//索引,只有创建表的时候使用,默认不需要
    Index[] indexes() default {};
}

3.@Id

定义数据库的主键,一个实体里至少有一个主键。

4.@IdClass

利用外部类的联合主键。

源码如下:

public @interface IdClass {
	//联合主键的类
    Class value();
}

需要满足以下几点:

  • 必须实现Serializable接口
  • 必须有默认的public无参构造方法
  • 必须覆盖equals和hashCode方法。equals方法用于判断两个对象是否相等,EntityManger通过find方法来查找Entity时是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码,生成的hashCode()相同的概率越小越好,算法可以进行优化。

联合主键的用处就是一个表中能存在多个主键,这些主键在关联在外部的一个对象中,这个对象需要满足以上条件。然后在需要关联主键的实体上加上@IdClass(value=关联主键对象.class)

5.@GeneratedValue

主键生成策略。

源码如下:

public @interface GeneratedValue {
	//Id生成策略,默认GenerationType.AUTO
    GenerationType strategy() default GenerationType.AUTO;
	//通过Sequences生成Id,常见的是Orcale数据库的Id生成规则,需要配合@SequenceGenerator使用
    String generator() default "";
}

GenerationType有以下4个值:

public enum GenerationType {
	//通过表产生主键,框架由表模拟序列产生主键,使用该策略可以使应用更易于数据库移植
    TABLE,
    //通过序列产生主键,通过@SequenceGenerator注解指定序列名,Mysql不支持这种方式
    SEQUENCE,
    //采用数据库Id自增,用于Mysql数据库
    IDENTITY,
    //JPA默认选项,自动选择策略
    AUTO;

    private GenerationType() {
    }
}

6.@Basic

@Basic表示属性是到数据库的字段的映射,如果实体的字段上没有任何注解,默认为@Basic。

源码如下:

public @interface Basic {
	//EAGER是立即加载,这是默认方式,可以看到还有一种LAZY懒加载
    FetchType fetch() default FetchType.EAGER;
	//Optional 类是一个可以为null的容器对象,设置为true,则表示字段可以为null,默认可以
    boolean optional() default true;
}

public enum FetchType {
    LAZY,
    EAGER;

    private FetchType() {
    }
}

7.@Transient

@Transient表示该属性并非一个到数据库表的字段的映射,是非持久化属性,与@Basic作用相反。

当你实体中有数据库中不存在的字段,但是又需要用到,那么可以加上@Transient注解,忽略这个字段的映射。

8.@Column

定义属性对应数据库中的列名。

源码如下:

public @interface Column {
    String name() default "";
	//表示该字段是否为唯一标识,默认为false,如果表中有一个字段需要唯一标识,则既可以使用该标记,也可以使用@Table标记中的@UniqueConstraint
    boolean unique() default false;
	//数据字段是否允许为空,默认允许
    boolean nullable() default true;
	//执行insert操作时是否包含此字段,默认包含
    boolean insertable() default true;
	//执行update操作时是否包含此字段,默认包含
	//insertable和updatable属性一般多用于只读的属性,例如主键和外键等。这些字段的值通常是自动生成的
    boolean updatable() default true;
	//表示创建表时,该字段创建的SQL语句,一般用于通过Entity生成表定义时使用
    String columnDefinition() default "";
	//表示当映射多个表时,指定表的表中的字段。默认值为主表的表名
    String table() default "";
	//字段长度,默认255
    int length() default 255;
	// precision属性和scale属性表示精度,当字段类型为double时,precision表示数值的总长度,scale表示小数点所占的位数
    int precision() default 0;
    int scale() default 0;
}

9.@Temporal

@Temporal用来设置Date类型的属性映射到对应精度的字段,也就是对日期进行格式化。

源码如下:

public enum TemporalType {
    DATE,
    TIME,
    TIMESTAMP;

    private TemporalType() {
    }
}public @interface Temporal {
    TemporalType value();
}


public enum TemporalType {
    DATE,
    TIME,
    TIMESTAMP;

    private TemporalType() {
    }
}

可以看到有三种格式化方式,

第一种:@Temporal(TemporalType.DATE)——》实体类会封装成日期“yyyy-MM-dd”的 Date类型。

第二种:@Temporal(TemporalType.TIME)——》实体类会封装成时间“hh-MM-ss”的 Date类型。

第三种:@Temporal(TemporalType.TIMESTAMP)——》实体类会封装成完整的时间“yyyy-MM-dd hh:MM:ss”的 Date类型。

10.@Enumerated

用于直接映射枚举类型的字段。

源码如下:

public @interface Enumerated {
    EnumType value() default EnumType.ORDINAL;
}

11.@Lob

@Lob将属性映射为数据库支持的大对象类型,支持以下两种数据库类型的字段。

(1)Clob:长字符串类型,java.sql.Clob、Character[]、char[]、String都将被映射成Clob类型。

(2)Blob:字节类型,java.sql.Blob、Byte[]、byte[]、实现了Serializable接口类型都将被映射成Blob类型。

二、关联关系注解

1.@JoinColumn

定义外键关联的字段名称。

源码如下:

public @interface JoinColumn {
	//注解所在当前表的主键名,必须写
    String name() default "";
	//关联外部表的列名,默认是外部主键名
    String referencedColumnName() default "";
	//外键字段是否唯一
    boolean unique() default false;
	//外键字段是否允许为空
    boolean nullable() default true;
	//是否跟随一起新增
    boolean insertable() default true;
	//是否跟随一起更新
    boolean updatable() default true;
	
    String columnDefinition() default "";

    String table() default "";

    ForeignKey foreignKey() default @ForeignKey(ConstraintMode.PROVIDER_DEFAULT);
}

配合@OneToOne、@OneToMany、@ManyToOne一起使用。

2.@OneToOne

源码如下:

public @interface OneToOne {
	//关系目标实体,默认为该字段的类型
    Class targetEntity() default void.class;
	//级联操作策略
	1.CascadeType.PERSIST 级联新建
	2.CascadeType.MERGE   级联更新
	3.CascadeType.REMOVE  级联删除
	4.CascadeType.REFRESH 级联刷新,比如A先对商品修改,B后来修改商品时需要先刷新A修改的商品才可	  以修改
	5.CascadeType.DETACH  级联脱管/游离操作,如果你要删除一个实体,但是它有外键无法删除,你就需	 要这个级联权限了。它会撤销所有相关的外键关联
	6.CascadeType.ALL     拥有以上所有级联操作权限
	7.默认,关系表不会产生任何影响
    CascadeType[] cascade() default {};
	//数据获取方式,立即加载和延迟加载
    FetchType fetch() default FetchType.EAGER;
    
    boolean optional() default true;
	//关联关系被谁维护,一般不需要特别指定
	注意:只有关系维护方才能操作两者的关系,被维护方即使设置了维护方属性进行存储也不会更新外键关联
	//mappedBy不能与@JoinColumn或者@JoinTable同时使用
	//mappedBy的值是指另一方的实体里属性的字段,而不是数据库字段,也不是实体的对象的名字,是另一方配置了@JoinColumn或者@JoinTable注解的属性的字段名称
    String mappedBy() default "";
	//是否级联删除,和CascadeType.REMOVE的效果一样,只是配置了两种中的一种就会自动级联删除
    boolean orphanRemoval() default false;
}

public enum CascadeType {
    ALL,
    PERSIST,
    MERGE,
    REMOVE,
    REFRESH,
    DETACH;

    private CascadeType() {
    }
}

@OneToOne需要配合@JoinColumn一起使用

在使用@OneToOne进行双向关联时,需要在类上加上注解@JsonIdentityInfo,这个注解被用来在序列化/反序列化时为该对象或字段添加一个对象识别码,通常是用来解决循环嵌套的问题。通过配置属性generator来确定识别码生成的方式,配置属性property来确定识别码的名称,识别码名称没有限制。

一般这个注解可以这么加,

@JsonIdentityInfo(generator =ObjectIdGenerators.PropertyGenerator.class,property = “id”)

3.@OneToMany和@ManyToOne

自行分析,同上。

4.OrderBy

用于关联查询时排序。

@orderBy(“字段名 ASC|DESC”)

5.一些注意事项

(1)所有的注解要么全配置在字段上,要么全配置在get方法上,不要混用,否则会报错。

(2)在关联查询中,表一般不需要建立外键索引。

三、定义查询方法

1.选择性暴露CRUD方法

@NoRepositoryBean
public interface MyRepository<T,ID extends Serializable> extends Repository<T,ID> {

    T findOne(ID id);
    T save(T entity);
}

通过自定义的公共基础接口,有选择的暴露了CRUD的方法。之后的数据接口只需要继承它即可。

2.方法的查询策略的属性表达式

假设一个User实体对象中有一个属性userCard,这个属性中包含有一个userId,在这种情况下,如果要通过userId查询User对象,那么查询方法写成:

User findByUserCardCardId(String userId);

为了解决属性匹配歧义问题,JPA保留了下划线字符,只需要在特定地方加上下划线,即可分割属性,当然强烈建议遵循Java驼峰命名规范。

User findByUserCard_CardId(String id);

3.Projections对查询结果的扩展

一般情况下,我们只需要返回一行数据的某些字段,但是JPA默认查找总会给我们全部返回,这样如果只需要其中几个字段,还需要手动去封装。Spring JPA考虑到这一点,允许对专用返回类型进行建模,以便我们有更多选择,将部分字段显示成视图对象。

现有两种方法可以进行建模。

(1)声明一个接口,接口中包含需要返回的属性的方法。

比如我有一个User实体,里面有4个字段,分别为id,name,email,gender,我现在只需要返回name和email,那么我可以这么定义接口。

public interface NameAndEmail {
    String getName();
    String getEmail();
}

Repository中这么写:

// 我这里是通过name查找的
NameAndEmail findByName(String name);

Controller写法示例:

@RequestMapping("/findByName")
@ResponseBody
public NameAndEmail findByName(){
    return userRepository.findByName("fcy");
}

(2)使用Dto,定义自己的Dto实体类。

@Data
public class NameAndEmailDto {

    private String name;
    private String email;
    
    public NameAndEmailDto(String name, String email) {
        this.name = name;
        this.email = email;
    }
}

Repository中这么写:

Collection<?> findByName(String name, Class<?> type);

Controller写法示例:

@RequestMapping("/findByName")
@ResponseBody
public Collection<?> findByName(){
    return userRepository.findByName("fcy",NameAndEmailDto.class);
}
//当然这里还可以传入 User.class,这样就返回所有字段了,算是通用接口,推荐这个写法

四、注解式查询方法

1.@Query

源码如下:

public @interface Query {
	//指定JPQL的查询语句,nativeQuery=true时,是原生SQL语句
    String value() default "";
	//指定count的JPQL语句,如果不指定将根据query自动生成
    String countQuery() default "";
	//根据哪个字段来count,默认即可
    String countProjection() default "";
	//默认为false,表示不是原生SQL,如果要用原生SQL,改成true
    boolean nativeQuery() default false;
	//可以指定一个query的名字,必须是唯一的
    String name() default "";
	//可以指定一个count的query的名字,必须是唯一的
    String countName() default "";
}

注意:nativeQuery不支持直接Sort的参数查询。

JPQL语句不支持insert

nativeQuery排序的错误写法:

@Query(value="......",nativeQuery=true)
User findByName(String name,Sort sort) 

正确的排序写法是通过order by来查询。

@Query排序
//使用JPQL语句
@Query(value = "select u from User u where u.name like ?1%")
List<NameAndEmail> findByAndSort(String name, Sort sort);

new Sort里的属性必须是实体类中的字段,如下。

userRepository.findByAndSort("f",new Sort("name"));
@Query分页
@Query("select u from User u where u.email=?1")
Page<NameAndEmail> findByEmail(String email, Pageable pageable);
userRepository.findByEmail("XXXXXXX",new PageRequest(1,10));

2.@Param

默认参数是通过顺序绑定在查询语句上的,这使得查询方法对参数位置的重构容易出错。为了解决这个问题,可以使用@Param注解指定方法参数的具体名称,通过绑定的参数名称做查询条件。

如下:

@Query("select u from User u where u.email = :email")
Page<NameAndEmail> findByEmail(@Param("email") String email, Pageable pageable);

3.@Modifying

源码如下:

public @interface Modifying {
    boolean flushAutomatically() default false;
	//我们知道jpa底层实现会有二级缓存,也就是在更新完数据库后,如果后面去用这个对象,你再去查这个对象,这个对象是在一级缓存中,并没有跟数据库同步,这个时候用clearAutomatically=true,就会刷新hibernate的一级缓存了,否则你在同一接口中,更新一个对象,接着查询这个对象,那么你查出来的这个对象还是没有更新之前的状态。
    boolean clearAutomatically() default false;
}

在@Query注解中编写JPQL实现DELETE和UPDATE操作的时候必须加上@Modifying注解,以通知Spring Data 这是一个DELETE或UPDATE操作。

五、JpaRepository的扩展

1.QueryByExampleExecutor

源码如下:

public interface QueryByExampleExecutor<T> {
	//根据“实例”查找一个对象
    <S extends T> Optional<S> findOne(Example<S> var1);

    <S extends T> Iterable<S> findAll(Example<S> var1);

    <S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);

    <S extends T> Page<S> findAll(Example<S> var1, Pageable var2);

    <S extends T> long count(Example<S> var1);

    <S extends T> boolean exists(Example<S> var1);
}
public interface Example<T> {
    static <T> Example<T> of(T probe) {
        return new TypedExample(probe, ExampleMatcher.matching());
    }

    static <T> Example<T> of(T probe, ExampleMatcher matcher) {
        return new TypedExample(probe, matcher);
    }

    T getProbe();

    ExampleMatcher getMatcher();

    default Class<T> getProbeType() {
        return ProxyUtils.getUserClass(this.getProbe().getClass());
    }
}

Example主要包含:

  • probe:这是具有填充字段的域对象的实际实体类,即查询条的封装类,必填。

  • ExampleMatcher:ExampleMatcher源码中有关于如何匹配特定字段的匹配规则,它可以重复使用在多个示例,必填,有默认值。

  • Example:用于创建查询。

理解ExampleMatcher
//现在ExampleMatcher好像变成interface了
public class ExampleMatcher {
NullHandler nullHandler; //Null值处理方式
StringMatcher defaultStringMatcher; //默认字符串匹配方式
boolean defaultIgnoreCase; //默认大小写忽略方式
PropertySpecifiers propertySpecifiers; //各属性特定查询方式
Set<String> ignoredPaths; //忽略属性列表
......
}

(1)nullHandler:Null值处理方式,枚举类型,有2个可选值:

  • INCLUDE(包括)

  • IGNORE(忽略)

    标识作为条件的实体对象中,一个属性值(条件值)为Null是,是否参与过滤。当该选项值是INCLUDE时,表示仍参与过滤,会匹配数据库表中该字段值是Null的记录;若为IGNORE值,表示不参与过滤。

(2)defaultStringMatcher:默认字符串匹配方式,枚举类型,有6个可选值:

  • DEFAULT(默认,效果同EXACT,即精确匹配))

  • EXACT(相等)

  • STARTING(开始匹配)

  • ENDING(结束匹配)

  • CONTAINING(包含,模糊匹配)

  • REGEX(正则表达式)

    该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的匹配方式。

(3)defaultIgnoreCase:默认大小写忽略方式,布尔型,当值为false时,即不忽略,大小不相等。该配置对所有字符串属性过滤有效,除非该属性在 propertySpecifiers 中单独定义自己的忽略大小写方式。

(4)propertySpecifiers:各属性特定查询方式,描述了各个属性单独定义的查询方式,每个查询方式中包含4个元素:属性名、字符串匹配方式、大小写忽略方式、属性转换器。如果属性未单独定义查询方式,或单独查询方式中,某个元素未定义(如:字符串匹配方式),则采用 ExampleMatcher 中定义的默认值,即上面介绍的 defaultStringMatcher 和 defaultIgnoreCase 的值。

(5)ignoredPaths:忽略属性列表,忽略的属性不参与查询过滤。

常用情况说明:

1、关于基本数据类型。
实体对象中,避免使用基本数据类型,采用包装器类型。如果已经采用了基本类型,
而这个属性查询时不需要进行过滤,则把它添加到忽略列表(ignoredPaths)中。

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

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

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

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

常用查询示例:

1、无匹配器的情况

要求:查询地址是“河南省郑州市”,且重点关注的客户。
说明:对于默认匹配器满足条件时,则不需要创建匹配器。

 //创建查询条件数据对象
        Customer customer = new Customer();
        customer.setAddress("河南省郑州市");
        customer.setFocus(true);
        
        //创建实例
        Example<Customer> ex = Example.of(customer); 
        
        //查询
        List<Customer> ls = dao.findAll(ex);
        
        //输出结果
        System.out.println("数量:"+ls.size());
        for (Customer bo:ls)
        {
            System.out.println(bo.getName());
        }

2、通用情况
要求:根据姓名、地址、备注进行模糊查询,忽略大小写,地址要求开始匹配。
说明:这是通用情况,主要演示改变默认字符串匹配方式、改变默认大小写忽略方式、属性特定查询方式配置、忽略属性列表配置。

//创建查询条件数据对象
        Customer customer = new Customer();
        customer.setName("zhang");
        customer.setAddress("河南省");
        customer.setRemark("BB");

        //创建匹配器,即如何使用查询条件
        ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
                .withStringMatcher(StringMatcher.CONTAINING) //改变默认字符串匹配方式:模糊查询
                .withIgnoreCase(true) //改变默认大小写忽略方式:忽略大小写
                .withMatcher("address", GenericPropertyMatchers.startsWith()) //地址采用“开始匹配”的方式查询
                .withIgnorePaths("focus");  //忽略属性:是否关注。因为是基本类型,需要忽略掉
        
        //创建实例
        Example<Customer> ex = Example.of(customer, matcher); 
        
        //查询
        List<Customer> ls = dao.findAll(ex);
        
        //输出结果
        System.out.println("数量:"+ls.size());
        for (Customer bo:ls)
        {
            System.out.println(bo.getName());
        }

3、多级查询
要求:查询所有潜在客户
说明:主要演示多层级属性查询

//创建查询条件数据对象
        Customer customer = new Customer();

        //创建匹配器,即如何使用查询条件
        ExampleMatcher matcher = ExampleMatcher.matching() //构建对象
                .withIncludeNullValues() //改变“Null值处理方式”:包括
                .withIgnorePaths("id","name","sex","age","focus","addTime","remark","customerType"); //忽略其他属性 
        
        //创建实例
        Example<Customer> ex = Example.of(customer, matcher); 
        
        //查询
        List<Customer> ls = dao.findAll(ex);
        
        //输出结果
        System.out.println("数量:"+ls.size());
        for (Customer bo:ls)
        {
            System.out.println(bo.getName());
        }

2.JpaSpecificationExecutor

JpaSpecificationExecutor是JPA2.0提供的Criteria API,可以动态生成query。

源码如下:

public interface JpaSpecificationExecutor<T> {
	//根据Specification条件查询单个对象
    Optional<T> findOne(@Nullable Specification<T> var1);

    List<T> findAll(@Nullable Specification<T> var1);

    Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);

    List<T> findAll(@Nullable Specification<T> var1, Sort var2);

    long count(@Nullable Specification<T> var1);
}

这个接口基本围绕Specification接口定义,Specification接口中的一个方法:

Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);

JpaSpecificationExecutor是针对Criteria API进行了predicate标准封装,帮我们封装了通过EntityManager的查询和使用细节,操作Criteria 更加便利。

Criteria 的介绍:

Root root :代表了可以查询和操作的实体对象的根。如果将实体对象比喻成表名,那root里面就是这张表里面的字段,只不过是JPQL的实体字段。通过里面的Pathget(String attributeName)来获取我们操作的字段。

CriteriaQuery<?>query:代表一个specific的顶层查询对象,它包含查询的各个部分,比如:select、from、where、group by、order by等。CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用,它提供了查询ROOT的方法,常用方法有:

    CriteriaQuery<T> select(Selection<? extends T> var1);
    CriteriaQuery<T> where(Predicate... var1);
    CriteriaQuery<T> having(Predicate... var1);
JpaSpecificationExecutor示例
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User>

实体类UserRepository必须继承JpaRepository或者Repository,同时继承JpaSpecificationExecutor才可以产生作用,因为JpaSpecificationExecutor并没有继承Repository。

下面给出一个动态查询的例子(通过email模糊查询,name精确查询,没有提供的话就忽略)

// 这里给出两种方法
public List<User> findByCondition(User user){
    return userRepository.findAll(((root, criteriaQuery, criteriaBuilder) -> {
        List<Predicate> predicates = new ArrayList<>();
        if (!StringUtils.isEmpty(user.getEmail())){
            predicates.add(criteriaBuilder.like(root.get("email"),"%"+user.getEmail()+"%"));
        }
        if (!StringUtils.isEmpty(user.getName())){
            predicates.add(criteriaBuilder.equal(root.get("name"),user.getName()));
        }
        return criteriaQuery.where(predicates.toArray(new Predicate[0])).getGroupRestriction();
    }));
}

public List<User> findByCondition2(User user){
    return userRepository.findAll((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
        Predicate predicate=criteriaBuilder.conjunction();
        if (!StringUtils.isEmpty(user.getEmail())){
       predicate.getExpressions().add(criteriaBuilder.like(root.get("email"),"%"+user.getEmail()+"%"));
        }
        if (!StringUtils.isEmpty(user.getName())){
            predicate.getExpressions().add(criteriaBuilder.equal(root.get("name"),user.getName()));
        }
        return predicate;
    });
}

如果要用到动态查询,推荐使用上述写法;如果平时只需要用到一些基本的条件查询,那么也可以使用下面这种工厂方式,提供一些常用的功能。

public class SpecificationFactory {
    //模糊查询
    public static Specification containsLike(String attribute,String value){
        return (root, criteriaQuery, criteriaBuilder) ->
            criteriaBuilder.like(root.get(attribute),"%"+value+"%");
    }

     //相等查询
    public static Specification equal(String attribute,Object value){
        return (root, criteriaQuery, criteriaBuilder) -> 
                criteriaBuilder.equal(root.get(attribute),value);
    }
}

我创建了一个免费的知识星球,用于分享知识日记,欢迎加入!

在这里插入图片描述

发布了10 篇原创文章 · 获赞 7 · 访问量 2377

猜你喜欢

转载自blog.csdn.net/a979331856/article/details/88937075