Spring Data JPA的研究

背景

最近的项目中需要对数据库进行大量的增删改查的动作,考量到大量的重复sql语句,用Mybatis会产生非常多的文件和sql语句,这是我们无意间就发现了JPA这个神奇的东西,JPA的功能十分的强大,大大的节省开发的时间,所以对JPA做了下技术调研,简单总结如下。

Spring Data JPA概述

介绍Spring Data JPA前,首先要介绍JPA,JPA(Java Persistence API)是Sun官方提出的Java持久化规范。它为Java开发人员提供了一种对象/关联映射工具来管理Java应用中的关系数据。它的出现主要是为了简化现有的持久化开发工作和整合ORM技术。
Spring data jpa是在JPA规范下提供了Repository层的实现,但是使用哪一种ORM需要你来决定。

Spring Data JPA使用

Spring Data JPA和SpringBoot是一对很好的搭档。如果有的小伙伴不知道SpringBoot的移驾去另一个页面看看。
http://192.168.2.181/DC-JN/res-share/blob/master/%E6%96%B0%E6%8A%80%E6%9C%AF%E8%B0%83%E7%A0%94/SpringBoot%20%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.md
1.需要在pom.xml中添加如下代码

<dependency>
              <groupId>org.springframework.boot</groupId>
              <artifactId>spring-boot-starter-data-jpa</artifactId>
   </dependency>
   <dependency>
              <groupId>org.springframework.data</groupId>
              <artifactId>spring-data-jpa</artifactId>
   </dependency>

2.开发阶段,声明持久层接口(其实就是所谓的Dao层,不明白的可以看图),该接口继承Repository

public interface MonitorResultRepository extends CrudRepository<MonitorResult,Integer>{
}

3.在持久层的接口中可以声明自己特殊的业务方法,Spring Data JPA将会根据指定的策略为该方法生成实现代码。用户不需要实现该接口。

public interface ETLJobStatusRepository extends CrudRepository<ETLJobStatus,ETLJobStatusPK> ,JpaSpecificationExecutor<ETLJobStatus> {
  // 通过任务名获取状态的实体
  @Query(value = "FROM ETLJobStatus t where t.id.jobName =?1 and TO_DAYS(t.id.startTime) = TO_DAYS(now())  order by t.id.startTime desc")
  ETLJobStatus getOne(String jobName);
  // 通过任务名称查询任务的状态信息
  @Query(value = "FROM ETLJobStatus t where t.id.jobName =?1 and TO_DAYS(t.id.startTime) = TO_DAYS(now())  order by t.id.startTime desc")
  List<ETLJobStatus> getAll(String jobName);
  // 对查询出来的任务名做去重
  @Query(value = "select  DISTINCT t.id.jobName  FROM ETLJobStatus t")
  Iterable<ETLJobStatus> selectName();
}

4.在业务层里面就可以直接调用持久层的内置方法或者自己声明的特殊方法了。内置方法会根据持久层声明的接口不同有所变化,例如CrudRepository接口,见下图。

  • 在业务层调用持久层内置方法
// 查询出符合条件的任务状态
    public Page<ETLJobStatus> findAll(ETLJobStatus etlJobStatus, Pageable pageable) {
        return  jobStatusRepository.findAll(initWhere(etlJobStatus), pageable);
    }
  • 在业务层调用持久层自定义的方法
// 查询不同的任务
    public Iterable<ETLJobStatus> selectName() {
        return  jobStatusRepository.selectName();
    }

Spring Data JPA分页和复杂查询操作

Spring Data JPA默认的接口不支持复杂查询操作,通过研究发现JpaSpecificationExecutor这个接口可以完美的解决分页和复杂的查询操作。

public interface JpaSpecificationExecutor<T> {
     T findOne(Specification<T> var1);

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

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

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

     long count(Specification<T> var1);
}
  • 首先要让自己的持久层继承JpaSpecificationExecutor这个接口,然后在根据接口的方法Page findAll(Specification var1, Pageable var2)实现分页和复杂的查询功能。其中Specification参数是一个难点,它主要的功能是为了实现复杂查询。里面有许多的坑要格外的注意。
