欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring Data JPA

程序员文章站 2022-04-25 20:01:24
...

对Spring Data JPA进行了一些研究,基本上是参考Spring Data JPA - Reference Documentation官方文档和github-spring-data-jpa/test.

Introduction

官方定义
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.

要解释清楚Spring Data JPA是什么,那么需要一步步说起:

期初JAVA需要通过各个数据库厂商提供的API进行数据库的访问,后来JAVA提出了JDBC,程序直接使用JDBC这套规范就可以跟各个数据库进行对接;
接着诞生了ORM技术,简化了Java对象的持久化工作,出现了Hibernate、TopLink等ORM框架;

Sun公司在JDK1.5的时候,吸收了Hibernate、TopLink等ORM框架的优点,提出了Java持久化规范:JPA;

Hibernate在3.2的时候提供了JPA的实现,其余的JPA的供应商还有诸如OpenJPA、Toplink等;

Spring在做持久化这一块的工作,开发了Spring-data-xxx这一系列包,如:Spring-data-jpa,Spring-data-redis,Spring-data-mongodb等等,这些都是Spring 提供的基于JPA 和其他一些 NOSQL的Repository。

Long story short, then, Spring Data JPA provides a definition to implement repositories that is supported under the hood by referencing the JPA specification, using the provider you define.

Spring Data JPA 是在JPA规范的基础下提供了Repository层的实现,但是使用哪一款ORM需要你自己去决定;相比我们更为熟悉的Hibernate和MyBatis,Spring Data JPA 可以看做更高层次的抽象。

TODO:
Spring Data JPA 默认使用的是Hibernate,是通过eclipselink(https://github.com/spring-projects/spring-data-examples/blob/be2c0b298cf77e4cbe2d648fb989d29fe3808588/jpa/eclipselink/pom.xml#L24)进行判断的;但是没有进一步进行验证。

Dependencies

spring-data-jpa/quick-start

<dependencies>
  <dependency>
  <groupId>org.springframework.data</groupId>
  <artifactId>spring-data-jpa</artifactId>
  <version>1.10.5.RELEASE</version>
  </dependency>
</dependencies>

如果要在Spring Boot下使用Spring Data JPA的话,需要引入:

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

Working with Spring Data Repositories

public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
 
  <S extends T> S save(S entity);
 
  T findOne(ID primaryKey);  
 
  Iterable<T> findAll(); 
 
  Long count();  
 
  void delete(T entity); 
 
  boolean exists(ID primaryKey); 
 
  // … more functionality omitted.
}

编写业务相关的Repository:UserCrudRepository.java

public interface UserCrudRepository extends CrudRepository<User, String>{
 
    User findOne(String userid);
 
    List<User> findByName(String name);
 
    List<User> findByNameAndAgeLessThan(String name, int age);
 
    void deleteByNameAndAgeLessThan(String name, int age);
 
    List<User> findDistinctByName(String name);
 
    List<User> findByNameIgnoreCase(String name);
 
    User findFirstByOrderByUseridDesc();
}

Repositories

Spring Date JPA提供了几个接口:

  • Repository:最顶层的接口,是一个空的接口,目的是为了统一所有Repository的类型,且能让组件扫描的时候自动识别。
  • CrudRepository:是Repository的子接口,提供CRUD的功能。
  • PagingAndSortingRepository :是CrudRepository的子接口,添加分页和排序的功能。
  • JpaRepository :是PagingAndSortingRepository的子接口,增加了批量操作等功能。

以上四个 XxxRepositoty 越来越具体、功能越来越丰富,从使用的角度应该是继承自己用到的最小集,如需扩展再做调整,嫌麻烦的话直接继承 JpaRepository 也没关系。

Query methods

Query creation

可以直接使用CrudRepository中的findOne和findAll方法

public interface UserCrudRepository extends CrudRepository<User, String>{
 
    /**
     * CrudRepository 中存在的方法,这里可以不写此方法 
     * CrudRepository中存在的方法:
     * https://github.com/spring-projects/spring-data-commons/blob/master/src/main/java/org/springframework/data/repository/CrudRepository.java#L27
     */
    User findOne(String userid);
 
 
    /**
     * 查询所有 可以不写此方法 
     * 不写,需要在查询完成后强转:List<User> userAfterDel = (List<User>) this.repository.findAll();
     * 写:List<User> userAfterDel = this.repository.findAll();
     */
    //List<User> findAll();
}

也可以对查询方法进行扩展,扩展的格式为:find…By, read…By, query…By, get…By, count…By

