SpringBoot系列教程JPA之delete使用姿式詳解

原文: 190702-SpringBoot系列教程JPA之delete使用姿式詳解

常見db中的四個操做curd,前面的幾篇博文分別介紹了insert,update,接下來咱們看下delete的使用姿式,經過JPA能夠怎樣刪除數據java

通常來說是不建議物理刪除(直接從表中刪除記錄)數據的,在現在數據就是錢的時代,更常見的作法是在表中添加一個表示狀態的字段,而後經過修改這個字段來表示記錄是否有效,從而實現邏輯刪除;這麼作的緣由以下mysql

  • 物理刪除,若是出問題恢復比較麻煩
  • 沒法保證代碼必定準確,在出問題的時候,刪錯了數據,那就gg了
  • 刪除數據,會致使重建索引
  • Innodb數據庫對於已經刪除的數據只是標記爲刪除,並不真正釋放所佔用的磁盤空間,這就致使InnoDB數據庫文件不斷增加,也會致使表碎片
  • 邏輯刪除,保留數據,方便後續針對數據的挖掘或者分析

<!-- more -->git

I. 環境準備

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

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

1. 表準備

沿用前一篇的表,結構以下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;

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. 數據準備

數據修改嘛,因此咱們先向表裏面插入兩條數據,用於後面的操做api

