SpringBoot第九篇:整合Spring Data JPA


作者:追夢1819
原文:https://www.cnblogs.com/yanfei1819/p/10910059.html
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

前言

  前面幾章,我們介紹了 JDBCTemplate、MyBatis 等 ORM 框架。下面我們來介紹極簡模式的 Spring Data JPA。

Spring Data JPA

簡介

  我們先來了解幾個基本概念,捋一下各個概念之間的關系。

1、JPA

  JPA是Java Persistence API的簡稱,中文名Java持久層API,SUN公司出品。是 JDK 5.0 注解或 XML 描述對象-關系表的映射關系,並將運行期的實體對象持久化到數據庫中。

  它的出現目的有兩個:

  • 簡化數據庫訪問層;
  • 實現 ORM 框架的統一(目前還沒有完全做到)。

2、JPA 和 hibernate 的關系

  • JPA 是 Hibernate 的一個抽象,就像 JDBC 和 JDBC 驅動的關系;

  • JPA 是一種 ORM 規范,是 Hibernate 功能的一個子集 ;

  • Hibernate 是 JPA 的一個實現;

3、Spring Data

  眾所周知,Spring 是一個大家庭,一個技術的生態圈,其中整合的內容包羅萬象。而 Spring Data xxx 系列就是 Spring 這個生態圈對數據持久化層的整合。

  Spring Data是Spring用來解決數據訪問的解決方案,它包含了大量關系型數據庫以及NoSQL數據庫的數據持久層訪問解決方案。它包含Spring Data JPA、Spring Data MongoDB、Spring Data Neo4j、Spring Data GernFile、Spring Data Cassandra等。

  目前現有的 Spring-data-jpa,Spring-data-template,Spring-data-mongodb,Spring-data-redis 等。當然,也包括最開始用的 mybatis-spring ,MyBatis 與 Spring 的整合。

  於是,就出現了 Spring-data-jpa ,Spring 與 JPA 的整合。

  具體詳情參看 Spring Data 官網介紹

4、關於 "dao"

  在 Java web 項目中,我們經常會看到這個包名或者以它為后綴名的類名,它所代表的是,數據庫持久化層,是 Data Access Object 的簡寫。不過有時候我們也會看到 mapper 等。其實這些都是同一個意思,代表數據庫持久化層。

  在 JDBCTemplate 中,可能會用 "dao",在 MyBatis 中,可能會用 "mapper"(對應 xxxMapper.xml文件),在 JPA 中,可能會用"repository"等。

  其實名稱完全是自定義,只不過開發者在開發的時候會有一些約定俗成的習慣,僅此而已。

5、Spring Data JPA

  正如上所說,Spring Data JPA 是 Spring 與 JPA 的整合。是基於 JPA 對數據庫訪問層的增強支持。旨在簡化數據庫訪問層,減少工作量。

  具體詳情參看 Spring Data JPA 官網


