SpringBoot系列教程JPA之update使用姿勢


原文: 190623-SpringBoot系列教程JPA之update使用姿勢

上面兩篇博文拉開了jpa使用姿勢的面紗一角,接下來我們繼續往下扯,數據插入db之后,並不是說就一層不變了,就好比我在銀行開了戶,當然是准備往里面存錢了,有存就有取(特別是當下銀行利率這么低還不如買比特幣屯着,截止19年6月22日,btc已經突破1.1w$,可惜沒錢買😭)這就是我們今天的主題,數據更新--update的使用姿勢

通過本篇博文,您至少可以選到

  • save() 直接根據id來修改記錄
  • 利用jpl 實現查詢修改的使用姿勢
  • 初識事物的神秘面紗

I. 環境准備

在開始之前,當然得先准備好基礎環境,如安裝測試使用mysql,創建SpringBoot項目工程,設置好配置信息等,關於搭建項目的詳情可以參考前一篇文章

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

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

3. 數據准備

數據修改嘛,所以我們先向表里面插入兩條數據,用於后面的操作

INSERT INTO `money` (`id`, `name`, `money`, `is_deleted`, `create_at`, `update_at`)
VALUES
    (21, 'jpa 修改->一灰灰', 1212, 0, '2019-06-22 21:41:13', '2019-06-22 21:41:13'),
    (22, 'jpa 修改->一灰灰', 6666, 0, '2019-06-22 21:41:13', '2019-06-22 21:41:13');

II. Update使用教程

下面開始進入正題,為方便初看的同學(沒空或者沒興趣瞅前面幾個博文的同學)會有部分內容和前面的博文相同,看過的請無視

1. 表關聯POJO

前面插入篇已經介紹了POJO的逐步創建過程,已經對應的注解含義,下面直接貼出成果

@Data
@DynamicInsert
@Entity
@Table(name = "money")
public class MoneyPO {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
<span class="hljs-keyword">private</span> Integer id;

<span class="hljs-meta">@Column</span>(name = <span class="hljs-string">"name"</span>)
<span class="hljs-keyword">private</span> String name;

<span class="hljs-meta">@Column</span>(name = <span class="hljs-string">"money"</span>)
<span class="hljs-keyword">private</span> Long money;

<span class="hljs-meta">@Column</span>(name = <span class="hljs-string">"is_deleted"</span>)
<span class="hljs-keyword">private</span> Byte isDeleted;

<span class="hljs-meta">@Column</span>(name = <span class="hljs-string">"create_at"</span>)
<span class="hljs-meta">@CreatedDate</span>
<span class="hljs-keyword">private</span> Timestamp createAt;

<span class="hljs-meta">@Column</span>(name = <span class="hljs-string">"update_at"</span>)
<span class="hljs-meta">@CreatedDate</span>
<span class="hljs-keyword">private</span> Timestamp updateAt;

}

上面類中的幾個注解,說明如下

  • @Data 屬於lombok注解,與jpa無關,自動生成getter/setter/equals/hashcode/tostring等方法
  • @Entity, @Table jpa注解,表示這個類與db的表關聯,具體匹配的是表 money
  • @Id @GeneratedValue 作用與自增主鍵
  • @Column表明這個屬性與表中的某列對應
  • @CreateDate根據當前時間來生成默認的時間戳

2. Repository API聲明

接下來我們新建一個api繼承自CurdRepository,然后通過這個api來與數據庫打交道

public interface MoneyUpdateRepository extends CrudRepository<MoneyPO, Integer> {
}

3. 使用姿勢

a. save

在前面一篇插入博文中,我們知道當POJO的id存在時,調用save方法可能有兩種情況

  • 若db中這個id對應的字段不存在,則插入
  • 若db中這個id對應的字段存在,則更新

我們來試一下更新的效果,下面的代碼演示了兩塊,一個是當po中的所有成員值有效,更新其中的一個時,會怎樣;另外一個演示的是部分更新時會怎樣(name為空,表示我不希望更新name)

public void simpleUpdateById() {
    MoneyPO record = moneyUpdateRepository.findById(21).get();
    // 直接修改這個record的內容
    record.setMoney(3333L);
    moneyUpdateRepository.save(record);
record = moneyUpdateRepository.findById(<span class="hljs-number">21</span>).get();
System.out.println(<span class="hljs-string">"after updateMoney record: "</span> + record);


record.setName(<span class="hljs-keyword">null</span>);
record.setMoney(<span class="hljs-number">6666L</span>);
moneyUpdateRepository.save(record);

record = moneyUpdateRepository.findById(<span class="hljs-number">21</span>).get();
System.out.println(<span class="hljs-string">"after updateMoney record: "</span> + record);

}

輸出結果發現,前面執行成功,后面執行失敗

