Springboot + MySQL+ JPA Ⅲ delete方法详解

本文通过源码分析了JPA中的deleteById、delete、deleteAllById和deleteAll等删除方法的实现原理,指出它们在单条和批量删除上的区别。deleteById和delete实质上是同源的,都先查询再删除。deleteAllById和deleteAll则是通过循环调用单条删除实现批量删除,而deleteAllInBatch和deleteAllByIdInBatch则直接执行批量删除SQL,效率更高,适用于大数据量删除场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、deleteById(Id id) 和 delete(T entity)

为什么要把这两个方法放在一起呢?我们先看源码再说

deleteById源码(通过id进行删除)

@Transactional
@Override
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)));
}
复制代码

delete源码(通过实体对象进行删除)

@Override
@Transactional
@SuppressWarnings("unchecked")
public void delete(T entity) {

   Assert.notNull(entity, "Entity must not be null!");

   if (entityInformation.isNew(entity)) {
      return;
   }

   Class<?> type = ProxyUtils.getUserClass(entity);

   T existing = (T) em.find(type, entityInformation.getId(entity));

   // if the entity to be deleted doesn't exist, delete is a NOOP
   if (existing == null) {
      return;
   }

   em.remove(em.contains(entity) ? entity : em.merge(entity));
}
复制代码

一目了然了吧!deleteById先在方法体内通过id求出entity对象,然后调用了delete的方法。也就是说,这两个方法同根同源,使用起来差距不大,结果呢?也是一样的,就是单条删除。实际使用中呢,也是使用deleteById的情况比较多,废话少说,try it。

Service层中添加deleteById方法(deleteById是三方件自带接口不需要在dao层中添加)

@Transactional
public void deleteById(Integer id){
    userDao.deleteById(id);
}
复制代码

control层

/**
 * 通过id进行删除数据
 * @param id
 */
@GetMapping("/deleteById")
public void deleteById(Integer id){
	userService.deleteById(id);
}
复制代码

浏览器测试成功 http://localhost:7777/deleteById?id=2


控制台打印了两行sql,如下:

Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: delete from user where id=?
复制代码

由此可见,先通过select看看实体对象是否存在,然后再通过id进行删除!

二、deleteAllById(Iterable<? extends ID> ids) 和 deleteAll(Iterable<? extends T> entities)

deleteAllById(Iterable<? extends ID> ids)(通过id进行批量删除)

@Override
@Transactional
public void deleteAllById(Iterable<? extends ID> ids) {

   Assert.notNull(ids, "Ids must not be null!");

   for (ID id : ids) {
      deleteById(id);
   }
}
复制代码

通过源码可以看出,就是遍历ids然后循环调用上面的deleteById(Id id)方法。

deleteAll(Iterable<? extends T> entities)(通过实体对象进行批量删除)

@Override
@Transactional
public void deleteAll(Iterable<? extends T> entities) {

   Assert.notNull(entities, "Entities must not be null!");

   for (T entity : entities) {
      delete(entity);
   }
}
复制代码

这个呢?也就是遍历entities然后循环调用上面的delete(T entity)方法

还有一个不传参数的deleteAll()方法来删除所有数据(慎用)

@Override
@Transactional
public void deleteAll() {

   for (T element : findAll()) {
      delete(element);
   }
}
复制代码

就是通过findAll求出所有实体对象然后循环调用delete方法

综上所述,我们发现以上所有的删除事件都是调用了delete(T entity)方法,也就是差距不是很大,就是单条 和多条删除的区别。

让我们来测试一下多条删除的场景:
Service层中添加deleteAllById方法(deleteAllById是三方件自带接口不需要在dao层中添加)

@Transactional
public void deleteAllById(Iterable ids){
	userDao.deleteAllById(ids);
}
复制代码

control层

/**
 * 通过id进行批量删除
 * @param ids
 */
@GetMapping("/deleteAllById")
public void deleteAllById(Integer[] ids){
	userService.deleteAllById(Arrays.asList(ids));
}
复制代码

浏览器测试成功 http://localhost:7777/deleteAllById?id=3,4
删除前:


删除后:

控制台打印如下:

Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: select user0_.id as id1_0_0_, user0_.age as age2_0_0_, user0_.name as name3_0_0_ from user user0_ where user0_.id=?
Hibernate: delete from user where id=?
Hibernate: delete from user where id=?
复制代码

由此可以看出,数据是一条一条的进行了删除。

三、deleteAllInBatch(Iterable entities)和deleteAllByIdInBatch(Iterable ids)

deleteAllInBatch(Iterable entities)源码(通过实体对象进行批量删除)

public static final String DELETE_ALL_QUERY_STRING = "delete from %s x";

@Override
@Transactional
public void deleteAllInBatch(Iterable<T> entities) {

   Assert.notNull(entities, "Entities must not be null!");

   if (!entities.iterator().hasNext()) {
      return;
   }

   applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em)
         .executeUpdate();
}
复制代码
/**
 * Creates a where-clause referencing the given entities and appends it to the given query string. Binds the given
 * entities to the query.
 *
 * @param <T> type of the entities.
 * @param queryString must not be {@literal null}.
 * @param entities must not be {@literal null}.
 * @param entityManager must not be {@literal null}.
 * @return Guaranteed to be not {@literal null}.
 */

public static <T> Query applyAndBind(String queryString, Iterable<T> entities, EntityManager entityManager) {

   Assert.notNull(queryString, "Querystring must not be null!");
   Assert.notNull(entities, "Iterable of entities must not be null!");
   Assert.notNull(entityManager, "EntityManager must not be null!");

   Iterator<T> iterator = entities.iterator();

   if (!iterator.hasNext()) {
      return entityManager.createQuery(queryString);
   }

   String alias = detectAlias(queryString);
   StringBuilder builder = new StringBuilder(queryString);
   builder.append(" where");

   int i = 0;

   while (iterator.hasNext()) {

      iterator.next();

      builder.append(String.format(" %s = ?%d", alias, ++i));

      if (iterator.hasNext()) {
         builder.append(" or");
      }
   }

   Query query = entityManager.createQuery(builder.toString());

   iterator = entities.iterator();
   i = 0;

   while (iterator.hasNext()) {
      query.setParameter(++i, iterator.next());
   }

   return query;
}
复制代码

通过上面的源码,我们大体能猜测出deleteAllInBatch(Iterable entities)的实现原理:
delete from %s where x=? or x=?
实际测试一下:http://localhost:7777/deleteAllInBatch?ids=14,15,16&names=a,b,c&ages=0,0,0
控制台打印如下:

Hibernate: delete from user where id=? or id=? or id=?
复制代码

deleteAllByIdInBatch(Iterable ids)源码(通过ids批量删除)


public static final String DELETE_ALL_QUERY_BY_ID_STRING = "delete from %s x where %s in :ids";

@Override
@Transactional
public void deleteAllByIdInBatch(Iterable<ID> ids) {

   Assert.notNull(ids, "Ids must not be null!");

   if (!ids.iterator().hasNext()) {
      return;
   }

   if (entityInformation.hasCompositeId()) {

      List<T> entities = new ArrayList<>();
      // generate entity (proxies) without accessing the database.
      ids.forEach(id -> entities.add(getReferenceById(id)));
      deleteAllInBatch(entities);
   } else {

      String queryString = String.format(DELETE_ALL_QUERY_BY_ID_STRING, entityInformation.getEntityName(),
            entityInformation.getIdAttribute().getName());

      Query query = em.createQuery(queryString);
      /**
       * Some JPA providers require {@code ids} to be a {@link Collection} so we must convert if it's not already.
       */
      if (Collection.class.isInstance(ids)) {
         query.setParameter("ids", ids);
      } else {
         Collection<ID> idsCollection = StreamSupport.stream(ids.spliterator(), false)
               .collect(Collectors.toCollection(ArrayList::new));
         query.setParameter("ids", idsCollection);
      }

      query.executeUpdate();
   }
}
复制代码

通过上面源码我们大体可以猜出deleteAllByIdInBatch(Iterable ids)的实现原理:
delete from %s where id in (?,?,?)
实际测试一下:http://localhost:7777/deleteAllByIdInBatch?ids=17,18,19 控制台打印如下:

Hibernate: delete from user where id in (? , ? , ?)
复制代码

这里同样有个不带参数的deleteAllInBatch()的方法,源码如下:

