Spring Data JPA 核心笔记

Spring Data JPA 核心笔记

Spring Data Jpa 笔记

Entity中常用的注解

首先我们常用的注解包括(@Entity、@Table、@Id、@IdClass、@GeneratedValue、@Basic、@Transient、@Column、@Temporal、@Enumerated、@Lob)

  1. @Entity使用此注解定义的对象将会成为被JPA管理的实体,将映射到指定的数据库表@Entity(name = "user")其中name默认是此实体类的名字,全局唯一。
  2. @Table指定此实体类对应的数据库的表名。若注解不加名字则系统认为表名和实体类的名字相同
  3. @Id定义字段为数据库的主键,一个实体里面必须有一个。
  4. @IdClass利用外部类的联合主键,其中外部类必须满足一下几点要求
    必须实现Serializable接口。
    必须有默认的public无参数的构造方法。
    必须覆盖equals和hashCode方法。equals方法用于判断两个对象是否相同,EntityManger通过find方法来查找Entity时是根据equals的返回值来判断的。hashCode方法返回当前对象的哈希码,生成的hashCode相同的概率越小越好,算法可以进行优化。
  5. @GeneratedValue为主键生成策略
  • 默认为AUTO即JPA自动选择合适的策略
  • IDENTITY 适用于MySQL,策略为自增
  • SEQUENCE 通过序列生成主键通过@SquenceGenerator指定序列名MySQL不支持
  • TABLE 框架由表模拟产生主键,使用该策略有利于数据库移植


  1. @Basic表示此字段是映射到数据库,如果实体字段上没有任何注解默认为@Basic。其中可选参数为@Basic(fetch = FetchType.*LAZY*, optional = false)其中fetch默认为EAGER立即加载,LAZY为延迟加载、optional表示该字段是否可以为null
  2. @Transient和@Basic的作用相反,表示该字段不是一个到数据库表的字段映射,JPA映射数据库的时候忽略此字段。
  3. @Column定义实体内字段对应的数据库中的列名
    @Column(
    name = "real_name",
    unique = true,
    nullable = false,
    insertable = false,
    updatable = false,
    columnDefinition = "varchar",
    length = 100)
    name对应数据库的字段名,可选默认字段名和实体属性名一样
    unique是否唯一,默认false,可选
    nullable是否允许为空。可选,默认为true
    insertable执行insert的时候是否包含此字段。可选,默认true
    updatable执行update的时候是否包含此字段。可选,默认true
    columnDefinition表示该字段在数据库中的实际类型
    length数据库字段的长度,可选,默认25
  4. @Temporal用来设置Date类型的属性映射到对应精度的字段
    @Temporal(TemporalType.DATE) //映射为只有日期
    @Temporal(TemporalType.TIME) //映射为只有时间
    @Temporal(TemporalType.TIMESTAMP) //映射为日期+时间
  5. @Lob将字段映射成数据库支持的大对象类型,支持一下两种数据库类型的字段。(注意:Clob、Blob占用的内存空间较大,一般配合@Basic(fetch = FetchType.*LAZY*)将其设置为延迟加载)
  • Clob:字段类型为Character[]、char[]、String将被映射为Clob
  • Blob:字段类型为Byte[]、byte[]和实现了Serializable接口的类型将被映射为Blob类型


JPA 常用类

基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能。它们的继承关系如下:

Repository : 仅仅是一个标识,表明任何继承它的均为仓库接口类

CrudRepository : 继承 Repository,实现了一组 CRUD 相关的方法

PagingAndSortingRepository : 继承 CrudRepository,实现了一组 分页排序 相关的方法

JpaRepository : 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法

自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。

JpaSpecificationExecutor : 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法


JPA 方法定义规范

简单条件查询: 查询某一个实体类或者集合

按照 Spring Data 的规范,查询方法以 find | read | get 开头, 涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母 大写

例如:定义一个 Entity 实体类

 class User 
  private String firstName; 
  private String lastName; 
  

使用And条件连接时,应这样写: findByLastNameAndFirstName(String lastName,String firstName); 条件的属性名称与个数要与参数的位置与个数一一对应