上面為第一個執行結果,從拼接的sql可以知道,是全量的修改;輸出結果也如我們預期

后面將name設置為空之后,再次更新,發現拋出異常,如下,這個是因為我們的db限制,字段不允許有null的存在

從拼接的sql上看,我們知道是因為每個成員都作為了update sql家族的一員,在insert這一篇中我們也遇到了類似的問題,當時是在POJO上添加注解@DynamicInsert,根據實際需要選擇插入,那么在更新這里是不是也有類似的注解呢

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

在pojo上添加注解@DynamicUpdate之后,再次進行嘗試,結果如下

居然還是失敗了,從輸出的sql來看,居然把namemoney都當成了sql的一部分,難道是因為我們調用了setter方法的原因么。待着猜測,再來一次

MoneyPO toUpdate = new MoneyPO();
toUpdate.setId(21);
toUpdate.setMoney(6666L);
moneyUpdateRepository.save(toUpdate);
record = moneyUpdateRepository.findById(21).get();
System.out.println("after updateMoney record: " + record);

輸出結果如下,看來我們上面的猜測並不對,拼接sql應該是根據哪個字段發生了變化,就把哪個做為sql的一部分來做的

上面這個使用姿勢看完之后,會有一個明顯的感覺,這個更新的支持,必須先獲取目標對象再進行修改,很難滿足我們的日常業務場景;

b. 查詢更新

根據某個條件來更新對應的數據,這個就比較常見了,在jpa中,沒有找到根據方法名來支撐這種場景的方式,但是發現了另外一個有意思的東西--jql

直接在方法方面,添加注解,注解內部寫sql

/** * 根據金錢來修改狀態 * * @param money * @param state */
@Modifying
@Query("update MoneyPO m set m.isDeleted=?2 where m.money=?1")
void updateStateByMoney(Long money, Byte state);

/**

  • 表達式計算
  • @param id
  • @param money
    */
    @Modifying
    @Query("update MoneyPO m set m.money=m.money + ?2 where m.id=?1")
    void addMoneyById(Integer id, Long money);

上面就是一個查詢更新的case,注意兩個注解

  • @Modifying 這個必須有,告訴框架我們執行的是更新/刪除操作
  • @Query 內部是正常的sql語句,但是需要注意的是表名,不是實際的表,而是我們前面定義的POJO

然后來測試一下使用

public void updateByQuery() {
    // 通過查詢修改
    moneyUpdateRepository.updateStateByMoney(6666L, (byte) 0x01);
MoneyPO record = moneyUpdateRepository.findById(<span class="hljs-number">21</span>).get();
System.out.println(<span class="hljs-string">"after update record: "</span> + record);


moneyUpdateRepository.addMoneyById(<span class="hljs-number">21</span>, <span class="hljs-number">3333L</span>);
record = moneyUpdateRepository.findById(<span class="hljs-number">21</span>).get();
System.out.println(<span class="hljs-string">"after addMoney record: "</span> + record);

}

執行上面的代碼,悲催的發現居然報錯了 Caused by: javax.persistence.TransactionRequiredException: Executing an update/delete query

從堆棧的描述上來看,更新/刪除必須要開啟事務,那么事務是什么東西呢?下面推薦幾篇博文

關於jpa中更多事務相關的,后面再引入,我們先回到本文主題,如何解決問題: 在調用上面的方法上面添加事務注解即可

@Transactional
public void testUpdate() {
    simpleUpdateById();
    updateByQuery();
}

再次執行之后,結果如下

看上面的結果,發現money+3333之后的輸出結果居然還是6666;但是我們再看db的結果,已經是9999了,為什么會這樣呢?

上面這個問題比較有意思了,初步猜測與事物已經jpa的內部緩存機制有關系,至於具體是不是這樣,有必要專門開坑來驗證一二

4. 小結

利用JPA實現表數據的更新,上面主要介紹了兩種方式, save + jql

save

通過save更新時,需要指定id來實現單條記錄的修改

jql

語法與sql差不多,配合兩個注解 @Modifying, @Query來使用,下面是一個實例,兩點需要注意

  • 表名是我們定義的與db中表關聯的POJO
  • 參數傳遞格式為?index, index為參數位置
@Modifying
@Query("update MoneyPO m set m.isDeleted=?2 where m.money=?1")
void updateStateByMoney(Long money, Byte state);

數據修改和刪除,需要顯示聲明事務,否則會報錯,一個是在調用的方法上添加注解 @Transactional,或者直接在repository api的接口上添加注解 @Transactional

II. 其他

0. 源碼與相關博文

源碼

相關博文

1. 一灰灰Blog

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

下面一灰灰的個人博客,記錄所有學習和工作中的博文,歡迎大家前去逛逛

原文地址:https://www.cnblogs.com/yihuihui/p/11071949.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM