原文連接: 191119-SpringBoot系列教程JPA之指定id保存java
前幾天有位小夥伴問了一個頗有意思的問題,使用 JPA 保存數據時,即使我指定了主鍵 id,可是新插入的數據主鍵倒是 mysql 自增的 id;那麼是什麼緣由致使的呢?又能夠如何解決呢?mysql
本文將介紹一下如何使用 JPA 的 AUTO 保存策略來指定數據庫主鍵 idgit
<!-- more -->github
實際開始以前,須要先走一些必要的操做,如安裝測試使用 mysql,建立 SpringBoot 項目工程,設置好配置信息等,關於搭建項目的詳情能夠參考前一篇文章 190612-SpringBoot 系列教程 JPA 之基礎環境搭建spring
下面簡單的看一下後續的代碼中,須要的配置 (咱們使用的是 mysql 數據庫)sql
沿用前一篇的表,結構以下數據庫
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;
配置信息,與以前有一點點區別,咱們新增了更詳細的日誌打印;本篇主要目標集中在添加記錄的使用姿式,對於配置說明,後面單獨進行說明dom
## 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
首先簡單的看一下,咱們通常使用默認的數據庫自增生成主鍵的使用方式,以便後面的自定義主鍵生成策略的對比ide
對於 jpa 的插入數據的知識點不太清楚的同窗,能夠看一下以前的博文: 190614-SpringBoot 系列教程 JPA 之新增記錄使用姿式spring-boot
首先咱們須要定義 PO,與數據庫中的表綁定起來
@Data @DynamicUpdate @DynamicInsert @Entity @Table(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; }
注意上面的主鍵生成策略用的是 GenerationType.IDENTITY
,配合 mysql 的使用就是利用數據庫的自增來生成主鍵 id
/** * 新增數據 * Created by @author yihui in 11:00 19/6/12. */ public interface MoneyCreateRepositoryV2 extends JpaRepository<MoneyPO, Integer> { }
接下來保存數據就很簡單了
private void addWithId() { MoneyPO po1 = new MoneyPO(); po1.setId(20); po1.setName("jpa 一灰灰 1x"); po1.setMoney(2200L + ((long) (Math.random() * 100))); po1.setIsDeleted((byte) 0x00); MoneyPO r1 = moneyCreateRepositoryV2.save(po1); System.out.println("after insert res: " + r1); }
強烈建議實際的體驗一下上面的代碼執行
首次執行確保數據庫中不存在 id 爲 20 的記錄,雖然咱們的 PO 對象中,指定了 id 爲 20,可是執行完畢以後,新增的數據 id 卻不是 20
Hibernate: select moneypo0_.id as id1_0_0_, moneypo0_.create_at as create_a2_0_0_, moneypo0_.is_deleted as is_delet3_0_0_, moneypo0_.money as money4_0_0_, moneypo0_.name as name5_0_0_, moneypo0_.update_at as update_a6_0_0_ from money moneypo0_ where moneypo0_.id=? Hibernate: insert into money (is_deleted, money, name) values (?, ?, ?) after insert res: MoneyPO(id=104, name=jpa 一灰灰 1x, money=2208, isDeleted=0, createAt=null, updateAt=null)
上面是執行的 sql 日誌,注意插入的 sql,是沒有指定 id 的,因此新增的記錄的 id 就會利用 mysql 的自增策略
當咱們的 db 中存在 id 爲 20 的記錄時,再次執行,查看日誌發現實際執行的是更新數據
Hibernate: select moneypo0_.id as id1_0_0_, moneypo0_.create_at as create_a2_0_0_, moneypo0_.is_deleted as is_delet3_0_0_, moneypo0_.money as money4_0_0_, moneypo0_.name as name5_0_0_, moneypo0_.update_at as update_a6_0_0_ from money moneypo0_ where moneypo0_.id=? Hibernate: update money set create_at=?, money=?, name=?, update_at=? where id=? after insert res: MoneyPO(id=20, name=jpa 一灰灰 1x, money=2234, isDeleted=0, createAt=null, updateAt=null)
大膽猜想,save 的執行過程邏輯如
那麼問題來了,若是我但願當個人 po 中指定了數據庫 id 時,db 中沒有這條記錄時,就插入 id 爲指定值的記錄;若是存在記錄,則更新
要實現上面這個功能,自定義主鍵 id,那麼咱們就須要修改一下主鍵的生成策略了,官方提供了四種
取值 | 說明 |
---|---|
GenerationType.TABLE |
使用一個特定的數據庫表格來保存主鍵 |
GenerationType.SEQUENCE |
根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列 |
GenerationType.IDENTITY |
主鍵由數據庫自動生成(主要是自動增加型) |
GenerationType.AUTO |
主鍵由程序控制 |
從上面四種生成策略說明中,很明顯咱們要使用的就是 AUTO 策略了,咱們新增一個 PO,並指定保存策略
@Data @DynamicUpdate @DynamicInsert @Entity @Table(name = "money") public class AutoMoneyPO { @Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "myid") @GenericGenerator(name = "myid", strategy = "com.git.hui.boot.jpa.generator.ManulInsertGenerator") @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; }
採用自定義的生成策略,須要注意,@GenericGenerator(name = "myid", strategy = "com.git.hui.boot.jpa.generator.ManulInsertGenerator")
這個須要有,不然執行會拋異常
這一行代碼的意思是,主鍵 id 是由ManulInsertGenerator
來生成
/** * 自定義的主鍵生成策略,若是填寫了主鍵id,若是數據庫中沒有這條記錄,則新增指定id的記錄;不然更新記錄 * * 若是不填寫主鍵id,則利用數據庫自己的自增策略指定id * * Created by @author yihui in 20:51 19/11/13. */ public class ManulInsertGenerator extends IdentityGenerator { @Override public Serializable generate(SharedSessionContractImplementor s, Object obj) throws HibernateException { Serializable id = s.getEntityPersister(null, obj).getClassMetadata().getIdentifier(obj, s); if (id != null && Integer.valueOf(id.toString()) > 0) { return id; } else { return super.generate(s, obj); } } }
具體的主鍵生成方式也比較簡單了,首先是判斷 PO 中有沒有主鍵,若是有則直接使用 PO 中的主鍵值;若是沒有,就利用IdentityGenerator
策略來生成主鍵(而這個主鍵生成策略,正好是GenerationType.IDENTITY
利用數據庫自增生成主鍵的策略)
接下來咱們再次測試插入
// 使用自定義的主鍵生成策略 AutoMoneyPO moneyPO = new AutoMoneyPO(); moneyPO.setId(20); moneyPO.setName("jpa 一灰灰 ex"); moneyPO.setMoney(2200L + ((long) (Math.random() * 100))); moneyPO.setIsDeleted((byte) 0x00); AutoMoneyPO res = moneyCreateRepositoryWithId.save(moneyPO); System.out.println("after insert res: " + res); moneyPO.setMoney(3200L + ((long) (Math.random() * 100))); res = moneyCreateRepositoryWithId.save(moneyPO); System.out.println("after insert res: " + res); moneyPO = new AutoMoneyPO(); moneyPO.setName("jpa 一灰灰 2ex"); moneyPO.setMoney(2200L + ((long) (Math.random() * 100))); moneyPO.setIsDeleted((byte) 0x00); res = moneyCreateRepositoryWithId.save(moneyPO); System.out.println("after insert res: " + res);
上面的代碼執行時,確保數據庫中沒有主鍵爲 20 的數據,輸出 sql 日誌以下
# 第一次插入 Hibernate: select automoneyp0_.id as id1_0_0_, automoneyp0_.create_at as create_a2_0_0_, automoneyp0_.is_deleted as is_delet3_0_0_, automoneyp0_.money as money4_0_0_, automoneyp0_.name as name5_0_0_, automoneyp0_.update_at as update_a6_0_0_ from money automoneyp0_ where automoneyp0_.id=? Hibernate: insert into money (is_deleted, money, name, id) values (?, ?, ?, ?) after insert res: AutoMoneyPO(id=20, name=jpa 一灰灰 ex, money=2238, isDeleted=0, createAt=null, updateAt=null) # 第二次指定id插入 Hibernate: select automoneyp0_.id as id1_0_0_, automoneyp0_.create_at as create_a2_0_0_, automoneyp0_.is_deleted as is_delet3_0_0_, automoneyp0_.money as money4_0_0_, automoneyp0_.name as name5_0_0_, automoneyp0_.update_at as update_a6_0_0_ from money automoneyp0_ where automoneyp0_.id=? Hibernate: update money set create_at=?, money=?, update_at=? where id=? after insert res: AutoMoneyPO(id=20, name=jpa 一灰灰 ex, money=3228, isDeleted=0, createAt=null, updateAt=null) # 第三次無id插入 Hibernate: insert into money (is_deleted, money, name) values (?, ?, ?) after insert res: AutoMoneyPO(id=107, name=jpa 一灰灰 2ex, money=2228, isDeleted=0, createAt=null, updateAt=null)
注意上面的日誌輸出
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