Keyword Sample JPQL snippet

@Query 注解

实体声明查询,默认 SQL 可写成从映射对象查询,若加入 nativeQuery = true ,则直接写原生 SQL

    // 根据层次编号查询模板
    @Query(value = "select  mb from Wsscmb mb where mb.ccbh = :ccbh")
    Wsscmb getByCcbh(String ccbh);

命名参数(推荐使用这种方式):

定义好参数名,赋值时采用 @Param ("参数名"),不用管顺序。

    // 根据联合主键修改模板的文书生成类别
    @Query(nativeQuery = true, value = "update S_WSSCMB set WSSCLB = :wssclb where COURT_NO = :courtno and XH = :xh")
    @Modifying
    int modifyWssclb(@Param("courtno") String courtno, @Param("xh") Integer xh, @Param("wssclb") String wssclb);

如果是 @Query 中有 LIKE 关键字,需要在占位符加 %,也可传入拼接好的变量

   @Query("select o from UserModel o where o.name like ?1%")
   public List<UserModel> findByUuidOrAge(String name);
  @Query("select o from UserModel o where o.name like %?1")
  public List<UserModel> findByUuidOrAge(String name);
  @Query("select o from UserModel o where o.name like %?1%")
  public List<UserModel> findByUuidOrAge(String name);

@Modifying 注解和事务

@Query 与 @Modifying 这两个注解一起声明,可定义个性化更新操作,例如只涉及某些字段更新时最为常用,示例如下:

    @Query(nativeQuery = true, value = "update S_WSSCMB set WSSCLB = :wssclb where COURT_NO = :courtno and XH = :xh")
    @Modifying
    int modifyWssclb(@Param("courtno") String courtno, @Param("xh") Integer xh, @Param("wssclb") String wssclb);

在调用该 数据库访问方法的 servie 的实现类加上事务注解

    @Transactional
    @Override
    public Integer modifyWssclb(String courtno, Integer xh, String wssclb) {
        int rows = wsscmbDao.modifyWssclb(courtno, xh, wssclb);
        return rows;
    }

CrudRepository

CrudRepository 接口提供了最基本的对实体类的添删改查操作

 T save(T entity);//保存单个实体 
 Iterable<T> save(Iterable<? extends T> entities);//保存集合    
 T findOne(ID id);//根据id查找实体    
 boolean exists(ID id);//根据id判断实体是否存在    
 Iterable<T> findAll();//查询所有实体,不用或慎用!    
 long count();//查询实体数量    
 void delete(ID id);//根据Id删除实体    
 void delete(T entity);//删除一个实体 
 void delete(Iterable<? extends T> entities);//删除一个实体的集合    
 void deleteAll();//删除所有实体,不用或慎用! 


PagingAndSortingRepository

该接口提供了分页与排序功能

 Iterable<T> findAll(Sort sort); //排序 
 Page<T> findAll(Pageable pageable); //分页查询(含排序功能) 
@Test
    public void testPagingAndSortingRespository(){
        //pageNo 从 0 开始. 
        int pageNo = 6 - 1;
        int pageSize = 5;
        //Pageable 接口通常使用的其 PageRequest 实现类. 其中封装了需要分页的信息
        //排序相关的. Sort 封装了排序的信息
        //Order 是具体针对于某一个属性进行升序还是降序. 
        Order order1 = new Order(Direction.DESC, "id");
        Order order2 = new Order(Direction.ASC, "email");
        Sort sort = new Sort(order1, order2);
        PageRequest pageable = new PageRequest(pageNo, pageSize, sort);
        Page<Person> page = personRepsotory.findAll(pageable);
        System.out.println("总记录数: " + page.getTotalElements());
        System.out.println("当前第几页: " + (page.getNumber() + 1));
        System.out.println("总页数: " + page.getTotalPages());
        System.out.println("当前页面的 List: " + page.getContent());
        System.out.println("当前页面的记录数: " + page.getNumberOfElements());
    }



JpaRepository

该接口提供了JPA的相关功能

List<T> findAll(); //查找所有实体 
List<T> findAll(Sort sort); //排序、查找所有实体 
List<T> save(Iterable<? extends T> entities);//保存集合 
void flush();//执行缓存与数据库同步 
T saveAndFlush(T entity);//强制执行持久化 
void deleteInBatch(Iterable<T> entities);//删除一个实体集合 

JpaSpecificationExecutor

不属于Repository体系,实现一组 JPA Criteria 查询相关的方法

接收参数Specifcation对象该对象是一个接口,此对象的作用想当与在hibernateJPA中使用QBC查询的操作,只是使用了Specifcation对象进行了封装

可以看出JpaSpecificationExecutor接口基本是围绕Specification接口定义的。而Specification接口最常用的就是以下方法:

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

(1)Root<T>root:代表了可以查询和操作的实体对象的根。通俗讲就是对象的属性。通过里面的Path<Y>get(String attributeName)来获得我们操作的字段

(2)CriteriaQuery<?>query:代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select、from、where、group by、order by等。它提供了查询ROOT的方法。常用的方法有

package javax.persistence.criteria;
import java.util.List;
import java.util.Set;
public interface CriteriaQuery<T> extends AbstractQuery<T> {
    CriteriaQuery<T> select(Selection<? extends T> selection);
    CriteriaQuery<T> multiselect(Selection<?>... selections);
    CriteriaQuery<T> multiselect(List<Selection<?>> selectionList);
    CriteriaQuery<T> where(Expression<Boolean> restriction);
    CriteriaQuery<T> where(Predicate... restrictions);
    CriteriaQuery<T> groupBy(Expression<?>... grouping);
    CriteriaQuery<T> groupBy(List<Expression<?>> grouping);
    CriteriaQuery<T> having(Expression<Boolean> restriction);
    CriteriaQuery<T> having(Predicate... restrictions);
    CriteriaQuery<T> orderBy(Order... o);
    CriteriaQuery<T> orderBy(List<Order> o);
    CriteriaQuery<T> distinct(boolean distinct);
    List<Order> getOrderList();
    Set<ParameterExpression<?>> getParameters();
}

(3)CriteriaBuilder cb:用来构建CritiaQuery的构建器对象,其实就相当于条件或者是条件组合,即以Predicate的形式返回。构建简单的Predicate示例:

Predicate predicate = criteriaBuilder.like(root.get("name"),name);

例子

