常见db中的四个操做curd,前面的几篇博文分别介绍了insert,update,接下来咱们看下delete的使用姿式,经过JPA能够怎样删除数据mysql
通常来说是不建议物理删除(直接从表中删除记录)数据的,在现在数据就是钱的时代,更常见的作法是在表中添加一个表示状态的字段,而后经过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么作的缘由以下git
在开始以前,固然得先准备好基础环境,如安装测试使用mysql,建立SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情能够参考前一篇文章github
下面简单的看一下演示添加记录的过程当中,须要的配置spring
沿用前一篇的表,结构以下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;
复制代码
配置信息,与以前有一点点区别,咱们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿式,对于配置说明,后面单独进行说明数据库
## 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
复制代码
数据修改嘛,因此咱们先向表里面插入两条数据,用于后面的操做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');
复制代码
下面谈及到的删除,都是物理删除,能够理解为直接将某些记录从表中抹除掉(并非说删了就彻底没有办法恢复)针对CURD四种操做而言,除了read以外,另外三个insert,update,delete都会加写锁(通常来将会涉及到行锁和gap锁,从后面也会看到,这三个操做要求显示声明事物)bash
前面插入篇已经介绍了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
根据当前时间来生成默认的时间戳接下来咱们新建一个api继承自CurdRepository
,而后经过这个api来与数据库打交道
public interface MoneyDeleteRepository extends CrudRepository<MoneyPO, Integer> {
/** * 查询测试 * @param id * @return */
List<MoneyPO> queryByIdGreaterThanEqual(int id);
}
复制代码
先写一个用于查询数据的方法,用于校验咱们执行删除以后,是否确实被删除了
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)]
复制代码
这种应该属于最多见的删除方式了,为了不误删,经过精确的主键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了,源码能够到项目工程中查看 👉 源码
虽然根据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,删除记录接下来演示一个删除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一个一个进行删除....
咱们经过声明方法的方式来实现条件删除;须要注意
@Transactional
源码
相关博文
尽信书则不如,以上内容,纯属一家之言,因我的能力有限,不免有疏漏和错误之处,如发现bug或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的我的博客,记录全部学习和工做中的博文,欢迎你们前去逛逛