INSERT INTO `money` (`id`, `name`, `money`, `is_deleted`, `create_at`, `update_at`)
VALUES
    (20, 'jpa 一灰灰5', 2323, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
    (21, 'jpa 一灰灰6', 2333, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
    (22, 'jpa 一灰灰7', 6666, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41'),
    (23, 'jpa 一灰灰8', 2666, 0, '2019-07-02 08:42:41', '2019-07-02 08:42:41');

II. Delete使用教程

下面談及到的刪除,都是物理刪除,能夠理解爲直接將某些記錄從表中抹除掉(並非說刪了就徹底沒有辦法恢復)針對CURD四種操做而言,除了read以外,另外三個insert,update,delete都會加寫鎖(通常來將會涉及到行鎖和gap鎖,從後面也會看到,這三個操做要求顯示聲明事物)bash

1. 表關聯POJO

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

@Data
@DynamicUpdate
@DynamicInsert
@Entity
@Table(name = "money")
public class MoneyPO {
    @Id
    // 若是是auto,則會報異常 Table 'mysql.hibernate_sequence' doesn't exist
    // @GeneratedValue(strategy = GenerationType.AUTO)
    @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;

}

上面類中的幾個註解,說明以下

  • @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 MoneyDeleteRepository extends CrudRepository<MoneyPO, Integer> {
    /**
     * 查詢測試
     * @param id
     * @return
     */
    List<MoneyPO> queryByIdGreaterThanEqual(int id);
}

3. 使用姿式

先寫一個用於查詢數據的方法,用於校驗咱們執行刪除以後,是否確實被刪除了

private void showLeft() {
    List<MoneyPO> records = moneyDeleteRepository.queryByIdGreaterThanEqual(20);
    System.out.println(records);
}

在執行下面操做以前,先調用上面的,輸出結果如

[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=21, name=jpa 一灰灰6, money=2333, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=22, name=jpa 一灰灰7, money=6666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

a. 根據主鍵id進行刪除

這種應該屬於最多見的刪除方式了,爲了不誤刪,經過精確的主鍵id來刪除記錄,是一個很是好的使用姿式,CrudRepository這個接口已經提供了對應的方法,因此咱們能夠直接使用

private void deleteById() {
    // 直接根據id進行刪除
    moneyDeleteRepository.deleteById(21);
    showLeft();
}

執行完畢以後,輸出結果以下,對比前面的輸出能夠知道 id=21 的記錄被刪除了

[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=22, name=jpa 一灰灰7, money=6666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

而後一個疑問天然而然的來了,若是這個id對應的記錄不存在,會怎樣?

把上面代碼再執行一次,發現拋了異常

爲何會這樣呢?咱們debug進去,調用的實現是默認的 SimpleJpaRepository,其源碼如

// 類爲: org.springframework.data.jpa.repository.support.SimpleJpaRepository
@Transactional
public void deleteById(ID id) {

    Assert.notNull(id, ID_MUST_NOT_BE_NULL);

    delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
            String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
}

@Transactional
public void delete(T entity) {

    Assert.notNull(entity, "The entity must not be null!");
    em.remove(em.contains(entity) ? entity : em.merge(entity));
}

從源碼能夠看出,這個是先經過id進行查詢,若是對應的記錄不存在時,直接拋異常;當存在時,走remove邏輯;

若是咱們但願刪除一個不存在的數據時,不要報錯,能夠怎麼辦?

  • 自定義實現一個繼承SimpleJpaRepository的類,覆蓋刪除方法
@Repository
@Transactional(readOnly = true)
public class MoneyDeleteRepositoryV2 extends SimpleJpaRepository<MoneyPO, Integer> {

    @Autowired
    public MoneyDeleteRepositoryV2(EntityManager em) {
        this(JpaEntityInformationSupport.getEntityInformation(MoneyPO.class, em), em);
    }

    public MoneyDeleteRepositoryV2(JpaEntityInformation<MoneyPO, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
    }

    public MoneyDeleteRepositoryV2(Class<MoneyPO> domainClass, EntityManager em) {
        super(domainClass, em);
    }

    @Override
    public void deleteById(Integer id) {
        Optional<MoneyPO> rec = findById(id);
        rec.ifPresent(super::delete);
    }
}

而後再調用上面的方法就能夠了,不演示具體的測試case了,源碼能夠到項目工程中查看 👉 源碼

b. 條件判斷刪除

雖然根據id進行刪除比較穩妥,但也沒法避免某些狀況下須要根據其餘的字段來刪除,好比咱們但願刪除名爲 jpa 一灰灰7的數據,這時則須要咱們在MoneyDeleteRepository新增一個方法

/**
 * 根據name進行刪除
 *
 * @param name
 */
void deleteByName(String name);

這裏比較簡單的提一下這個方法的命名規則,後面在查詢這一篇會更加詳細的說明;

  • delete 表示執行的是刪除操做
  • By 表示根據某個字段來進行條件限定
  • Name 這個有POJO中的屬性匹配

上面這個方法,若是翻譯成sql,至關於 delete from money where name=xx

調用方式和前面同樣,以下

private void deleteByName() {
    moneyDeleteRepository.deleteByName("jpa 一灰灰7");
    showLeft();
}

而後咱們執行上面的測試,發現並不能成功,報錯了

經過前面update的學習,知道須要顯示加一個事物的註解,咱們這裏直接加在Repository

/**
 * 根據name進行刪除
 *
 * @param name
 */
@Transactional
void deleteByName(String name);

而後再次執行輸出以下,這裏咱們把sql的日誌也打印了

Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.name=?
Hibernate: delete from money where id=?
Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=?
[MoneyPO(id=20, name=jpa 一灰灰5, money=2323, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0), MoneyPO(id=23, name=jpa 一灰灰8, money=2666, isDeleted=0, createAt=2019-07-02 08:42:41.0, updateAt=2019-07-02 08:42:41.0)]

從最終剩餘的記錄來看,name爲jpa 一灰灰7的被刪除了,再看一下前面刪除的sql,會發現一個有意思的地方,deleteByName 這個方法,翻譯成sql變成了兩條

  • select * from money where name=xxx 先根據name查詢記錄
  • delete from money where id = xxx 根據前面查詢記錄的id,刪除記錄

c. 比較刪除

接下來演示一個刪除money在[2000,3000]區間的記錄,這時咱們新增的放入能夠是

/**
 * 根據數字比較進行刪除
 *
 * @param low
 * @param big
 */
@Transactional
void deleteByMoneyBetween(Long low, Long big);

經過方法命名也能夠簡單知道上面這個等同於sql delete from money where money between xxx and xxx

測試代碼爲

private void deleteByCompare() {
    moneyDeleteRepository.deleteByMoneyBetween(2000L, 3000L);
    showLeft();
}

輸出日誌

Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.money between ? and ?
Hibernate: delete from money where id=?
Hibernate: delete from money where id=?
Hibernate: select moneypo0_.id as id1_0_, moneypo0_.create_at as create_a2_0_, moneypo0_.is_deleted as is_delet3_0_, moneypo0_.money as money4_0_, moneypo0_.name as name5_0_, moneypo0_.update_at as update_a6_0_ from money moneypo0_ where moneypo0_.id>=?
[]

從拼接的sql能夠看出,上面的邏輯等同於,先執行了查詢,而後根據id一個一個進行刪除....

4. 小結

咱們經過聲明方法的方式來實現條件刪除;須要注意

  • 刪除須要顯示聲明事物 @Transactional
  • 刪除一個不存在的記錄,會拋異常
  • 聲明刪除方法時,實際等同於先查詢記錄,而後根據記錄的id進行精準刪除

II. 其餘

源碼

相關博文

1. 一灰灰Blog

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

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

一灰灰blog

相關文章
相關標籤/搜索