/**
     * 目标: 实现带查询条件的分页. id > 5 的条件
     * 调用 JpaSpecificationExecutor 的 Page<T> findAll(Specification<T> spec, Pageable pageable);
     * Specification: 封装了 JPA Criteria 查询的查询条件
     * Pageable: 封装了请求分页的信息: 例如 pageNo, pageSize, Sort
    @Test
    public void testJpaSpecificationExecutor(){
        int pageNo = 3 - 1;
        int pageSize = 5;
            PageRequest pageable = PageRequest.of(pageNo, pageSize);
        //通常使用 Specification 的匿名内部类
        Specification<Person> specification = new Specification<Person>() {
        * @param root: 代表查询的实体类. 
        * @param query: 可以从中可到 Root 对象, 
        * 即告知 JPA Criteria 查询要查询哪一个实体类. 添加查询条件
        * @param cb: CriteriaBuilder 对象. 创建 Criteria 相关对象的工厂.
        * 当然可以从中获取到 Predicate 对象
        * @return: Predicate 类型, 代表一个查询条件. 
            @Override
            public Predicate toPredicate(Root<Person> root,
                    CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path path = root.get("id");
                Predicate predicate = cb.gt(path, 5);
                return predicate;
        Page<Person> page = personRepsotory.findAll(specification, pageable);
        System.out.println("总记录数: " + page.getTotalElements());
        System.out.println("当前第几页: " + (page.getNumber() + 1));
        System.out.println("总页数: " + page.getTotalPages());
        System.out.println("当前页面的 List: " + page.getContent());
        System.out.println("当前页面的记录数: " + page.getNumberOfElements());
    }


单条件查询

    Specification spc = new Specification(){
        @Override
         * @param root 根参数,通过次对象获取实体对象字段进行条件的设置
         * @param criteriaQuery 定义一个基本的查询
         * @param criteriaBuilder 创建一个查询条件
         * @return: javax.persistence.criteria.Predicate
        public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
            return criteriaBuilder.equal(root.get("username"),"AAA");
    };

多条件查询

直接在条件查询内写入查询的条件即可SpringData 会自动的将条件进行拼接

    Specification spc = (x,y,z)->z.and(z.equal(x.get("username"),"AAA"),
                                       z.equal(x.get("password"),"123"));
    List<Users> list = this.userDaoFive.findAll(spc);
    list.stream().forEach(System.out::println);

分页查询

    Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    Page<Users> page = this.userDaoFive.findAll(spc, new PageRequest(0, 5));
    page.getContent().stream().forEach(System.out::println);

排序查询

    Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    List<Users> list = 
    this.userDaoFive.findAll(spc, new Sort(Sort.Direction.DESC,"userid"));
    list.stream().forEach(System.out::println);

多条件排序

    List< Order> orders=new ArrayList< Order>();
    orders.add( new Order(Direction. ASC, "c"));
    orders.add( new Order(Direction. DESC, "d"));
    Pageable pageable= new PageRequest(pageNumber, pageSize, new Sort(orders));
    jpaRepo.findByAAndB(a,b,pageable);


分页+排序+条件

    Specification<Users> spc = (x,y,z)->z.ge(x.get("userid"),2);
    Page<Users> page = this.userDaoFive
    .findAll(spc, new PageRequest(0, 3, new Sort(Sort.Direction.DESC, "userid")));
    page.getContent().stream().forEach(System.out::println);


Spring Data JPA 注意点

  • @id @GeneratedValue(strategy = GenerationType.IDENTITY) 策略需要设置表id主键自增
  • @Entity 默认 UserInfo 表会映射数据库中的 user_info 表,相关错误 Unknown column 'user0_.create_time' in 'field list'
  • Table 'dev-test.hibernate_sequence' doesn't exist 一般是实体对象的主键生成策略设置问题
  • Unknown column in field list 表示实体对象中的字段与数据库不对应
  • jpa-hibernate-ddl-auto:update 配置自动更新或创建数据表
  • @Transient 表示该属性并非一个到数据库表的字段的映射,表示非持久化属性


补充

Iterable类型

  • java.lang.Iterable
  • java.util.Iterator
  • Iterator是迭代器类,而Iterable是接口。


好多类都实现了Iterable接口,这样对象就可以调用iterator()方法。

Jpa 中 Iterable<> 返回类型的值与 List<> 类型转换的两种方式

Iterable<Entity> geted = entityDao.findAll();
List<Entity> list = Lists.newArrays();
geted.forEach(single ->{list.add(single)});
import com.google.common.collect.Lists;
 @RequestMapping("/findAll")
    public Iterable<Person> findAll(){
        List<Person> people = Lists.newArrayList(demoService.findAll());
        return demoService.findAll();
    }


数据库连接配置

常用数据库连接配置

spring.datasource.url = jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&&serverTimezone=UTC
spring.datasource.username = 
spring.datasource.password = 
​

联合主键实体类配置

关键点

  • 联合主键 id 属性加入 @EmbeddedId
  • 联合主键类名前加入 @Embeddable 并实现有参无参构造方法
  • 可使用 Lombok 注解 @NoArgsConstructor @AllArgsConstructor
@Entity
@Table(name = "S_WSSCMB")
@Data
@Accessors(chain = true)
@ApiModel("文书生成模板")
public class Wsscmb implements Serializable {
    private static final long serialVersionUID = -9069767536642372411L;
    /** 联合主键 */
    @EmbeddedId
    @NotNull
    @Valid
    @ApiModelProperty("联合主键")
    private WsscmbPK id;
}


