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

常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿势,通过JPA可以怎样删除数据

一般来讲是不建议物理删除(直接从表中删除记录)数据的,在如今数据就是钱的时代,更常见的做法是在表中添加一个表示状态的字段,然后通过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么做的原因如下

  • 物理删除,如果出问题恢复比较麻烦
  • 无法保证代码一定准确,在出问题的时候,删错了数据,那就gg了
  • 删除数据,会导致重建索引
  • Innodb数据库对于已经删除的数据只是标记为删除,并不真正释放所占用的磁盘空间,这就导致InnoDB数据库文件不断增长,也会导致表碎片
  • 逻辑删除,保留数据,方便后续针对数据的挖掘或者分析

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
(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锁,从后面也会看到,这三个操作要求显示声明事物)

1. 表关联POJO

前面插入篇已经介绍了POJO的逐步创建过程,已经对应的注解含义,下面直接贴出成果

@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了,源码可以到项目工程中查看

SpringBoot系列教程JPA之delete使用姿势详解的更多相关文章

  1. SpringBoot系列教程JPA之query使用姿势详解之基础篇

    前面的几篇文章分别介绍了CURD中的增删改,接下来进入最最常见的查询篇,看一下使用jpa进行db的记录查询时,可以怎么玩 本篇将介绍一些基础的查询使用姿势,主要包括根据字段查询,and/or/in/l ...

  2. SpringBoot系列教程JPA之update使用姿势

    原文: 190623-SpringBoot系列教程JPA之update使用姿势 上面两篇博文拉开了jpa使用姿势的面纱一角,接下来我们继续往下扯,数据插入db之后,并不是说就一层不变了,就好比我在银行 ...

  3. SpringBoot 系列教程 JPA 错误姿势之环境配置问题

    191218-SpringBoot 系列教程 JPA 错误姿势之环境配置问题 又回到 jpa 的教程上了,这一篇源于某个简单的项目需要读写 db,本想着直接使用 jpa 会比较简单,然而悲催的是实际开 ...

  4. SpringBoot系列教程JPA之指定id保存

    原文链接: 191119-SpringBoot系列教程JPA之指定id保存 前几天有位小伙伴问了一个很有意思的问题,使用 JPA 保存数据时,即便我指定了主键 id,但是新插入的数据主键却是 mysq ...

  5. SpringBoot系列教程JPA之新增记录使用姿势

    SpringBoot系列教程JPA之新增记录使用姿势 上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第 ...

  6. SpringBoot系列教程JPA之基础环境搭建

    JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Spring Data JPA是在 Hibernat ...

  7. Springboot系列(四)web静态资源配置详解

    Springboot系列(四)web静态资源配置 往期精彩 SpringBoot系列(一)idea新建Springboot项目 SpringBoot系列(二)入门知识 SpringBoot系列(三)配 ...

  8. SpringBoot 系列教程之编程式事务使用姿势介绍篇

    SpringBoot 系列教程之编程式事务使用姿势介绍篇 前面介绍的几篇事务的博文,主要是利用@Transactional注解的声明式使用姿势,其好处在于使用简单,侵入性低,可辨识性高(一看就知道使用 ...

  9. Java工程师之SpringBoot系列教程前言&目录

    前言 与时俱进是每一个程序员都应该有的意识,当一个Java程序员在当代步遍布的时候,你就行该想到我能多学点什么.可观的是后端的框架是稳定的,它们能够维持更久的时间在应用中,而不用担心技术的更新换代.但 ...

随机推荐

  1. 最详细的keepalived+lvs-dr配置文档

    四台台机器: 分发器主:192.168.0.154 分发器备:192.168.0.171 rs_1:192.168.0.131 rs_2:192.168.0.132 keepalived安装: yum ...

  2. 前端知识--控制input按钮的显示与隐藏

    if(fm.ReadFlag.value=="readonly"){ var arr = document.getElementsByTagName("input&quo ...

  3. Java 多线程实战

    Java多线程 public class ThreadTest { public static void main(String[] args) throws InterruptedException ...

  4. WinDbg常用命令系列---反汇编u*

    u, ub, uu (Unassemble) u*命令显示内存中指定程序代码的汇编转换.不要将此命令与~u(解冻线程)命令混淆. u[u|b] Range u[u|b] Address u[u|b] ...

  5. Redis的订阅、事务、持久化

    1.Redius的订阅: 运用关键字subscribe订阅: 关键字publish发布: 发布后,订阅的页面才会出现发布的内容. 2.Redis事务: Redis事务与mysql的事务不同,mysql ...

  6. dinoql 试用

    dinoql 前面有过介绍,详细的参考文档即可,这篇主要是简单使用 注意目前dinoql 直接通过node 运行会有window 的问题,有好几种解决方法,后边会说明 环境准备 项目初始化 yarn ...

  7. 留言板(Nodejs)

    一.知识点 ①nodejs实际开发中,通常把所有的HTML文件放到views目录中 ②nodejs实际开发中,通常把所有的静态资源文件放到public目录中,方便统一处理 当浏览器收到HTML响应内容 ...

  8. 使用Sublime Text 写Processing

    本来以为是个很简单的事情,没想到一波三折~ 1.下载Sublime Text 3(中文版)并且安装,没啥好说的 2.打开[工具 - 命令面板 - install package],接着就报错了 “Th ...

  9. Good Morning

    题目链接:Good Morning 题目大意:按键盘上的数字,只能在此位置的基础上往右往下按,要求输出与所给值差的绝对值最小的数 AC代码如下: #include <iostream> # ...

  10. 安利一个github上面的一个神级库thefuck,Linux命令敲错了,没关系,自动纠正你的命令

    没错就是这么神奇,名字相当噶性,thefuck.当你命令输入错误不要怕,直接来一句fuck,自动纠正你输入的命令. 在你输入错误的命令的时候,忍俊不禁的想来一句fuck,没错你不仅可以嘴上说,命令里面 ...