@Override
@Transactional
public void deleteAllInBatch() {
   em.createQuery(getDeleteAllQueryString()).executeUpdate();
}

public static final String DELETE_ALL_QUERY_STRING = "delete from %s x";

private String getDeleteAllQueryString() {
   return getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName());
}

复制代码

通过源码不难猜到实现原理吧,多的不说,直接给测试的控制台数据:
Hibernate: delete from user

最终结论:

从上面两种删除接口来看,第二种实现比起第一种更加的快捷;第一种就是一条一条的进行删除操作,如果有万级的数据,执行起来肯定非常耗时,所以如果数据量比较大的话,还是建议大家使用第二种。

作者:烟雨戏江南
链接:https://juejin.cn/post/7221425806349729848

### Spring Boot与Vue结合Element UI进行项目开发 #### 创建Spring Boot项目 为了启动一个新的Spring Boot项目,推荐使用Spring Initializr工具。在创建过程中可以选择必要的依赖项,比如Lombok用于简化Java代码、Spring Web模块以及MyBatis框架和MySQL驱动程序来处理数据库操作[^2]。 ```java // application.properties 配置文件示例 spring.datasource.url=jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=password mybatis.mapper-locations=classpath:mapper/*.xml ``` #### 构建前端环境并引入Element UI 对于前端部分,采用Vue CLI快速建立Vue应用,并通过npm安装`element-ui`库及其样式资源。这使得能够轻松利用Element提供的各种UI组件,如表格、表单、对话框等,从而加速界面设计过程[^1]。 ```bash # 安装 element-ui 及其 CSS 文件 npm install element-ui --save ``` 接着,在项目的入口文件main.js中全局注册Element: ```javascript import Vue from 'vue' import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); new Vue({ render: h => h(App), }).$mount('#app'); ``` #### 后端API接口定义 后端主要负责业务逻辑处理和服务端渲染工作。可以借助于Spring Data JPA或者MyBatis实现数据持久层的操作;而RESTful API的设计则遵循HTTP协议标准方法GET, POST, PUT 和 DELETE 来完成CRUD (Create Retrieve Update Delete) 功能。 ```java @RestController @RequestMapping("/api/users") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ResponseEntity<User> getUserById(@PathVariable Long id){ User user = userService.findById(id).orElseThrow(() -> new ResourceNotFoundException("User not exist with id :" + id)); return ResponseEntity.ok(user); } } ``` #### 前后端交互及状态管理 前后端分离架构下,通常会使用Axios发起异步请求获取服务器返回的数据。同时 Vuex Store可以帮助维护应用程序的状态变化,确保不同视图之间共享相同的信息流。 ```javascript // store/modules/user.js 示例 export const state = { token: localStorage.getItem('token') || '', }; export const mutations = { SET_TOKEN(state, token) { state.token = token; localStorage.setItem('token', token); }, }; ``` #### 登录验证机制 模拟成功的登录流程可以通过设置本地存储中的JWT(JSON Web Token),并在每次发送HTTP请求时将其附加到Authorization头部字段里去认证身份。一旦接收到有效的响应,则跳转至首页显示欢迎消息。 ```html <!-- Login.vue 组件 --> <template> <div> <el-form :model="ruleForm" status-icon :rules="rules" ref="ruleForm"> <!-- 用户名密码输入框省略... --> <el-button type="primary" @click="submitForm()">提交</el-button> </el-form> </div> </template> <script> methods:{ submitForm(){ axios.post('/login',{ username:this.ruleForm.username, password:this.ruleForm.password }).then(response=>{ this.$store.commit('SET_TOKEN',response.data.token); // 存储Token router.push({path:'/'}); }); } } </script> ``` #### 使用Tree控件展示菜单结构 当涉及到权限管理和导航栏布局时,可选用Element提供的Tree组件来呈现层次化的节点列表。此组件支持多选模式(`show-checkbox`),默认展开全部子级(`default-expand-all`)等功能特性[^3]。 ```html <!-- Menu.vue 组件 --> <template> <el-tree :data="menuData" show-checkbox default-expand-all node-key="id" ref="tree" highlight-current :props="defaultProps"> </el-tree> </template> <script> export default{ data() { return { menuData:[], // 菜单项数组 defaultProps: { children: 'children', label: 'label' } }; } } </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值