@Data
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("S_WSSCMB 表联合主键")
@Embeddable
public class WsscmbPK implements Serializable {
    private static final long serialVersionUID = -6264471737217641392L;
    /** 法院编号 */
    @Column(name = "{COURT_NO}", length = 4, nullable = false)
    @NotBlank
    @Length(max = 4)
    @JsonView(Wsscmb.SimpleView.class)
    @ApiModelProperty("法院编号")
    private String courtNo;
    /** 序号 */
    @Column(name = "{XH}", nullable = false)
    @NotNull(groups = UpdateGroup.class)
    @JsonView(Wsscmb.SimpleView.class)
    @ApiModelProperty("序号")
    private Integer xh;
}

@JsonView 使用

前端查询获取后端传递的对象数据,后端进行查询时会有冗余数据,可采用 @JsonView 指定返回的对象数据字段

用法

  • 首先在数据库映射实体对象里新建两个接口
    public interface SimpleView {};  
    public interface DetailView extends SimpleView{}; //继承
  • 然后用 @JsonView 在类的属性上指定视图包含字段
 @JsonView(UserSimpleView.class)
 private String username;
 @JsonView(UserDetailView.class)
  private String password;
  • Controller 指定返回数据使用的映射类的视图
    @GetMapping("query1")
    @JsonView(User.SimpleView.class)
    public List<User> query(){...}
    @GetMapping("query1")
    @JsonView(User.SimpleView.class)
    public List<User> query(){...}

此时 query1 返回的数据对象只包含 username 字段,而 query2 包含 username password

Lombok 常用注解

  • @Setter 注解在类或字段,注解在类时为所有字段生成setter方法,注解在字段上时只为该字段生成setter方法。
  • @Getter 使用方法同上,区别在于生成的是getter方法。
  • @ToString 注解在类,添加toString方法。
  • @EqualsAndHashCode 注解在类,生成hashCode和equals方法。
  • @NoArgsConstructor 注解在类,生成无参的构造方法。
  • @RequiredArgsConstructor 注解在类,为类中需要特殊处理的字段生成构造方法,比如final和被@NonNull注解的字段。
  • @AllArgsConstructor 注解在类,生成包含类中所有字段的构造方法。
  • @Data 注解在类,生成setter/getter、equals、canEqual、hashCode、toString方法,如为final属性,则不会为该属性生成setter方法。
  • @Slf4j 注解在类,生成log变量,严格意义来说是常量。private static final Logger log = LoggerFactory.getLogger(UserController.class);

Entity 中建立索引

@Entity
@Table(
    name = "{SPWF_FLOW}",
    indexes = {
        @Index(name = "IDX_TYPE_ZT_XH_ACT", 
               columnList = "{WF_FLOWTYPE_ID},{ZT},{XH},{WF_FLOWVER_ID_ACT}")
)


SpringBoot注解验证参数

废话不多说,直接上表格说明:

ata-draft-node="block" data-draft-type="table" data-size="normal" data-row-style="normal">

注解 作用类型 解释

以上注解用到要验证参数的封装类中的属性上:

findById(findOne())和Optional<T>的使用

1、findOne()方法的替代方法findById()

2.0版本,Spring-Data-Jpa修改findOne()。

1)2.0版本之前

T findOne(ID primaryKey);

2)2.0版本

  Optional<T> findById(ID id); 

2、Optional Optional<T>的使用

文档 Optional

container对象,可能包含也可能不包含非 null 值。如果存在值,则 isPresent() 将返回 true get() 将返回该值。提供依赖于是否存在包含值的其他方法,例如 orElse() (如果值不存在则返回默认值)和 ifPresent() (如果值存在则执行代码块)。 Optional<T> findById(ID id)中Optional的一些用法

1)如果没找到指定实体,则返回一个默认值。

Foo foo = repository.findById(id)
                    .orElse(new Foo());

或者

Foo foo = repository.findById(id)
                    .orElse(null);

2)如果找到实体返回它,否则抛出异常

return repository.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id));

3)假设希望根据是否找到实体来应用不同的处理(无需抛出异常)

Optional<Foo> fooOptional = fooRepository.findById(id);
if (fooOptional.isPresent()){