Spring Data JPA的簡單入門

java06051515發表於2020-08-10

前言

spring data JPA是spring團隊打造的sping生態全家桶的一部分,本身核心使用的是hibernate核心原始碼,用來作為了解java持久層框架基本構成的樣本是再好不過的選擇。最近閒來無事,構建了一個demo工程,用來閱讀spring data JPA原始碼,這對於巨集觀瞭解持久層框架的基本工作、微觀分析spring data JPA的原理和優缺點、避免使用過程中採坑,將會有一定的幫助。

基本工程搭建

spring data JPA的使用需要依託於web框架,最簡單快速的方式就是使用https://start.spring.io/構建一個包含spring data JPA的spring boot專案,只需要在引導的對應SQL的選單中選中Spring Data Jpa和響應資料庫的Driver即可,目前無論是idae還是eclipse都支援採用這種方式。上述方法的實質還是在最終生成的spring boot專案的pom.xml中加入了相關的依賴,所以也可以直接生成由maven管理的spring boot專案,然後pom.xml中:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.8.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>jpademo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>jpademo</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <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>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/apt</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

其中,spring-boot-starter-web是spring boot框架依賴,spring-boot-starter-data-jpa是Spring Data Jpa依賴,我使用的Mysql資料庫,所以引入了mysql-connector-java。 querydsl-jpa和querydsl-apt是在JPA基礎上使用querydsl的依賴,可以不引入。最後lombok廣泛使用的懶癌工具包。
接下來當然是構建一個資料來源,本地需要安裝mysql,這部分我就不細說了。資料庫安裝完成後,建立一個test庫,建song表,建表語句:

CREATE TABLE `song` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` char(10) DEFAULT NULL,
  `year` int(11) DEFAULT NULL,
  `length` int(11) DEFAULT NULL,
  `type` varchar(64) DEFAULT NULL,
  PRIMARY KEY (`id`)
)
接著專案中application.properties中設定資料庫配置:
#driver配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#資料庫配置
spring.datasource.url=jdbc:mysql://localhost:3306/test?useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username=root
spring.datasource.password=123456
#jpa配置
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update

spring.jpa.hibernate.ddl-auto是最有意思的一個配置,一不小心可能造成刪庫的悲慘結果。

  • create:每次執行該程式,沒有表格會新建表格,表內有資料會清空。
  • create-drop:每次程式結束的時候會清空表。
  • update:每次執行程式,沒有表格會新建表格,表內有資料不會清空,只會更新
  • validate:執行程式會校驗資料與資料庫的欄位型別是否相同,不同會報錯

這裡的是否有表格是以應用中配置的Entity實體為依據的,這意味著應用能夠根據自身的Entity實體來影響表的建立和表的結構。千言萬語匯成一句話,千萬不要配置為create。
最後需要在應用中配置相應的實體Entity和實體的Repository。專案目錄整體檔案結構如下:
spring data JPA的簡單入門-JpaProjectStructure.png
其中:

  • JpademoApplication.java:spring boot應用入口
  • TestController.java: 測試用controller介面
  • Song.java: 對應song表的實體類Entity
  • SongRepository: 對應實體類Song的Repository介面

Song實體定義:

@Entity  //定義為實體類
@Table(name = "song")  //對映表配置
@DynamicInsert         //支援動態插入
@DynamicUpdate         //支援動態更新
//以下為lombok懶癌註解
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Song {
    @Id              //主鍵id
    @GeneratedValue  //主鍵自動生成
    private Long id;
    private String name;
    private Integer year;
    private Integer length;
    private String type;
}

Jpa實體Entity對應了持久層的表資料,Song實體的定義,就對應了本地mysql資料test庫中的song表。而對錶資料的增刪改查操作,可以通過Repository實現。

public interface SongRepository extends JpaRepository<Song,Integer>{
    Song findById(Long id);
    Song getById(Long id);
    Song queryByIdAndYear(Long id, Long year);
    void deleteById(Long id);
}

SongRepository繼承JpaRepository介面,預設實現了諸如save(S entity)、findAll()、count()、delete(T entity)之類的方法,同時也支援子類通過名字擴充自定義新的查詢或其他運算元據的方式,比如我在上面定義的find、get、query By id或者 IdAndYear,就是通過id或者year組合查詢。雖然這種通過名字來擴充新的運算元據介面的方法看起來比較蠢,但是具體到實現原理還是值得探討一番的。

基本使用

瞭解一個事物基本原理,首先需要全面的瞭解事物的外在功能。對於spirng data jpa,主要的資料操作方式有使用Repository和使用EntityManager這兩種形式。
@Slf4j
@RestController
public class TestController {
    @Resource
    private EntityManager entityManager;
    @Resource
    private SongRepository songRepository;
    @GetMapping("/")
    public List<Song> test() {
        return repositoryList();
    }
    public List<Song> repositoryList() {
        //Repository方式
        List<Song> songs = songRepository.findAll();
        return songs;
    }
    @Transactional
    public void repositorySave() {
        Song song = new Song();
        song.setName("MyHeart");
        song.setLength(230);
        song.setYear(2020);
        songRepository.save(song);
    }
    @Transactional
    public void entityManagerMerge() {
        //EntityManager方式
        Song song = new Song();
        song = new Song();
        song.setName("Journey");
        song.setLength(230);
        song.setYear(2020);
        entityManager.merge(song);
    }
    public List<Song> hqlList() {
        //hql方式
        Query query = entityManager.createQuery("select s as ss from Song s");
        List<Song> songs = query.getResultList();
        return  songs;
    }
    public List<Song> sqlList() {
        //sql方式
        Query nativeQuery = entityManager.createNativeQuery("select * from song s where name LIKE 'MyHeart'");
        List<Song> songs = nativeQuery.getResultList();
        return songs;
    }
}

Repository提供標準Crud介面,也支援按照規範的命名標準自定義介面,這些在上文已經介紹過了。EntityManager方式其實是Repository的底層實現,這種方式提供了建立CriteriaQuery(上例沒有展示)、hql 的Query、SQL的Query,通過Query完成最終和資料庫的互動。從上例可以看出hql形式和sql比較類似,只是hql在“select s as ss from Song s”語句中,使用了物件導向的封裝,這裡Song對應的是實體Song,而不表名。原生NativeQuery對應的就是原生Sql查詢。
除此之外,Repository也提供非規範命名的介面查詢,本質也是使用EntityManager的hql和sql方式。在Repository的介面方法上可以使用 @Query註解,並定義相應的hql或者sql的語句,查詢條件需要和介面方法的入參一致,查詢結果對應介面方法的返回引數。
實際上,spring data jpa提供了一個sql演化的基本思路,即從sql到hql再到結合實體使用的Repository。但是實體很難解決關係型資料庫表資料的join問題,在《 java資料庫持久層框架基礎:為什麼不是JPA?》這篇文章中,我提到了可以使用querydsl解決聯表問題,實際上spring data jpa也有自己的方案,即在單個實體中定義對映集合。簡單解釋來說,假設一個表對應實體類Person,Person包含一個集合屬性Head,Head對應的是另一個表中的資料。Person實體中使用

 @JoinTable(name = "head", joinColumns = { @JoinColumn(nullable = false, name = "item_id", referencedColumnName = "id") })

實現聯表。但這種方式存在複雜的級聯關係,在更新、插入和刪除操作中極其難以處理。

作者:孫新【滴滴出行軟體開發工程師】

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559758/viewspace-2710429/,如需轉載,請註明出處,否則將追究法律責任。

相關文章