SpringBoot中JPA@Query插入查询编写报错,求正确实现方案
如何在Spring Boot JPA中正确编写插入查询
嘿,我来帮你搞定这个JPA插入查询的问题!你原来的写法确实踩了几个常见的坑,咱们一步步拆解问题,然后给出靠谱的解决方案。
首先先说说你原代码的问题出在哪:
- 缺少
@Modifying和@Transactional注解:INSERT属于数据修改操作,JPA需要这两个注解来识别这不是普通的查询,而是要修改数据库的操作,没加的话肯定会报错。 - 语法完全不对:JPQL的INSERT语法根本不是这么写的,而且你把列名和值整个当成参数传的方式,JPA根本不支持——参数绑定是用来传值的,不能用来传列名、表名这类标识符。
- 混淆了JPQL和原生SQL:如果直接写数据库表名,得声明是原生SQL,不然JPA会把它当成实体类名来解析。
接下来分两种常见场景给你解决方案:
场景1:固定列名的插入(最常用)
如果你的插入列是固定的,只是值需要动态传入,这是最规范的用法,直接这么写就行:
首先确保你的LaganikartaDetails实体类和数据库表对应上,然后在Repository接口里这么定义方法:
import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.transaction.annotation.Transactional; import org.springframework.data.repository.query.Param; public interface LaganikartaDetailsRepository extends CrudRepository<LaganikartaDetails, Long> { @Modifying @Transactional // 用JPQL的话,这里写的是实体类的属性名,不是数据库列名哦 @Query("INSERT INTO LaganikartaDetails (name, email, phone) VALUES (:nameVal, :emailVal, :phoneVal)") void insertFixedColumns(@Param("nameVal") String name, @Param("emailVal") String email, @Param("phoneVal") String phone); }
这里的关键点:
@Modifying:告诉Spring Data JPA这是修改操作,不是查询@Transactional:修改数据库必须在事务里执行,不然会抛出异常- 用命名参数(
@Param)比位置参数(?1)更清晰,不容易搞混顺序
场景2:动态列名的插入(你的需求场景)
如果你确实需要动态传入列名(比如从Excel表头获取列名),那JPQL就搞不定了,得用原生SQL,而且要特别注意SQL注入风险——直接拼接字符串很危险,一定要做校验。
更安全的方式:用EntityManager手动构建SQL
这种方式可以灵活处理动态列名,同时避免SQL注入:
import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; import java.util.Arrays; import java.util.List; @Repository public class LaganikartaDetailsCustomRepo { @PersistenceContext private EntityManager entityManager; // 先定义允许的列名列表,用来校验传入的表头,防止SQL注入 private static final List<String> ALLOWED_COLUMNS = Arrays.asList("name", "email", "phone", "address"); @Transactional public void insertDynamicColumns(String[] headings, Object[] values) { // 第一步:校验传入的列名是否合法,不在允许列表里的直接抛出异常 for (String heading : headings) { if (!ALLOWED_COLUMNS.contains(heading)) { throw new IllegalArgumentException("非法列名:" + heading); } } // 第二步:构建SQL的列名部分和占位符部分 StringBuilder columnStr = new StringBuilder(); StringBuilder placeholderStr = new StringBuilder(); for (int i = 0; i < headings.length; i++) { if (i > 0) { columnStr.append(", "); placeholderStr.append(", "); } columnStr.append(headings[i]); placeholderStr.append("?").append(i + 1); } // 第三步:拼接完整SQL并执行 String sql = String.format("INSERT INTO LaganikartaDetails (%s) VALUES (%s)", columnStr, placeholderStr); var query = entityManager.createNativeQuery(sql); // 绑定参数,避免SQL注入 for (int i = 0; i < values.length; i++) { query.setParameter(i + 1, values[i]); } query.executeUpdate(); } }
题外话:Excel导入更推荐的方式
如果你是要把Excel数据导入数据库,其实没必要手动写INSERT语句——直接把Excel的每一行转换成LaganikartaDetails实体类对象,然后用JPA的saveAll()批量保存就行,这才是符合JPA设计理念的做法:
// 假设你已经把Excel数据转换成了实体类列表 List<LaganikartaDetails> excelEntities = convertExcelRowsToEntities(excelFile); // 直接批量保存 laganikartaDetailsRepository.saveAll(excelEntities);
这种方式既安全又省心,JPA会自动帮你生成正确的INSERT语句,还能处理事务和异常。
最后再强调几个关键注意点:
- 所有修改操作(INSERT/UPDATE/DELETE)必须加
@Modifying和@Transactional - 用JPQL时操作的是实体类属性,不是数据库列名;要操作数据库表的话,记得加
nativeQuery = true - 动态列名场景一定要做列名校验,用参数绑定传递值,绝对不能直接拼接用户传入的字符串!
内容的提问来源于stack exchange,提问作者Munal Dhakal
相关产品推荐
相关产品推荐

