SpringBoot 中 JPA 的使用

前言

第一次使用 Spring JPA 的時候,感受這東西簡直就是神器,幾乎不須要寫什麼關於數據庫訪問的代碼一個基本的 CURD 的功能就出來了。下面咱們就用一個例子來說述如下 JPA 使用的基本操做。java

新建項目,增長依賴

在 Intellij IDEA 裏面新建一個空的 SpringBoot 項目。具體步驟參考 SpringBoot 的第一次邂逅。根據本樣例的需求,咱們要添加下面三個依賴mysql

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>
複製代碼

準備數據庫環境

爲這個項目,咱們專門新建一個 springboot_jpa 的數據庫,而且給 springboot 用戶受權git

create database springboot_jpa;

grant all privileges on springboot_jpa.* to 'springboot'@'%' identified by 'springboot';

flush privileges;
複製代碼

項目配置

#通用數據源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.110.2.56:3306/springboot_jpa?charset=utf8mb4&useSSL=false
spring.datasource.username=springboot
spring.datasource.password=springboot
# Hikari 數據源專用配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
# JPA 相關配置
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create
複製代碼

這李前面的數據源配置和前文《SpringBoot 中使用 JDBC Templet》中的同樣。後面的幾個配置須要解釋一下github

  1. spring.jpa.show-sql=true 配置在日誌中打印出執行的 SQL 語句信息。
  2. spring.jpa.hibernate.ddl-auto=create 配置指明在程序啓動的時候要刪除而且建立實體類對應的表。這個參數很危險,由於他會把對應的表刪除掉而後重建。因此千萬不要在生成環境中使用。只有在測試環境中,一開始初始化數據庫結構的時候才能使用一次。
  3. spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect 。在 SrpingBoot 2.0 版本中,Hibernate 建立數據表的時候,默認的數據庫存儲引擎選擇的是 MyISAM (以前好像是 InnoDB,這點比較詭異)。這個參數是在建表的時候,將默認的存儲引擎切換爲 InnoDB 用的。

創建第一個數據實體類

數據庫實體類是一個 POJO Bean 對象。這裏咱們先創建一個 UserDO 的數據庫實體。數據庫實體的源碼以下spring

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 用戶實體類
 *
 * @author 楊高超
 * @since 2018-03-12
 */
@Entity
@Table(name = "AUTH_USER")
public class UserDO {
    @Id
    private Long id;
    @Column(length = 32)
    private String name;
    @Column(length = 32)
    private String account;
    @Column(length = 64)
    private String pwd;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
複製代碼

其中:sql

