1. 程式人生 > >SpringBoot系列教程JPA之新增記錄使用姿勢

SpringBoot系列教程JPA之新增記錄使用姿勢

SpringBoot系列教程JPA之新增記錄使用姿勢

上一篇文章介紹瞭如何快速的搭建一個JPA的專案環境,並給出了一個簡單的演示demo,接下來我們開始業務教程,也就是我們常說的CURD,接下來進入第一篇,如何新增資料

通過本篇文章,你可以get到以下技能點

  • POJO物件如何與表關聯
  • 如何向DB中新增單條記錄
  • 如何批量向DB中新增記錄
  • save 與 saveAndFlush的區別

<!-- more -->

I. 環境準備

實際開始之前,需要先走一些必要的操作,如安裝測試使用mysql,建立SpringBoot專案工程,設定好配置資訊等,關於搭建專案的詳情可以參考前一篇文章

190612-SpringBoot系列教程JPA之基礎環境搭建

下面簡單的看一下演示新增記錄的過程中,需要的配置

1. 表準備

沿用前一篇的表,結構如下

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT '' COMMENT '使用者名稱',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT '錢',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2. 專案配置

配置資訊,與之前有一點點區別,我們新增了更詳細的日誌列印;本篇主要目標集中在新增記錄的使用姿勢,對於配置說明,後面單獨進行說明

## DataSource
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=
## jpa相關配置
spring.jpa.database=MYSQL
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
spring.jpa.hibernate.naming.physical-strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl

II. Insert使用教程

在開始之前,先宣告一下,因為個人實際專案中並沒有使用到JPA,對JPA的原則和hibernate的一些特性瞭解的也不多,目前處於學習探索階段,主要是介紹下使用姿勢,下面的東西都是經過測試得出,有些地方描述可能與規範不太一樣,或者有些差錯,請發現的大佬指正

接下來我們進入正題,如何通過JPA實現我們常見的Insert功能

1. POJO與表關聯

首先第一步就是將POJO物件與表關聯起來,這樣就可以直接通過java的操作方式來實現資料庫的操作了;

我們直接建立一個MoneyPo物件,包含上面表中的幾個欄位

@Data
public class MoneyPO {
    private Integer id;

    private String name;

    private Long money;

    private Byte isDeleted;

    private Timestamp createAt;

    private Timestamp updateAt;
}

自然而然地,我們就有幾個問題了

  • 這個POJO怎麼告訴框架它是和表Money繫結的呢?
  • Java中變數命令推薦駝峰結構,那麼 isDeleted 又如何與表中的 is_deleted 關聯呢?
  • POJO中成員變數的型別如何與表中的保持一致呢,如果不一致會怎樣呢?

針對上面的問題,一個一個來說明

對hibernate熟悉的同學,可能知道我可以通過xml配置的方式,來關聯POJO與資料庫表(當然mybatis也是這麼玩的),友情連結一下hibernate的官方說明教程;我們使用SpringBoot,當然是選擇註解的方式了,下面是通過註解的方式改造之後的DO物件

package com.git.hui.boot.jpa.entity;

import lombok.Data;
import org.springframework.data.annotation.CreatedDate;

import javax.persistence.*;
import java.sql.Timestamp;

/**
 * Created by @author yihui in 21:01 19/6/10.
 */
@Data
@Entity(name="money")
public class MoneyPO {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "name")
    private String name;

    @Column(name = "money")
    private Long money;

    @Column(name = "is_deleted")
    private Byte isDeleted;

    @Column(name = "create_at")
    @CreatedDate
    private Timestamp createAt;

    @Column(name = "update_at")
    @CreatedDate
    private Timestamp updateAt;
}

有幾個有意思的地方,需要我們注意

a. entity註解

@Entity 這個註解比較重要,用於宣告這個POJO是一個與資料庫中叫做 money 的表關聯的物件;

  • @Entity註解有一個引數name,用於指定表名,如果不主動指定時,預設用類名,即上面如果不指定那麼,那麼預設與表 moneypo 繫結

另外一個常見的方式是在類上添加註解 @Table,然後指定表名,也是可以的

@Data
@Entity
@Table(name = "money")
public class MoneyPO {
}

b. 主鍵指定

我們可以看到id上面有三個註解,我們先看下前面兩個

  • @Id 顧名思義,用來表明這傢伙是主鍵,比較重要,需要特殊關照
  • @GeneratedValue 設定初始值,談到主鍵,我們一般會和”自增“這個一起說,所以你經常會看到的取值為 strategy = GenerationType.IDENTITY (由資料庫自動生成)

這個註解主要提供了四種方式,分別說明如下