// 条件查询功能
    public Page<MonitorResult> list(MonitorResult monitorResult, Pageable pageable) {
        Page<MonitorResult> list = null;
        try{

           list = resultRepository.findAll(initWhere(monitorResult), pageable);
        }catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
    // jpa特殊的用法,用于做复杂的条件查询
    private Specification<MonitorResult> initWhere(final MonitorResult monitorResult) {
        return new Specification<MonitorResult>() {
            @Override
            public Predicate toPredicate(Root<MonitorResult> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> list = new ArrayList<>();
                SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd");
                try {
                    // 对日期的开始时间进行判空
                    if (!StringUtil.isNullOrEmpty(monitorResult.getTimesBegin())) {
                        Path<Date> excuteTime = root.get("excuteTime");
                        list.add(cb.greaterThanOrEqualTo(excuteTime, sdf.parse(monitorResult.getTimesBegin())));
                    }
                    // 对日期的结束时间进行判空
                    if (!StringUtil.isNullOrEmpty(monitorResult.getTimesEnd())) {
                        Path<Date> excuteTime = root.get("excuteTime");
                        list.add(cb.lessThanOrEqualTo(excuteTime,sdf.parse(monitorResult.getTimesEnd())));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 对监控任务的类型进行判空
                if (!StringUtil.isNullOrEmpty(monitorResult.getTask().getMonitorType().getName())) {
                    Path<String> typeId = root.get("task").get("monitorType").get("name");
                    list.add(cb.equal(typeId,monitorResult.getTask().getMonitorType().getName()));
                }
                // 对机器的IP进行判空
                if (!StringUtil.isNullOrEmpty(monitorResult.getIpBegin())) {
                    Path<String> ipBegin = root.get("task").get("monitorIp");
                    list.add(cb.greaterThanOrEqualTo(ipBegin, monitorResult.getIpBegin()));
                }
                return query.where(list.toArray(new Predicate[list.size()])).getRestriction();
            }
        };
    }

Spring Data JPA实体类(多表查询)

其实实体类是一个非常复杂的环节也是Spring Data JPA的关键所在,就相当盖房子的地基,首先就是实体类里面的注解。我们拿一个具体的实体类做例子。

@Entity
@Table( name = "monitor_result")
public class MonitorResult {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private int id;

  @ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, optional = true)
  @JoinColumn(name = "taskId", columnDefinition = "int comment '所属任务'")
  private MonitorTask task;

  @Column(columnDefinition = "datetime comment '执行时间'")
  private Date excuteTime;
  @Column(columnDefinition = "varchar(200) comment '任务结果'")
  private String result;
  @Column(columnDefinition = "int(1) comment '状态:0:监控结果正常,1:监控结果异常,不用发告警信息,2:监控结果异常,需要发告警信息,3:监控结果异常,已发告警信息'")
  private int status;
  @Transient
  private String timesBegin;
  @Transient
  private String timesEnd;

  public String getIpBegin() {
    return ipBegin;
  }

  @Transient
  private String ipBegin;
  @Transient
  private String ipEnd;

  public MonitorResult(int id,Date excuteTime, String result, int status,MonitorTask task) {
    this.task = task;
    this.id = id;
    this.excuteTime = excuteTime;
    this.result = result;
    this.status = status;
  }
  public  MonitorResult (){}

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public MonitorTask getTask() {
    return task;
  }

  public void setTask(MonitorTask task) {
    this.task = task;
  }

  public Date getExcuteTime() {
    return excuteTime;
  }

  public void setExcuteTime(Date excuteTime) {
    this.excuteTime = excuteTime;
  }

  public String getResult() {
    return result;
  }

  public void setResult(String result) {
    this.result = result;
  }

  public int getStatus() {
    return status;
  }

  public void setStatus(int status) {
    this.status = status;
  }


  public void setIpBegin(String ipBegin) {
    this.ipBegin = ipBegin;
  }

  public String getIpEnd() {
    return ipEnd;
  }

  public void setIpEnd(String ipEnd) {
    this.ipEnd = ipEnd;
  }

  public String getTimesBegin() {
    return timesBegin;
  }

  public void setTimesBegin(String timesBegin) {
    this.timesBegin = timesBegin;
  }

  public String getTimesEnd() {
    return timesEnd;
  }

  public void setTimesEnd(String timesEnd) {
    this.timesEnd = timesEnd;
  }
}

其中@Entity 代表的是实体,@Table代表的是具体操作哪一张表,@Id代表的是主键,@GeneratedValue(strategy = GenerationType.AUTO)代表的是主键生成策略,@Column代表的是字段类型,可以有多个参数做选择, @Transient代表的是瞬时字段(不需要与数据库映射的字段,在保存的时候不需要保存倒数据库)

总结

Spring Data JPA的优点

1.极大的简化的增删改查的代码量。
2.节省了开发时间,可以将工作重心转移到其他业务的层面。
3.减少了sql的书写,避免了很多不必要的错误出现。

Spring Data JPA的缺点

1.入门比较难,需要耐着性子学习,同时需要有hibernate的基础。
2.复杂的多表查询显得比较鸡肋。

参考网站