You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

最新版Spring Data JPA @Modifying与@Transactional使用疑问

关于@Modifying注解的最新版本使用说明

截止目前Spring Data JPA最新稳定版(3.2.x系列),自定义JPQL/原生SQL的增删改操作依然必须加@Modifying注解,没有任何版本移除过这个要求。
这个注解的核心作用是告诉框架:当前@Query标注的方法不是查询操作,底层不要用executeQuery()跑SQL,要切换成executeUpdate()执行修改逻辑。如果你不加,执行这类修改语句时会直接抛InvalidDataAccessApiUsageException,没有例外。

只有一种情况不需要手动加@Modifying:直接调用JpaRepository自带的衍生方法,比如save()、deleteById()、saveAll()这类,框架内部已经做了封装,不需要额外处理。

正确使用规则:

  • @Modifying必须加在Repository接口中对应@Query方法上,不能加在Service层
  • 单独加@Modifying默认只会执行修改语句,不会自动flush持久化上下文的待提交数据,也不会清空一级缓存,方法执行完如果要查询同一条数据,大概率会拿到缓存里的旧值。生产环境推荐配置两个参数:@Modifying(flushAutomatically = true, clearAutomatically = true),前者会在执行修改前把所有待持久化的数据先刷到库,后者执行完修改直接清空EntityManager一级缓存,避免脏读
  • 必须搭配事务支持,不然执行会报错

Repository层正确写法示例:

public interface UserRepository extends JpaRepository<User, Long> {
    // 错误写法:不加@Modifying,执行update/delete类自定义查询直接抛异常
    @Query("update User u set u.status = :status where u.id = :userId")
    void updateUserStatusWrong(Long userId, Integer status);

    // 基础可用写法
    @Modifying
    @Query("update User u set u.status = :status where u.id = :userId")
    int updateUserStatusBasic(Long userId, Integer status);

    // 生产推荐写法:避免缓存不一致问题
    @Modifying(flushAutomatically = true, clearAutomatically = true)
    @Query("update User u set u.status = :status where u.registerTime < :expireTime")
    int batchDisableExpiredUsers(LocalDateTime expireTime, Integer status);
}
@Transactional注解的使用规则

@Transactional没有强制和@Modifying绑定的位置要求,但有明确的场景使用规范:

  1. JpaRepository自带的所有CRUD方法,默认已经在Repository层加了@Transactional,调用时不需要重复加
  2. 自定义的@Modifying修改/删除方法,如果上层Service方法没有加事务,就必须在Repository的方法上补@Transactional,不然框架拿不到数据库连接的事务上下文,执行会报错
  3. 凡是涉及多步数据库写操作的业务逻辑,一律把@Transactional加在Service层的public方法上,不要只加在Repository层——Repository层的事务只覆盖单条数据库操作,没法保证整个业务逻辑的原子性,比如先插用户再插权限,只在Repository的两个save方法上加事务,插权限失败的时候已经插进去的用户不会回滚,会产生脏数据
  4. 纯查询方法可以加@Transactional(readOnly = true),会给框架提示跳过flush、关闭脏检查,查询性能有明显提升,还能避免方法里误触发写操作

Service层实际场景使用示例:

@Service
public class UserService {
    private final UserRepository userRepository;
    private final UserPermissionRepository permissionRepository;

    // 构造注入省略

    /**
     * 多步写操作场景:创建用户+初始化默认权限,必须加事务,任意一步失败全部回滚
     */
    @Transactional(rollbackFor = Exception.class)
    public Long createUserWithDefaultPermission(UserCreateDTO dto) {
        // 第一步:保存用户基础信息
        User user = new User();
        user.setUsername(dto.getUsername());
        user.setStatus(1);
        User savedUser = userRepository.save(user);

        // 第二步:给新用户绑定默认访客权限
        UserPermission permission = new UserPermission();
        permission.setUserId(savedUser.getId());
        permission.setPermissionCode("VISITOR");
        permissionRepository.save(permission);

        // 中间任何一步抛异常,前面插入的用户数据也会回滚,不会出现脏数据
        return savedUser.getId();
    }

    /**
     * 批量封禁过期用户场景:调用Repository层带@Modifying的自定义方法,Service层加事务
     */
    @Transactional(rollbackFor = Exception.class)
    public int batchDisableExpiredUsers() {
        LocalDateTime expireTime = LocalDateTime.now().minusYears(1);
        return userRepository.batchDisableExpiredUsers(expireTime, 0);
    }

    /**
     * 纯查询场景:加只读事务提升性能
     */
    @Transactional(readOnly = true)
    public UserVO getUserDetail(Long userId) {
        // 关联查询用户信息、权限信息,只读事务不会触发flush,性能更好
        return userRepository.findUserWithPermission(userId);
    }
}

新手常踩的坑:

  1. 不要图省事在整个Repository接口类上加@Modifying和@Transactional,只有自定义update/delete方法需要,普通查询加了会导致每次查询都触发不必要的缓存刷新,性能损耗明显
  2. @Transactional不要加在private、protected方法上,Spring的AOP代理扫不到,加了也不生效
  3. 写操作的@Transactional建议手动指定rollbackFor = Exception.class,默认Spring只对RuntimeException和Error回滚,遇到checked异常不会回滚,容易出脏数据

内容的提问来源于stack exchange,提问作者user19254373

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 19:18:48