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

@CascadeType.MERGE子实体不生效?多对多关联重复存储问题

解决JPA多对多关联中Authority实体重复存储的问题

我在User和Authority两个实体间实现@ManyToMany关联时遇到了问题:保存拥有READ权限的userA和userB时,READ权限会被重复插入到Authority表中,或者出现 transient 对象异常。

现有表结构

CREATE TABLE user (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    password VARCHAR(50) NOT NULL,
    enabled BOOLEAN DEFAULT TRUE
);


CREATE TABLE authority (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    authority VARCHAR(50) UNIQUE NOT NULL
);

CREATE TABLE user_authority (
    user_id BIGINT,
    authority_id BIGINT,
    PRIMARY KEY (user_id, authority_id),
    FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE,
    FOREIGN KEY (authority_id) REFERENCES authority(id) ON DELETE CASCADE
);

实体类与业务代码

UserEntity

@Entity
@Data
@Builder
@Table(name = "user", schema = "user_database")
@NoArgsConstructor
@AllArgsConstructor
public class UserEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true)
    private String username;

    private String password;

    private boolean enabled;

    @ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE, CascadeType.ALL})
    @JoinTable(
            name = "user_authority",
            joinColumns = @JoinColumn(name = "user_id"),
            inverseJoinColumns = @JoinColumn(name = "authority_id")
    )
    private Set<AuthorityEntity> authority;
}

AuthorityEntity

@Data
@Entity
@Builder
@Table(name = "authority", schema = "user_database")
@NoArgsConstructor
@AllArgsConstructor
public class AuthorityEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true)
    private String authority;

    @ToString.Exclude
    @ManyToMany(mappedBy = "authority")
    private Set<UserEntity> users;
}

Service层代码

public Response<String> createUser(UserEntity user) {
    UserEntity saveUserEntity = userRepository.save(user);
    log.info(saveUserEntity.toString());
    return new Response<>(200, "SUCCESS", saveUserEntity.toString());
}

Repository层代码

@Repository
public interface UserRepository extends JpaRepository<UserEntity, Long> {
    UserEntity findByUsername(String username);
}

期望结果

USER表

id, username, password
1,  userA,    1234
2,  userB,    4321

AUTHORITY表

id, authority
1,   READ
2,   WRITE

USER_AUTHORITY表

user_id, authority_id
1,        1
1,        2
2,        1

已尝试的方案及问题

  • 使用CascadeType.ALL:保存userA后,保存userB时抛出:

Duplicate entry 'READ' for key 'authority'

  • 使用{CascadeType.MERGE, CascadeType.ALL}:结果同上
  • 使用CascadeType.MERGE:保存userA后,保存userB时抛出:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.example.AuthService.repository.entity.AuthorityEntity

解决方案

问题核心在于:创建userB时传入的READ权限是未持久化的新对象,JPA无法识别它与已存在的READ是同一个实体。需要先检查权限是否存在,存在则复用,不存在再创建。

步骤1:添加AuthorityRepository查询方法

@Repository
public interface AuthorityRepository extends JpaRepository<AuthorityEntity, Long> {
    Optional<AuthorityEntity> findByAuthority(String authority);
}

步骤2:修改Service层逻辑处理权限复用

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private AuthorityRepository authorityRepository;

    public Response<String> createUser(UserEntity user) {
        Set<AuthorityEntity> processedAuthorities = new HashSet<>();
        for (AuthorityEntity auth : user.getAuthority()) {
            // 查询已有权限,不存在则保存新权限
            AuthorityEntity existingAuth = authorityRepository.findByAuthority(auth.getAuthority())
                    .orElseGet(() -> authorityRepository.save(auth));
            processedAuthorities.add(existingAuth);
        }
        user.setAuthority(processedAuthorities);
        
        UserEntity saveUserEntity = userRepository.save(user);
        log.info(saveUserEntity.toString());
        return new Response<>(200, "SUCCESS", saveUserEntity.toString());
    }
}

步骤3:调整UserEntity的CascadeType

移除不必要的级联操作,避免JPA自动处理权限时引发冲突:

@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
        name = "user_authority",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "authority_id")
)
private Set<AuthorityEntity> authority;

原理说明

  • 通过findByAuthority提前查询数据库,复用已存在的权限实体,避免重复插入
  • 移除User到Authority的级联操作,将权限的持久化逻辑交由Service层控制,避免级联操作导致的重复键或 transient 对象异常
  • 最终实现用户与权限的正确关联,同时保证Authority表中无重复数据

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 03:48:10