内容简介:常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿势,通过JPA可以怎样删除数据一般来讲是不建议物理删除(直接从表中删除记录)数据的,在如今数据就是钱的时代,更常见的做法是在表中添加一个表示状态的字段,然后通过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么做的原因如下在开始之前,当然得先准备好基础环境,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章
常见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了,源码可以到项目工程中查看 :point_right: 源码
b. 条件判断删除
虽然根据id进行删除比较稳妥,但也无法避免某些情况下需要根据其他的字段来删除,比如我们希望删除名为 jpa 一灰灰7
的数据,这时则需要我们在 MoneyDeleteRepository
新增一个方法
/** * 根据name进行删除 * * @param name */ void deleteByName(String name);
这里比较简单的提一下这个方法的命名规则,后面在查询这一篇会更加详细的说明;
delete By Name
上面这个方法,如果翻译成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 delete from money where id = xxx
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
II. 其他
源码
- 工程: https://github.com/liuyueyi/spring-boot-demo
- module: https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/102-jpa
相关博文
- mysql之锁与事务详解
- Spring学习之事务的使用姿势
- Spring学习之事务管理与传播属性
- 190612-SpringBoot系列教程JPA之基础环境搭建
- 190614-SpringBoot系列教程JPA之新增记录使用姿势
- 190623-SpringBoot系列教程JPA之update使用姿势
1. 一灰灰Blog
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛
- 一灰灰Blog个人博客 https://blog.hhui.top
- 一灰灰Blog-Spring专题博客 http://spring.hhui.top
打赏 如果觉得我的文章对您有帮助,请随意打赏。
以上所述就是小编给大家介绍的《190702-SpringBoot系列教程JPA之delete使用姿势详解》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Linux内核ROP姿势详解(二)
- SpringBoot高级篇JdbcTemplate之数据插入使用姿势详解
- Redis全方位详解--数据类型使用场景和redis分布式锁的正确姿势
- 强大的姿势感知模型用于姿势不变的人脸识别
- 从姿势到图像——基于人体姿势引导的时尚图像生成算法
- 行人重识别告别辅助姿势信息,港中文、商汤等提出姿势无关的特征提取GAN
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
微信小程序运营与推广完全自学手册
王洪波 / 电子工业出版社 / 2018-6 / 59
本书是运营管理方面的书籍,将小程序的运营推广问题置千小程序的整个运营管理体系中来谈,主要讲述小程序的定位规划、营销吸粉策略、评估优化这三大方面的内容,这三方面的内容之间是三位一体、密切相关的。 书中通过列举丰富且具有代表性的小程序实际案例来向读者提供些可行的运营推广办法。案例涉及美食类、电商类、旅游类、媒体类等小程序,可供多个行业的小程序运营者参考借鉴。 书中所提供的各种小程序营销策略......一起来看看 《微信小程序运营与推广完全自学手册》 这本书的介绍吧!