核心概念

  對於開發者來說,使用起來很簡單。下面我們看看 Spring Data JPA 幾個核心概念。

  • Repository:最頂層的接口,是一個空的接口,目的是為了統一所有Repository的類型,且能讓組件掃描的時候自動識別;

  • CrudRepository :是Repository的子接口,提供CRUD的功能;

    @NoRepositoryBean
    public interface CrudRepository<T, ID> extends Repository<T, ID> {
    	<S extends T> S save(S entity);
    	<S extends T> Iterable<S> saveAll(Iterable<S> entities);
    	Optional<T> findById(ID id);
    	boolean existsById(ID id);
    	Iterable<T> findAll();
    	Iterable<T> findAllById(Iterable<ID> ids);
    	long count();
    	void deleteById(ID id);
    	void delete(T entity);
    	void deleteAll(Iterable<? extends T> entities);
    	void deleteAll();
    }
    
  • JpaRepository:是PagingAndSortingRepository的子接口,增加了一些實用的功能,比如:批量操作等;

    @NoRepositoryBean
    public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
        List<T> findAll();
        List<T> findAll(Sort var1);
        List<T> findAllById(Iterable<ID> var1);
        <S extends T> List<S> saveAll(Iterable<S> var1);
        void flush();
        <S extends T> S saveAndFlush(S var1);
        void deleteInBatch(Iterable<T> var1);
        void deleteAllInBatch();
        T getOne(ID var1);
        <S extends T> List<S> findAll(Example<S> var1);
        <S extends T> List<S> findAll(Example<S> var1, Sort var2);
    }
    
  • PagingAndSortingRepository:是 CrudRepository 的子接口,添加分頁和排序的功能;

    @NoRepositoryBean
    public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
    	Iterable<T> findAll(Sort sort);
    	Page<T> findAll(Pageable pageable);
    }
    
  • Specification:是Spring Data JPA提供的一個查詢規范,要做復雜的查詢,只需圍繞這個規范來設置查詢條件即可;

    public interface Specification<T> extends Serializable {
        long serialVersionUID = 1L;
        static <T> Specification<T> not(Specification<T> spec) {
            return Specifications.negated(spec);
        }
        static <T> Specification<T> where(Specification<T> spec) {
            return Specifications.where(spec);
        }
        default Specification<T> and(Specification<T> other) {
            return Specifications.composed(this, other, CompositionType.AND);
        }
        default Specification<T> or(Specification<T> other) {
            return Specifications.composed(this, other, CompositionType.OR);
        }
        @Nullable
        Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
    }
    
  • JpaSpecificationExecutor:用來做負責查詢的接口。

    public interface JpaSpecificationExecutor<T> {
        Optional<T> findOne(@Nullable Specification<T> var1);
        List<T> findAll(@Nullable Specification<T> var1);
        Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
        List<T> findAll(@Nullable Specification<T> var1, Sort var2);
        long count(@Nullable Specification<T> var1);
    }
    

Spring Data JPA的使用

  下面我們演示幾種 JPA 的使用。

使用內置方法

  構建SpringBoot項目,首先引入 maven 依賴:

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

  然后,application.properties 中配置相關的信息:

server.address=
server.servlet.context-path=
server.port=8082

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://192.168.1.88:3306/test?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=pass123

# 第一次建表用create,后面使用update
# 不然每次重新系統工程都會先刪除表再新建
spring.jpa.hibernate.ddl-auto=create
# 控制台打印sql
spring.jpa.show-sql=true

  下一步,創建實體類 User:

package com.yanfei1819.jpademo.entity;
import javax.persistence.*;

/**
 * Created by 追夢1819 on 2019-05-05.
 */
@Entity
@Table(name="user")
public class User {
    @Id
    // 字段自動生成
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    private int age;
    private String name;
	// set/get 省略
}

  再者,創建 dao 層:

package com.yanfei1819.jpademo.repository;
import com.yanfei1819.jpademo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

/**
 * Created by 追夢1819 on 2019-05-05.
 */
public interface UserRepository extends JpaRepository<User,Long> {
    
}

  最后,創建controller 層(此處為了簡化代碼,省略了 service 層):

package com.yanfei1819.jpademo.web.controller;

import com.yanfei1819.jpademo.entity.User;
import com.yanfei1819.jpademo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;

/**
 * Created by 追夢1819 on 2019-05-05.
 */
@Controller
public class UserController {
    @Autowired
    private UserRepository userRepository;
    
    @ResponseBody
    @GetMapping("/queryUser")
    public List<User> queryUser(){
        // 通過 jpa 內置的方法進行查詢
        return userRepository.findAll();
    }
}

  在上述示例中,我們使用的是 JPA 的內置方法 findAll() ,另外也有很多別的方法,如下:

  寫了幾個查詢方法。


創建查詢方法

  在以上示例的基礎上,我們添加一個新的方法:

UserController:

@ResponseBody
@GetMapping("/queryUser/{name}")
public User queryUserByName(@PathVariable String name){
    // 通過自定義的方法進行查詢
    return userRepository.findByName(name);
}

UserRepository:

User findByName(String name);

  spring-data-jpa 會根據方法的名字來自動生成 sql 語句,Spring Data JPA 支持通過定義在 Repository 接口中的方法名來定義查詢,方法名是根據實體類的屬性名來確定的。我們只需要按照方法定義的規則即可。其中findBy關鍵字可以用find、read、readBy、query、queryBy、get、getBy替代。

  這種方式不僅僅限制這種簡單的參數支持,還包括限制返回的查詢的結果、返回流式查詢結果、異步查詢等,可以參看示例:

// 限制查詢結果
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

// 返回流式查詢結果
Stream<User> readAllByFirstnameNotNull();

// 異步查詢
@Async
Future<User> findByFirstname(String firstname);  

@Query查詢

  在上述示例中添加新的方法:

UserController:

@ResponseBody
@GetMapping("/queryUser2/{id}")
public User queryUserById(@PathVariable Long id){
    // 通過 @Query 注解查詢
    return userRepository.withIdQuery(id);
}

UserRepository:

// @Query("select u from User u where u.id = :id ")
@Query(value = "select u from User u where u.id = ?1 ",nativeQuery = true)
User withIdQuery(@Param("id") Long id);

  使用這種命名查詢來聲明實體查詢是一種有效的方法,適用於少量查詢。上面的 @Query 注解需要注意的是:

  1、@Query(value = "select u from User u where u.id = ?1 ",nativeQuery = true) 中的 nativeQuery 屬性如果設置為 true 時,表示的意思是:可以執行原生sql語句,所謂原生sql,也就是說這段sql拷貝到數據庫中,然后把參數值給一下就能運行了,原生的 sql 都是真實的字段,真實的表名。如果設置為 false 時,就不是原生的 sql ,而不是數據庫對應的真正的表名,而是對應的實體名,並且 sql 中的字段名也不是數據庫中真正的字段名,而是實體的字段名;

  2、nativeQuery 如果不設置時,即 @Query("select u from User u where u.id = ?1 ") ,默認是 false;

  3、 @Query("select u from User u where u.id = ?1 ") 可以寫成 @Query("select u from User u where u.id = :id ")

  4、如涉及到刪除和修改在需要加上@Modifying.也可以根據需要添加 @Transactional對事物的支持,查詢超時的設置等。

@NamedQuery查詢

  在以上的示例中,為了區分 User 實體類,我們重新創建一個實體類 UserVo:

package com.yanfei1819.jpademo.entity;
import javax.persistence.*;

/**
 * Created by 追夢1819 on 2019-05-23.
 */
@Entity
@Table(name = "user")
@NamedQuery(name = "findAll",query = "select u from User u")
public class UserVo {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO)
    private Long id;
    @Column(name = "age")
    private int age;
    @Column(name = "name")
    private String name;
	// get/set 省略
}

在 UserController 中加一個方法:

@Autowired
private EntityManager entityManager;

@ResponseBody
@GetMapping("/queryUserByNameQuery")
public List<UserVo> queryUserByNameQuery(){
    // 通過 @Query 注解查詢
    return entityManager.createNamedQuery("findAll").getResultList();
}

  啟動程序,訪問 http://localhost:8082/queryUserByNameQuery ,能夠查詢到所有用戶:

  再看看控制台打印結果:

  注意一點,如果是多個查詢,可以使用 @NamedQuerys

  筆者其實並不推薦使用這種自定義方法,因為它對實體類的污染很嚴重。不利於可讀性。


總結

  JPA 可以單獨使用,只不過 SpringBoot 這個大平台將該技術引入進來。

  JPA也是一個大的分支,細枝末節有很多,作者認為,對於 JPA 的學習,只要關注三個方面:

  1) ORM 映射元數據,支持 XML 和 JDK 注解兩種元數據的形式;

  2) 核心的 API;

  3) 查詢語言:JPQL。

  當然,如果感興趣的,可以好好研究一下 JPA 的官方文檔,深入研讀一下其源碼。此處由於篇幅所限,就不一一闡述。后續有時間會開設一個 JPA 專欄,專門講述這一大分支,歡迎大家持續關注。

  話分兩頭,另一方面,正如前面幾章說的,各個 ORM 技術框架各有優缺點,沒有一種框架是絕對完美的。技術、框架的選型,沒必要追求最新,追求最適合即可。
  源碼:我的GitHub


參考

  1. spring doc

  2. Spring Data 官網介紹

  3. Spring Data JPA 官網


![](https://img2018.cnblogs.com/blog/1183871/201905/1183871-20190523093647589-109913758.png)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM