转载

Spring Data JPA

对Spring Data JPA进行了一些研究,基本上是参考Spring Data JPA - Reference Documentation官方文档。

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。

http://stackoverflow.com/questions/16148188/spring-data-jpa-versus-jpa-whats-the-difference 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 可以看做更高层次的抽象。

Dependencies

具体步骤可以参考官网: [spring-data-jpa/quick-start](https://spring.io/projects/spring-data-jpa)

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-jpa</artifactId>
    </dependency>
</dependencies>
复制代码

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

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
复制代码

Working with Spring Data Repositories

让我们看一个简单的例子,实现增删查改的功能;从例子中我们可以发现,可以通过方法名称的定义,就可以达到 SQL 的效果:

比如 findByName(String name) 就相当于 select * from user where name = ?

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 中存在的方法,这里可以不写此方法 
     */
    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>{
    /**
     * 按照姓名查询,非主键:
     * @param name
     * @return
     */
    List<User> findByName(String name);

    List<User> queryByName(String name);

    /**
     * 按照姓名查询,并且年龄小于入参:
     * @param name
     * @return
     */
    List<User> findByNameAndAgeLessThan(String name, int age);

    List<User> findDistinctByName(String name);

    List<User> findByNameIgnoreCase(String name);
}
复制代码

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

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

NamingStrategy

解决上面的第二点问题:如果 JAVA 的 Entity 类中属性按照驼峰格式命名,也需要通过 @Column(name = "") 标签进行关联。

JPA 中的 NamingStrategy 接口(最先是 Hibernate 中提出),实现接口中的表名与列名命名函数,可以完成自己的命名策略设定。

举个例子,我们经常会遇到这样的问题,表的列明都是带 的:user_id,user_name…,如果 User.java 中的属性都是不带 的,那么需要使用 @Column 标签进行和数据库字段的绑定:

@Entity
@Table(name = "USER")
@Data
public class User {
  @Column(name = "user_id")
  @Id
  private String userId;

  @Column(name = "user_name")  //如果这样的字段很多,也是一个比较大的工作量
  private String userName;
}
复制代码

而通过 NamingStrateg y接口的实现,可以统一的完成驼峰转下划线的功能,userId → user_id 、 userName → user_name;

Spring Boot提供了Configure JPA properties,我们可以可以直接在application.yml中进行配置:

spring.jpa.hibernate.naming.physical-strategy = com.example.MyPhysicalNamingStrategy
复制代码

在Hibernate5中,提供了两种映射策略:

  • spring.jpa.hibernate.naming.physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    不做任何修改,userId→ userId,userid → userid
  • spring.jpa.hibernate.naming.physical-strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    将驼峰修改成下划线,userId → user_id,如果不做配置,默认这个策略
@Entity
@Table(name = "USER")
@Data
public class User {
  @Id
  private String userId;  //不需要在使用@Column(name = "user_id"),会自动转成 user_id

  private String userName; //自动转成 user_name
}
复制代码

同时会有一个问题,如果数据库中表的列名规则不统一,比如有些带下划线,有些不带下划线的话,那么在编写Entity的时候,带下划线的字段写成驼峰格式,不带下划线的字段写成全小写,看起来也比较怪异。

@Entity
@Table(name = "USER")
@Data
public class User {
  @Id
  private String userId;  //不需要在使用@Column(name = "user_id")

  private String userName;

  private String cardid;  //列名为 cardid ,不带下划线,所以这里不能写成驼峰格式,但是从代码风格上看,感觉比较怪异
}
复制代码

如果有更特殊的要求,例如数据库中所有的表或者字段都带固定格式的前缀,CSC_USER 表,字段有CSC_USER_ID, CSC_USER_NAME,可以自己去实现 NamingStrategy 接口(如果是 Spring Boot 的话,需要实现

PhysicalNamingStrategy 接口),在里面去增加前缀名称。

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(); //按 Userid 倒序
}
复制代码

也可以继承 PagingAndSortingRepository ,使用 Sort 进行排序:

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");
  }
}
复制代码

也可以通过 [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}.

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);
}
复制代码
原文  https://juejin.im/post/5de9b7fd518825124f53a639
正文到此结束
Loading...