@CascadeType.MERGE子实体不生效?多对多关联重复存储问题
我在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
相关产品推荐
相关产品推荐