  1. @Entity 是一個必選的註解,聲明這個類對應了一個數據庫表。
  2. @Table(name = "AUTH_USER") 是一個可選的註解。聲明瞭數據庫實體對應的表信息。包括表名稱、索引信息等。這裏聲明這個實體類對應的表名是 AUTH_USER。若是沒有指定,則表名和實體的名稱保持一致。
  3. @Id 註解聲明瞭實體惟一標識對應的屬性。
  4. @Column(length = 32) 用來聲明實體屬性的表字段的定義。默認的實體每一個屬性都對應了表的一個字段。字段的名稱默認和屬性名稱保持一致(並不必定相等)。字段的類型根據實體屬性類型自動推斷。這裏主要是聲明瞭字符字段的長度。若是不這麼聲明,則系統會採用 255 做爲該字段的長度

以上配置所有正確,則這個時候運行這個項目,咱們就能夠看到日誌中以下的內容:數據庫

Hibernate: drop table if exists auth_user
Hibernate: create table auth_user (id bigint not null, account varchar(32), name varchar(32), pwd varchar(64), primary key (id)) engine=InnoDB
複製代碼

系統自動將數據表給咱們建好了。在數據庫中查看錶及表結構 springboot

springboot_jpa 表及表結構

以上過程和咱們前使用 Hibernate 的過程基本相似,不管是數據庫實體的聲明仍是表的自動建立。下面咱們才正式進入 Spring Data JPA 的世界,來看一看他有什麼驚豔的表現bash

實現一個持久層服務

在 Spring Data JPA 的世界裏,實現一個持久層的服務是一個很是簡單的事情。以上面的 UserDO 實體對象爲例,咱們要實現一個增長、刪除、修改、查詢功能的持久層服務,那麼我只須要聲明一個接口,這個接口繼承 org.springframework.data.repository.Repository<T, ID> 接口或者他的子接口就行。這裏爲了功能的完備,咱們繼承了 org.springframework.data.jpa.repository.JpaRepository<T, ID> 接口。其中 T 是數據庫實體類,ID 是數據庫實體類的主鍵。 而後再簡單的在這個接口上增長一個 @Repository 註解就結束了。dom

package com.yanggaochao.springboot.learn.springbootjpalearn.security.dao;


import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 用戶服務數據接口類
 *
 * @author 楊高超
 * @since 2018-03-12
 */

@Repository
package com.yanggaochao.springboot.learn.springbootjpalearn.security.dao;


import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 用戶服務數據接口類
 *
 * @author 楊高超
 * @since 2018-03-12
 */

@Repository
public interface UserDao extends JpaRepository<UserDO, Long> {
}
複製代碼

一行代碼也不用寫。那麼針對 UserDO 這個實體類,咱們已經擁有下下面的功能

UserDao 保存實體功能

UserDao 保存實體刪除功能

UserDao 查詢實體刪除功能

例如,咱們用下面的代碼就將一些用戶實體保存到數據庫中了。

package com.yanggaochao.springboot.learn.springbootjpalearn;

import com.yanggaochao.springboot.learn.springbootjpalearn.security.dao.UserDao;
import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDOTest {

    @Autowired
    private UserDao userDao;

    @Before
    public void before() {
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setName("風清揚");
        userDO.setAccount("fengqy");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO = new UserDO();
        userDO.setId(3L);
        userDO.setName("東方不敗");
        userDO.setAccount("bubai");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO.setId(5L);
        userDO.setName("向問天");
        userDO.setAccount("wentian");
        userDO.setPwd("123456");
        userDao.save(userDO);
    }
    @Test
    public void testAdd() {
        UserDO userDO = new UserDO();
        userDO.setId(2L);
        userDO.setName("任我行");
        userDO.setAccount("renwox");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO = new UserDO();
        userDO.setId(4L);
        userDO.setName("令狐沖");
        userDO.setAccount("linghuc");
        userDO.setPwd("123456");
        userDao.save(userDO);
    }

    @After
    public void after() {
        userDao.deleteById(1L);
        userDao.deleteById(3L);
        userDao.deleteById(5L);
    }

}
複製代碼

這個是採用 Junit 來執行測試用例。@Before 註解在測試用例以前執行準備的代碼。這裏先插入三個用戶信息。 執行執行這個測試,完成後,查看數據庫就能夠看到數據庫中有了 5 條記錄:

數據庫記錄

咱們還能夠經過測試用例驗證經過標識查找對象功能,查詢全部數據功能的正確性,查詢功能甚至能夠進行排序和分頁

@Test
    public void testLocate() {
        Optional<UserDO> userDOOptional = userDao.findById(1L);
        if (userDOOptional.isPresent()) {
            UserDO userDO = userDOOptional.get();
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }

    @Test
    public void testFindAll() {
        List<UserDO> userDOList = userDao.findAll(new Sort(Sort.Direction.DESC,"account"));
        for (UserDO userDO : userDOList) {
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }
複製代碼

能夠看到,咱們所作的所有事情僅僅是在 SpingBoot 工程裏面增長數據庫配置信息,聲明一個 UserDO 的數據庫實體對象,而後聲明瞭一個持久層的接口,改接口繼承自 org.springframework.data.jpa.repository.JpaRepository<T, ID> 接口。而後,系統就自動擁有了豐富的增長、刪除、修改、查詢功能。查詢功能甚至還擁有了排序和分頁的功能。

這就是 JPA 的強大之處。除了這些接口外,用戶還會有其餘的一些需求, JPA 也同樣能夠知足你的需求。

擴展查詢

從上面的截圖 「UserDao 查詢實體刪除功能」 中,咱們能夠看到,查詢功能是不盡人意的,不少咱們想要的查詢功能尚未。不過放心。JPA 有很是方便和優雅的方式來解決

根據屬性來查詢

若是想要根據實體的某個屬性來進行查詢咱們能夠在 UserDao 接口中進行接口聲明。例如,若是咱們想根據實體的 account 這個屬性來進行查詢(在登陸功能的時候可能會用到)。咱們在 com.yanggaochao.springboot.learn.springbootjpalearn.security.dao.UserDao 中增長一個接口聲明就能夠了

UserDO findByAccount(String account);
複製代碼

而後增長一個測試用例

@Test
    public void testFindByAccount() {
        UserDO userDO = userDao.findByAccount("wentian");
        if (userDO != null) {
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }
複製代碼

運行以後,會在日誌中打印出

name = 向問天
account = wentian
複製代碼

這種方式很是強大,不經可以支持單個屬性,還能支持多個屬性組合。例如若是咱們想查找帳號和密碼同時知足查詢條件的接口。那麼咱們在 UserDao 接口中聲明

UserDO findByAccountAndPwd(String account, String pwd);
複製代碼

再例如,咱們要查詢 id 大於某個條件的用戶列表,則能夠聲明以下的接口

List<UserDO> findAllByIdGreaterThan(Long id);
複製代碼

這個語句結構能夠用下面的表來講明

JPA 關鍵字說明

自定義查詢

若是上述的狀況還沒法知足須要。那麼咱們就能夠經過經過 import org.springframework.data.jpa.repository.Query 註解來解決這個問題。例如咱們想查詢名稱等於某兩個名字的全部用戶列表,則聲明以下的接口便可

@Query("SELECT O FROM UserDO O WHERE O.name = :name1 OR O.name = :name2 ")
List<UserDO> findTwoName(@Param("name1") String name1, @Param("name2") String name2);
複製代碼

這裏是用 PQL 的語法來定義一個查詢。其中兩個參數名字有語句中的 : 後面的支付來決定

若是你習慣編寫 SQL 語句來完成查詢,還能夠在用下面的方式實現

@Query(nativeQuery = true, value = "SELECT * FROM AUTH_USER WHERE name = :name1 OR name = :name2 ")
List<UserDO> findSQL(@Param("name1") String name1, @Param("name2") String name2);
複製代碼

這裏在 @Query 註解中增長一個 nativeQuery = true 的屬性,就能夠採用原生 SQL 語句的方式來編寫查詢。

聯合主鍵

從 org.springframework.data.jpa.repository.JpaRepository<T, ID> 接口定義來看,數據實體的主鍵是一個單獨的對象,那麼若是一個數據庫的表的主鍵是兩個或者兩個以上字段聯合組成的怎麼解決呢。

咱們擴充一下前面的場景。假如咱們有一個角色 Role 對象,有兩個屬性 一個 id ,一個 name ,對應了 auth_role 數據表,同時有一個角色用戶關係對象 RoleUser,說明角色和用戶對應關係,有兩個屬性 roleId,userId 對應 auth_role_user 表。那麼咱們須要聲明一個 RoleDO 對象以下

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 角色實體類
 *
 * @author 楊高超
 * @since 2018-03-12
 */
@Entity
@Table(name = "AUTH_ROLE")
public class RoleDO {
    @Id
    private Long id;
    @Column(length = 32)
    private String name;
    @Column(length = 64)
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}
複製代碼

對於有多個屬性做爲聯合主鍵的狀況,咱們通常要新建一個單獨的主鍵類,他的屬性和數據庫實體主鍵的字段同樣,要實現 java.io.Serializable 接口,類聲明以下

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import java.io.Serializable;

/**
 * 聯合主鍵對象
 *
 * @author 楊高超
 * @since 2018-03-12
 */
public class RoleUserId implements Serializable {
    private Long roleId;
    private Long userId;
}

複製代碼

一樣的,咱們聲明一個 RoleUserDO 對象以下

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * 角色用戶關係實體類
 *
 * @author 楊高超
 * @since 2018-03-12
 */
@Entity
@IdClass(RoleUserId.class)
@Table(name = "AUTH_ROLE_USER")
public class RoleUserDO  {
    @Id
    private Long roleId;
    @Id
    private Long userId;

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

複製代碼

這裏由於數據實體類和數據實體主鍵類的屬性同樣,因此咱們能夠刪除掉這個數據實體主鍵類,而後將數據實體類的主鍵類聲明爲本身便可。固然,本身也要實現 java.io.Serializable 接口。

這樣,咱們若是要查詢某個角色下的全部用戶列表,就能夠聲明以下的接口

@Query("SELECT U FROM UserDO U ,RoleUserDO RU WHERE U.id = RU.userId AND RU.roleId = :roleId")
List<UserDO> findUsersByRole(@Param("roleId") Long roleId);
複製代碼

固然了,這種狀況下,咱們會看到系統自動創建了 AUTH_ROLE 和 AUTH_ROLE_USER 表。他們的表結構以下所示

auth_role 和 auth_role_user 表結構

注意這裏 auth_role_user 表中,屬性名 userId 轉換爲了 user_id, roleId 轉換爲了 role_id.

若是咱們要用 SQL 語句的方式實現上面的功能,那麼咱們就把這個接口聲明修改成下面的形式。

@Query("SELECT U.* FROM AUTH_USER U ,AUTH_ROLE_USER RU WHERE U.id = RU.user_id AND RU.role_id = :roleId")
List<UserDO> findUsersByRole(@Param("roleId") Long roleId);
複製代碼

後記

這個樣例基本上講述了 JPA 使用過程當中的一些細節。咱們能夠看出。使用 JPA 來完成關於關係數據庫增刪改查的功能是很是的方便快捷的。全部代碼已經上傳到 github 的倉庫 springboot-jpa-learn 上了

原文發表在 簡書

相關文章
相關標籤/搜索