public interface UserCrudRepository extends CrudRepository<User, String>{
    /**
     * 按照姓名查询,非主键 更多用法参考:
     * http://docs.spring.io/spring-data/jpa/docs/1.10.6.RELEASE/reference/html/#new-features.1-10-0
     * Table 4. Supported keywords inside method names
     *
     * @param name
     * @return
     */
    List<User> findByName(String name);
 
    List<User> queryByName(String name);
 
    List<User> findByNameAndAgeLessThan(String name, int age);
 
    List<User> findDistinctByName(String name);
   
    List<User> findByNameIgnoreCase(String name);
}

TODO
以下两点是测试发现的问题,但是有可能通过一些配置去解决:

如果数据库字段带有[ _ ]符号的,JAVA的Entity类中对应的属性不能带有[ _ ],需要通过@Column(name = "")标签进行关联。

如果JAVA的Entity类中属性按照驼峰格式命名,也需要通过@Column(name = "")标签进行关联。

Using @Query

使用@Query Annotation 绑定SQL语句

public interface UserCrudRepository extends CrudRepository<User, String>{
    //@Query("select u from User u where u.name = ?1")
    //@Query("select u from User u where 1=1 and u.name = ?1")
    //@Query("select u.userid from User u where 1=1 and u.name = ?1")
    //@Query(value="select * from User u where u.name = ?1", nativeQuery = true)
    List<User> findByName(String name);
}

使用@Param Annotation 可以将参数中的名字和query中的名字进行绑定

public interface UserCrudRepository extends CrudRepository<User, String>{
    @Query("select u from User u where u.name = :name and u.gender = :gender")
    List<User> findUsersByNameAndGender(@Param("name")String name , @Param("gender")String gender);
}

Using Sort

public interface UserCrudRepository extends CrudRepository<User, String>{
 
    List<User> findByOrderByUseridDesc();
}

public interface UserPageRepository extends PagingAndSortingRepository<User, String> {
 
    List<User> findAll(Sort sort);
}

Using Pageable

需要extends PagingAndSortingRepository或更高级的JpaRepository。

public class UserPageRepositoryTest {
  @Test
  public void findPageable() throws Exception {
    Page<User> usersPageOne = this.repository.findAll(new PageRequest(0, 2)); //1.PageRequest(int page, int size) 2.page从0开始
     
    assertThat(usersPageOne.getContent().size()).isEqualTo(2);
    assertThat(usersPageOne.getContent().get(0).getUserId()).isEqualTo("1");
     
    assertThat(usersPageOne.hasPrevious()).isEqualTo(false);  //判断是否有上一页
     
    Page<User> usersPageTwo = this.repository.findAll(usersPageOne.nextPageable());  //查询下一页
    assertThat(usersPageTwo.getContent().size()).isEqualTo(2);
    assertThat(usersPageTwo.getContent().get(0).getUserId()).isEqualTo("3");
  }
}

Limiting query results

通过 [first] 或 [top] 关键字,取得结果集的前N条数据。

public interface UserCrudRepository extends CrudRepository<User, String>{
 
    User findFirstByOrderByUseridDesc();
 
    User findFirstByGenderOrderByUseridDesc(String gender);
}

Save methods

保存一个Entity需要使用CrudRepository.save(…)方法,包括Insert和Update。

Delete methods

Delete 方法基本可以参考Query 方法,除了delete()和deleteAll()之外,还可以使用deleteBy...的方式,同样也可以使用@query标签。

在JpaRepository接口中,提供了deleteInBatch()的方法:Deletes the given entities in a batch which means it will create a single {@link Query}.

TODO
联合主键的情况下,deleteInBatch()方法最终的SQL会写成一个SQL,多组条件用or进行关联,而find()方法,则会被拆分成多条SQL语句进行执行。

Why use Spring Date JPA

  • 可以看出Spring Data JPA和常用的Hibernate和MyBatis相比,在编码上更简洁,减少Boilerplate Code。

  • 实际上交易型的微服务应用的查询通常只是明细查询或简单的列表查询,而真正的复杂查询或者数据分析通常应是另建应用或者全文检索或者 ODS/DW 之类、并从交易型微服务同步数据,而这一部分是走 MyBatis 还是 NOSQL 另说。

  • Feign 虽然解决的不是 Spring Data Repository 同一个领域的问题,但是实现哲学是一致的,通过接口、COC 尽量减少 Rest 调用的 Boilerplate Code。

    @FeignClient(url = "https://api.github.com")
    interface GitHubClient {
    @RequestMapping(method = RequestMethod.GET, value = "/repos/{owner}/{repo}/contributors")
    List<Contributor> contributors(@RequestParam("owner") String owner, @RequestParam("repo") String repo);
    }