JPA使用

JPA使用

最近在做一个化工实验平台的项目技术栈使用的是SpringBoot+JPA+MySql,刚接触的时候有些棘手,但是上手后发现太香了,感觉比Mybatis好用很多,听人说因为国内主流用的是Mybatis所以大环境下用的都是Mybatis。但不得不承认JPA确实比Mybatis好用,话不多说,让我们来了解一下JPA的使用。

简介

目的:实现应用程序的数据访问层一直是很繁琐的,总是要写很多的样板代码来执行简单的查询,比如分页、审计。Spring DataJPA旨在改进数据访问层的实现,减少开发时间。开发者在编写数据库接口时,包括自定义的查询方法,Spring DataJPA会自动提供其实现。

参考文档:https://www.cntofu.com/book/88/index.html

我们认为它是java ( JVM ) 世界中构建技术的一个飞跃.spring data jpa 提供了:像操作对象一样操作数据库标准的封装。

Spring Data JPA

Spring Data JPA不是一个JPA实现,它是一个框架或库,提供了JPA Provider之上的一个额外的抽象层。如果我们决定使用Spring Data JPA,那么应用程序的后端部分至少会包含三层:
1) Spring Data JPA
Spring Data JPA提供了JPA Provider的抽象层。
2) Spring Data Commons
Spring Data Commons提供了共享的基础构件。
3) JPA Provider
java Persistence API的实现。
看起来Spring DataJPA使我们的应用程序变得更复杂,从某些方面来说确实如此。但是要明白,Spring DataJPA减少了我们编写样板代码的时间。

项目持久层的结构如图所示:

image-20211224234655979

Spring Data Repositories介绍
Spring Data JPA依赖于Spring Data Commons一它是一个数据库抽象层,提供了共享的数据库基础构件。
我们使用Spring DataJPA时无需关注任何数据库抽象层的实现,但必须熟悉Spring Data数据库接口。这些接口的描述如下:
1、Spring Data Commons提供了如下接口

  • Repository<T,ID extends Serializable>接口它是一个标记接口,有两个目标:
    一是捕获托管实体的类型和实体ID的类型
    二是在类路径扫描期间,帮助Spring容器找到具体的数据仓库接口。
  • CrudRepository<T,ID extends Serializable>接口
    提供了对托管实体的CRUD操作。
  • PagingAndSortingRepository<T,ID extends Serializable>接口
    提供了对从数据库取回的实体的分页和排序操作方法。
  • QueryDslPredicateExecutor接口
    此接口不是数据仓库接口。它声明了通过查询DSL谓词对象检索数据库的方法。

2、Spring Data JPA提供了如下接口

  • JpaRepository<T,ID extends Serializable>接口
    它是一个JPA特定的数据仓库接口,是Commons数据仓库接口背后的单一接口声明的方法组合。
  • JpaSpecifcationExecutor接口
    此接口不是数据仓库接口。它声明了通过使用JPA标准API的Specifcation《T》对象从数据库取回实体的方法。

数据仓库的分层看起来如图所示:

image-20211224235738792

可以看到IDE中的继承关系

image-20211225000013093

可以看到还有一个QueryByExampleExecutor,这个可以使用Example进行查询,

image-20211225000731157

image-20211225000044578

image-20211225000234801

定义依赖

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

定义实体

@Entity
@Table(name="booking")
public class BookingEntity extends BaseEntity implements Serializable {
    @ManyToOne//定义了一对多的关系,即一个用户有多个预定,在预定表中,有一个外健account_id
    private Account consumer;
    @ManyToOne
    private TicketEntity ticket;
}

//@ManyToMany的关系,一对多的关系,在数据库中就有除了这两个实体的第三张表
@Entity
@Table(name="daily_open_plan")
@Data
public class DailyOpenPlanEntity extends BaseEntity implements Serializable {
    @ColumnDefault("")//可以使用注释,对数据库中字段进行设置
      private String name;
    @ManyToMany
    private List<PeriodOfTimeEntity> periodOfTimes;
}

//还有@OneToOne一对一的关系

项目运行,会自动检测连接的数据库是否已经建立好这几个实体的表,如果没有建立,或者一些属性没有添加,可以直接进行修改。

定义Repository

public interface AccountRepository extends JpaRepository<Account, Long> {
}

声明一个接口继承Repository或其子类,输入实体类型和ID类型。

从下图可以看到,JpaRepository已经帮我们封装好了一些方法,可以直接使用,用来操作数据库。

image-20211225003859822

还有父类的CrudRepository的一些方法都可以直接使用

image-20211225004059976

假如我们想要的方法或者说sql没有定义,那JPA提供属性值拼接方法名,以及编写原生Sql的方法,具体看下面章节

定义查询方法

query builder机制内置为构建约束查询库的实体。 带前缀的机制findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy自动解析的其余部分。进一步引入子句可以包含表达式等Distinct设置不同的条件创建查询。 然而,第一个By作为分隔符来表示实际的标准的开始。 在一个非常基础的查询,可以定义条件And或者Or

详细看下面例子:

根据方法名创建查询

public interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
    //对于时间属性还可以用Befor和After来选择时间段要求
  public PeriodOfTimeEntity getPeriodOfTimeEntityByStartTimeBeforeAndAndEndTimeAfter(LocalTime starTime,LocalTime endTime);
  • 遍历表达式通常结合运算符连接。您可以把表达式AndOr,Between,LessThan(不超过) , GreaterThan,Like等运算符,这些操作对不同的数据库可能有所不同,具体参考各参考文档
  • 方法解析支持设置IgnoreCase在属性上面(如,findByLastnameIgnoreCase(…)),或者支持查询所有属性忽略大小写(如,findByLastnameAndFirstnameAllIgnoreCase(…)), 忽略大小写支持所有的数据库,其它的查询参考相关文档
  • 您可以应用静态排序通过附加一个OrderBy基准进行排序,引用属性和方向提供了一个排序(ascDesc)。 创建一个支持动态排序的查询方法,明白了特殊参数处理 。

使用@Query,来写复杂的Sql语句

public interface AccountRepository extends JpaRepository<Account, Long> {
    Account findByUsername(String username);

    @Query(value = "select  a from Account a left join Role r on a.id=r.account.id where r.roleName=?1")
    //@Query(nativeQuery = true,value = "select * from account a left join role r on r.account_id=a.id where r.roleName=?1") 纯原生
    Page<Account> findAcccounts(String roleName,Pageable pageable);
}

public interface BookingRepository extends JpaRepository<BookingEntity,Long> {
    @Query(value = "select * " +
            "from booking  b " +
            "left join ticket t on b.ticket_id=t.id " +
            "left join resource r on t.resource_id=r.id "+
            "left join Account account on b.consumer_id=account.id" +
            " where r.id=?1 and if(IFNULL(?2,'') !='',account.grade=?2,1=1) and if(IFNULL(?3,'') !='',account.username=?3,1=1) and if(IFNULL(?4,'') !='',t.date between ?4 and ?5,1=1) ",nativeQuery = true)
    Page<BookingEntity> findHaveDoneExp(Long resourceId, String grade, String username, LocalDate startDate, LocalDate endDate, Pageable pageable);
}
//if(IFNULL(?2,'') !='',account.grade=?2,1=1)这个表达式可以让这个查询属性为null的时候不参与条件筛选,做到动态拼接条件


public interface BoardRepository extends JpaRepository<Board, Long> {
    @Query(value = "select new cn.edu.tju.cs.experimenting.vo.BoardVo(" +
            "b.createTime,a.username,b.message)  from Board as b,Account  as a " +
            "where b.ticket.id = ?1 AND b.account.id=a.id")
    List<BoardVo> returnBoardVoList(Long ticketId);
  //返回自定义的类
}

目前还有使用Example查询器,以及Specification等构造复杂查询的,这个留到等到真正用到的时候再写。

限制查询结果

TopFirst查询限制结果大小

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

     Slice<User> findTop3ByLastname(String lastname, Pageable pageable);

     List<User> findFirst10ByLastname(String lastname, Sort sort);

     List<User> findTop10ByLastname(String lastname, Pageable pageable);

排序和分页

排序

//应用静态排序通过附加一个OrderBy基准进行排序,引用属性和方向提供了一个排序(asc或 Desc)。
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
//在@Query中自己写order by
//asc 升序或 Desc降序

PageableSort应用动态查询分页和排序

List<User> findByLastname(String lastname, Sort sort);
//Pageable 里可以包含排序方法
//Page里包含了 总页数
Page<User> findByLastname(String lastname, Pageable pageable);
//可以直接返回分页结果,即List
List<User> findByLastname(String lastname, Pageable pageable);

//看具体例子
//Repo里的方法
Page<BookingEntity> findHaveDoneExp(Long resourceId, String grade, String username, LocalDate startDate, LocalDate endDate, Pageable pageable);
//具体使用
Pageable pageable=PageRequest.of(PageNum-1,pageSize,Sort.Direction.ASC, "t.date");
Page<BookingEntity> haveDoneExp=bookingRepository.findHaveDoneExp(searchExpVo.getResourceId(),...,pageable);

//只使用分页功能
//Repo里的方法
Page<Account> findAcccounts(String roleName,Pageable pageable);
//具体使用
Pageable pageable=PageRequest.of(pageNum-1, pageSize);

注:Example查询器可以和Pageable结合使用

异步查询结果

@Async
Future<User> findByFirstname(String firstname); 

总结

​ JPA能像操作对象一样操作数据库,帮助我们减轻了对数据库语句的编写压力,让后端业务的开发更加关注于业务。使用JPA开发,对小型简单项目来说十分方便。

当然我目前使用到的JPA功能只是他的冰山一角,后续有更进阶的一些用法,后续会补充~


   转载规则


《JPA使用》 朱林刚 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Go string和[]byte详解 Go string和[]byte详解
string和[]byte详解string标准概念Go标准库builtin给出了所有内置类型的定义。 源代码位于src/builtin/builtin.go,其中关于string的描述如下: // string is the set of
2022-01-10
下一篇 
GRPC框架 GRPC框架
GRPC框架GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架。GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多个服务,对于移动设备更加友好。目前提供 C、Java 和 Go 语言版本,分别是:g
2021-12-24
  目录