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

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

上一篇文章介紹瞭如何快速的搭建一個JPA的項目環境,並給出了一個簡單的演示demo,接下來咱們開始業務教程,也就是咱們常說的CURD,接下來進入第一篇,如何添加數據java

經過本篇文章,你能夠get到如下技能點mysql

  • POJO對象如何與表關聯
  • 如何向DB中添加單條記錄
  • 如何批量向DB中添加記錄
  • save 與 saveAndFlush的區別

I. 環境準備

實際開始以前,須要先走一些必要的操做,如安裝測試使用mysql,建立SpringBoot項目工程,設置好配置信息等,關於搭建項目的詳情能夠參考前一篇文章 190612-SpringBoot系列教程JPA之基礎環境搭建git

下面簡單的看一下演示添加記錄的過程當中,須要的配置github

1. 表準備

沿用前一篇的表,結構以下spring

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. 項目配置

配置信息,與以前有一點點區別,咱們新增了更詳細的日誌打印;本篇主要目標集中在添加記錄的使用姿式,對於配置說明,後面單獨進行說明sql

## 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功能api

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可能就有預感,上面的執行多半要跪(😏😏😏),下面是執行截圖,也是明確告訴了咱們這個不能爲null

0

那麼有辦法解決麼?難道就這麼向現實放棄,向大佬妥協麼?

默認值嘛,一個很容易想到的方法,我直接在PO對象中給一個默認值,是否是也能夠,而後咱們的PO改造爲

@Data
@Entity
@Table(name = "money")
public class MoneyPO {
    // ... 省略其餘
    
    @Column(name = "is_deleted")
    private Byte isDeleted = (byte) 0x00;
}

測試代碼註釋一行,變成下面這個

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());
    }
}

再次執行看下結果如何,順利走下去,沒有報錯,喜大普奔

1

這樣我就知足了嗎?要是手抖上面測試註釋掉的那一行忘了註釋,豈不是依然會跪?並且我但願是表中的默認值,直接在代碼中硬編碼會不會不太優雅?這個主動設置的默認值,在後面查詢的時候會不會有坑?

  • 做爲一個有追求的新青年,固然對上面的答案say no了

咱們的解決方法也簡單,在PO類上,加一個註解 @DynamicInsert,表示在最終建立sql的時候,爲null的項就不要了哈

而後咱們的新的PO,在原始版本上變成以下(注意幹掉上一次的默認值)

@Data
@DynamicInsert
@Entity
@Table(name = "money")
public class MoneyPO {
  // ... 省略
}

再來一波實際的測試,完美了,沒有拋異常,插入成功,並且控制檯中輸出的sql日誌也驗證了咱們上面說的@DynamicInsert註解的做用(日誌輸出hibernate的sql,能夠經過配置application.properties文件,添加參數spring.jpa.show-sql=true

2

c. 類型關聯

針對上面的PO對象,有幾個地方感受不爽,isDelete我想要boolean,true表示刪除false表示沒刪除,搞一個byte用起來太不方便了,這個要怎麼搞?

這個並不怎麼複雜,由於直接將byte類型改爲boolean就能夠了,若是db中時0對應的false;1對應的true,下面是驗證結果,並無啥問題

3

在JPA規範中,並非全部的類型的屬性均可以持久化的,下表列舉了可映射爲持久化的屬性類型:

分類 類型
基本類型 byte、int、short、long、boolean、char、float、double
基本類型封裝類 Byte、Integer、Short、Long、Boolean、Character、Float、Double
字節和字符數組 byte[]、Byte[]、char[]、Character[]
大數值類型 BigInteger、BigDecimal
字符串類型 String
時間日期類 java.util.Date、java.util.Calendar、java.sql.Date、java.sql.Time、java.sql.Timestamp
集合類 java.util.Collection、java.util.List、java.util.Set、java.util.Map
枚舉類型
嵌入式

關於類型關聯,在查詢這一篇會更詳細的進行展開說明,好比有個特別有意思的點

如db中is_delete爲1,須要映射到PO中的false,0映射到true,和咱們上面默認的是個反的,要怎麼搞?

d. 插入時指定ID

再插入的時候,咱們上面的case都是沒有指定id的,可是若是你指定了id,會發生什麼事情?

咱們將po恢復到以前的狀態,測試代碼以下

private void addWithId() {
    // 單個添加
    MoneyPO moneyPO = new MoneyPO();
    moneyPO.setId(20);
    moneyPO.setName("jpa 一灰灰 ex");
    moneyPO.setMoney(2200L + ((long) (Math.random() * 100)));
    moneyPO.setIsDeleted((byte) 0x00);
    MoneyPO res = moneyCreateRepository.save(moneyPO);
    System.out.println("after insert res: " + res);
}

看下輸出結果,驚訝的發現,這個指定id並無什麼卵用,最終db中插入的記錄依然是自增的方式來的

爲何會這樣子呢,咱們看下sql是怎樣的

直接把id給丟了,也就是說咱們設置的id不生效,咱們知道@GeneratedValue 這個註解指定了id的增加方式,若是咱們去掉這個註解會怎樣

從輸出結果來看:

  • 若是這個id對應的記錄不存在,則新增
  • 若是這個id對應的記錄存在,則更新

否則這個註解能夠主動指定id方式進行插入or修改,那麼若是沒有這個註解,插入時也不指定id,會怎樣呢?

很遺憾的是直接拋異常了,沒有這個註解,就必須手動賦值id了

4. 小結

本文主要介紹了下如何使用JPA來實現插入數據,單個or批量插入,也拋出了一些問題,有的給出了回答,有的等待後文繼續跟進,下面簡單小結一下主要的知識點

  • POJO與表關聯方式
    • 注意幾個註解的使用
    • @Entity, @Table 用於指定這個POJO對應哪張表
    • @Column 用於POJO的成員變量與表中的列進行關聯
    • @Id @GeneratedValue來指定主鍵
    • POJO成員變量類型與DB表中列的關係
  • db插入的幾種姿式
    • save 單個插入
    • saveAll 批量插入
    • 插入時,如要求DO中成員爲null時,用mysql默認值,可使用註解 @DynamicInsert,實現最終拼接部分sql方式插入
    • 指定id查詢時的幾種case

此外本文還留了幾個坑沒有填

  • POJO成員類型與表列類型更靈活的轉換怎麼玩?
  • save 與 saveAndFlush 之間的區別(從命名上,前者保存,可能只保存內存,不必定落庫;後者保存並落庫,可是沒有找到驗證他們區別的實例代碼,因此先不予評價)
  • 註解的更詳細使用說明

II. 其餘

-1. 相關博文

0. 項目

1. 一灰灰Blog

盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛

一灰灰blog

相關文章
相關標籤/搜索