上一篇文章介紹瞭如何快速的搭建一個JPA的項目環境,並給出了一個簡單的演示demo,接下來咱們開始業務教程,也就是咱們常說的CURD,接下來進入第一篇,如何添加數據java
經過本篇文章,你能夠get到如下技能點mysql
實際開始以前,須要先走一些必要的操做,如安裝測試使用mysql,建立SpringBoot項目工程,設置好配置信息等,關於搭建項目的詳情能夠參考前一篇文章 190612-SpringBoot系列教程JPA之基礎環境搭建git
下面簡單的看一下演示添加記錄的過程當中,須要的配置github
沿用前一篇的表,結構以下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;
配置信息,與以前有一點點區別,咱們新增了更詳細的日誌打印;本篇主要目標集中在添加記錄的使用姿式,對於配置說明,後面單獨進行說明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
在開始以前,先聲明一下,由於我的實際項目中並無使用到JPA,對JPA的原則和hibernate的一些特性瞭解的也很少,目前處於學習探索階段,主要是介紹下使用姿式,下面的東西都是通過測試得出,有些地方描述可能與規範不太同樣,或者有些差錯,請發現的大佬指正數據庫
接下來咱們進入正題,如何經過JPA實現咱們常見的Insert功能api
首先第一步就是將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; }
天然而然地,咱們就有幾個問題了
isDeleted
又如何與表中的 is_deleted
關聯呢?針對上面的問題,一個一個來講明
對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; }
有幾個有意思的地方,須要咱們注意
@Entity
這個註解比較重要,用於聲明這個POJO是一個與數據庫中叫作 money
的表關聯的對象;
@Entity
註解有一個參數name,用於指定表名,若是不主動指定時,默認用類名,即上面若是不指定那麼,那麼默認與表 moneypo
綁定另一個常見的方式是在類上添加註解 @Table
,而後指定表名,也是能夠的
@Data @Entity @Table(name = "money") public class MoneyPO { }
咱們能夠看到id上面有三個註解,咱們先看下前面兩個
@Id
顧名思義,用來代表這傢伙是主鍵,比較重要,須要特殊關照@GeneratedValue
設置初始值,談到主鍵,咱們通常會和」自增「這個一塊兒說,因此你常常會看到的取值爲 strategy = GenerationType.IDENTITY
(由數據庫自動生成)這個註解主要提供了四種方式,分別說明以下
取值 | 說明 |
---|---|
GenerationType.TABLE |
使用一個特定的數據庫表格來保存主鍵 |
GenerationType.SEQUENCE |
根據底層數據庫的序列來生成主鍵,條件是數據庫支持序列 |
GenerationType.IDENTITY |
主鍵由數據庫自動生成(主要是自動增加型) |
GenerationType.AUTO |
主鍵由程序控制 |
關於這幾種使用姿式,這裏不詳細展開了,有興趣的能夠能夠看一下這博文: @GeneratedValue
這個註解就是用來解決咱們pojo成員名和數據庫列名不一致的問題的,這個註解內部的屬性也很多,相對容易理解,後面會單開一章來記錄這些經常使用註解的說明查閱
這個註解和前面不同的是它並不是來自jpa-api
包,而是spring-data-common
包中提供的,表示會根據當前時間建立一個時間戳對象
到這裏這個POJO已經建立完畢,後續的表中添加記錄也能夠直接使用它了,可是還有幾個問題是沒有明確答案的,先提出來,期待後文能夠給出回答
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> { }
好的,到這裏就能夠直接添加數據了 (感受什麼都沒幹,你竟然告訴我能夠插入數據???)
常規的使用姿式,無非單個插入和批量插入,咱們先來看一下常規操做
@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); } }
看下上面的兩個插入方式,就這麼簡單,
save
, saveAll
方法就ok了上面是通常的使用姿式,那麼非通常使用姿式呢?
在建立表的時候,咱們知道字段都有默認值,那麼若是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
那麼有辦法解決麼?難道就這麼向現實放棄,向大佬妥協麼?
默認值嘛,一個很容易想到的方法,我直接在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()); } }
再次執行看下結果如何,順利走下去,沒有報錯,喜大普奔
這樣我就知足了嗎?要是手抖上面測試註釋掉的那一行忘了註釋,豈不是依然會跪?並且我但願是表中的默認值,直接在代碼中硬編碼會不會不太優雅?這個主動設置的默認值,在後面查詢的時候會不會有坑?
咱們的解決方法也簡單,在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
)
針對上面的PO對象,有幾個地方感受不爽,isDelete我想要boolean,true表示刪除false表示沒刪除,搞一個byte用起來太不方便了,這個要怎麼搞?
這個並不怎麼複雜,由於直接將byte類型改爲boolean就能夠了,若是db中時0對應的false;1對應的true,下面是驗證結果,並無啥問題
在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,和咱們上面默認的是個反的,要怎麼搞?
再插入的時候,咱們上面的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方式進行插入or修改,那麼若是沒有這個註解,插入時也不指定id,會怎樣呢?
很遺憾的是直接拋異常了,沒有這個註解,就必須手動賦值id了
本文主要介紹了下如何使用JPA來實現插入數據,單個or批量插入,也拋出了一些問題,有的給出了回答,有的等待後文繼續跟進,下面簡單小結一下主要的知識點
@Entity
, @Table
用於指定這個POJO對應哪張表@Column
用於POJO的成員變量與表中的列進行關聯@Id
@GeneratedValue
來指定主鍵@DynamicInsert
,實現最終拼接部分sql方式插入此外本文還留了幾個坑沒有填
盡信書則不如,以上內容,純屬一家之言,因我的能力有限,不免有疏漏和錯誤之處,如發現bug或者有更好的建議,歡迎批評指正,不吝感激
下面一灰灰的我的博客,記錄全部學習和工做中的博文,歡迎你們前去逛逛