@Embeddable 复合键不会在 Spring 数据 JPA

问题描述

我使用 Spring Data JPA 和 @Embedabble 创建复合键。 一个Base类BaseDate会被所有Entity扩展。

sysCreationDate 将在插入时生成(非空且不可更新)

保存用户第一次工作正常,但这里有 3 个问题-

  1. 在第二次调用期间,它不是抛出异常而是更新 sysUpdateDate 和 userType
  2. 在第一次调用 sysUpdateDate 时不为 null (@UpdateTimestamp)
  3. 在第二次调用响应期间,它将 sysCreationDate 返回为 null

以下是代码- 可嵌入类

@Embeddable
public class CompKey implements Serializable {  
    @Column(name ="USER_ID")
    private String userId;
    @Column(name ="USER_NAME")
    private String userName;    
    public CompKey(String userId,String userName) {
        super();
        this.userId = userId;
        this.userName = userName;
    }
    public CompKey() {
        super();
    }
    //Getters /Setters /Equual and Hashcode
}

日期的基类

@MappedSuperclass
public abstract class BaseDate {    
    @CreationTimestamp
    @Column(name = "SYS_CREATION_DATE",updatable=false,nullable=false)
    private Calendar sysCreationDate;
    @Column(name = "SYS_UPDATE_DATE")
    @UpdateTimestamp
    private Calendar sysUpdateDate; 
    public BaseDate(Calendar sysCreationDate,Calendar sysUpdateDate) {
        this.sysCreationDate = sysCreationDate;
        this.sysUpdateDate = sysUpdateDate;
    }
    public BaseDate() {
    }   
    //Getters and Setters
}

实体类

@Entity
public class User extends BaseDate{ 
    @Column(name = "USER_TYPE")
    private String userType;
    @EmbeddedId
    private CompKey compkey;
    
    public User() {
        super();
    }
    public User(Calendar sysCreationDate,Calendar sysUpdateDate,String userType,CompKey compkey) {
        super(sysCreationDate,sysUpdateDate);
        this.userType = userType;
        this.compkey = compkey;
    }   
    //Getters and setters
}

回购 -

@Repository
public interface UserRepo extends CrudRepository<User,CompKey> {
}

服务和控制器 -

@Service
public class UserService {
    @Autowired
    UserRepo userRepo;

    public User saveUser(User user) {
        
        return userRepo.save(user);
    }

    public Optional<User> getUser(CompKey key) {
        
        return userRepo.findById(key);
    }
}

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserService userService;
    
    @PostMapping("/save")
    public User saveUser(@RequestBody User user) {
        
        return userService.saveUser(user);
    }
    
    @GetMapping("/get")
    public Optional<User> getUser(@RequestBody CompKey key) {
        
        return userService.getUser(key);
    }

输入 -

{
    "userType": "K","compkey": {
        "userId": "1002","userName": "ASDF"
    }
}

输出 1)-

{
    "sysCreationDate": "2021-01-08T18:09:28.802+00:00","sysUpdateDate": "2021-01-08T18:09:28.802+00:00","userType": "K","userName": "ASDF"
    }
{
    "sysCreationDate": null,"sysUpdateDate": "2021-01-08T18:10:43.206+00:00","userName": "ASDF"
    }
}

提前致谢

解决方法

不会抛出完整性约束违反异常,因为您的 Spring 存储库只是更新了对象。

Spring 存储库不区分插入和更新。只有一种通用方法——save。默认情况下,此方法持久化(插入)一个新对象仅当主键为null0时;否则,它合并(更新)到现有对象中。你总是有一个主键集,所以它总是调用 merge,它会第二次更新。

它在 SimpleJpaRepository 中的基本实现如下所示:

    @Transactional
    public <S extends T> S save(S entity) {
        Assert.notNull(entity,"Entity must not be null.");
        if (this.entityInformation.isNew(entity)) {
            this.em.persist(entity);
            return entity;
        } else {
            return this.em.merge(entity);
        }
    }

关键部分是 isNew 方法,其默认实现如下:

    public boolean isNew(T entity) {

        ID id = getId(entity);
        Class<ID> idType = getIdType();

        if (!idType.isPrimitive()) {
            return id == null;
        }

        if (id instanceof Number) {
            return ((Number) id).longValue() == 0L;
        }

        throw new IllegalArgumentException(String.format("Unsupported primitive id type %s!",idType));
    }

可用的解决方案是:

  1. 直接致电EntityManager
  2. 从 Spring 实现 Persistable 接口并实现您自己的 isNew 以通知 Spring 存储库您的对象是新的还是已经持久化了。
  3. 对您的逻辑键使用代理主键(long@GeneratedValue)和唯一约束

我会推荐第三种解决方案(带有代理主键),因为它很简单并且具有更好的可扩展性。例如,添加引用实体的外键会更容易。

还有一个解决方案是先调用find,只是为了检查对象是否存在于数据库中。但是这种方案容易出现race问题(两个并发REST请求创建一个新对象,都调用find,都收到null,因此都保存,一个数据丢失/覆盖)。

对于@UpdateTimestamp,您已经有评论,对于@CreationTimestamp null,请发布您的控制器。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...