取值說明
GenerationType.TABLE使用一個特定的資料庫表格來儲存主鍵
GenerationType.SEQUENCE根據底層資料庫的序列來生成主鍵,條件是資料庫支援序列
GenerationType.IDENTITY主鍵由資料庫自動生成(主要是自動增長型)
GenerationType.AUTO主鍵由程式控制

關於這幾種使用姿勢,這裡不詳細展開了,有興趣的可以可以看一下這博文: @GeneratedValue

c. Column註解

這個註解就是用來解決我們pojo成員名和資料庫列名不一致的問題的,這個註解內部的屬性也不少,相對容易理解,後面會單開一章來記錄這些常用註解的說明查閱

d. CreateDate註解

這個註解和前面不一樣的是它並非來自jpa-api包,而是spring-data-common包中提供的,表示會根據當前時間建立一個時間戳物件

e. 其他

到這裡這個POJO已經建立完畢,後續的表中新增記錄也可以直接使用它了,但是還有幾個問題是沒有明確答案的,先提出來,期待後文可以給出回答

  1. POJO屬性的型別與表中型別
  2. mysql表中列可以有預設值,這個在POJO中怎麼體現
  3. 一個表包含另一個表的主鍵時(主鍵關聯,外來鍵)等特殊的情況,POJO中有體現麼?

2. Repository API宣告

jpa非常有意思的一點就是你只需要建立一個介面就可以實現db操作,就這麼神奇,可惜本文裡面見不到太多神奇的用法,這塊放在查詢篇來見證奇蹟

我們定義的API需要繼承自org.springframework.data.repository.CrudRepository,如下

package com.git.hui.boot.jpa.repository;

import com.git.hui.boot.jpa.entity.MoneyPO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;

/**
 * 新增資料
 * Created by @author yihui in 11:00 19/6/12.
 */
public interface MoneyCreateRepository extends CrudRepository<MoneyPO, Integer> {
}

好的,到這裡就可以直接新增資料了 (感覺什麼都沒幹,你居然告訴我可以插入資料???)

3. 使用姿勢

a. 基礎使用case

常規的使用姿勢,無非單個插入和批量插入,我們先來看一下常規操作

@Component
public class JpaInsertDemo {
    @Autowired
    private MoneyCreateRepository moneyCreateRepository;
    public void testInsert() {
        addOne();
        addMutl();
    }

    private void addOne() {
        // 單個新增
        MoneyPO moneyPO = new MoneyPO();
        moneyPO.setName("jpa 一灰灰");
        moneyPO.setMoney(1000L);
        moneyPO.setIsDeleted((byte) 0x00);
        Timestamp now = new Timestamp(System.currentTimeMillis());
        moneyPO.setCreateAt(now);
        moneyPO.setUpdateAt(now);

        MoneyPO res = moneyCreateRepository.save(moneyPO);
        System.out.println("after insert res: " + res);
    }

    private void addMutl() {
        // 批量新增
        MoneyPO moneyPO = new MoneyPO();
        moneyPO.setName("batch jpa 一灰灰");
        moneyPO.setMoney(1000L);
        moneyPO.setIsDeleted((byte) 0x00);
        Timestamp now = new Timestamp(System.currentTimeMillis());
        moneyPO.setCreateAt(now);
        moneyPO.setUpdateAt(now);

        MoneyPO moneyPO2 = new MoneyPO();
        moneyPO2.setName("batch jpa 一灰灰");
        moneyPO2.setMoney(1000L);
        moneyPO2.setIsDeleted((byte) 0x00);
        moneyPO2.setCreateAt(now);
        moneyPO2.setUpdateAt(now);

        Iterable<MoneyPO> res = moneyCreateRepository.saveAll(Arrays.asList(moneyPO, moneyPO2));
        System.out.println("after batchAdd res: " + res);
    }
}

看下上面的兩個插入方式,就這麼簡單,

  • 通過IoC/DI注入 repository
  • 建立PO物件,然後呼叫save, saveAll方法就ok了

上面是一般的使用姿勢,那麼非一般使用姿勢呢?

b. 插入時預設值支援方式

在建立表的時候,我們知道欄位都有預設值,那麼如果PO物件中某個成員我不傳,可以插入成功麼?會是預設的DB值麼?

private void addWithNull() {
    // 單個新增
    try {
        MoneyPO moneyPO = new MoneyPO();
        moneyPO.setName("jpa 一灰灰 ex");
        moneyPO.setMoney(2000L);
        moneyPO.setIsDeleted(null);
        MoneyPO res = moneyCreateRepository.save(moneyPO);
        System.out.println("after insert res: " + res);
    } catch (Exception e) {
        System.out.println("addWithNull field: " + e.getMessage());
    }
}

當看到上面的try/catch可能就有預感,上面的執行多半要跪(