问题描述
我使用 Spring Data JPA 和 @Embedabble 创建复合键。 一个Base类BaseDate会被所有Entity扩展。
sysCreationDate 将在插入时生成(非空且不可更新)
保存用户第一次工作正常,但这里有 3 个问题-
- 在第二次调用期间,它不是抛出异常而是更新 sysUpdateDate 和 userType
- 在第一次调用 sysUpdateDate 时不为 null (@UpdateTimestamp)
- 在第二次调用响应期间,它将 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
。默认情况下,此方法持久化(插入)一个新对象仅当主键为null
或0
时;否则,它合并(更新)到现有对象中。你总是有一个主键集,所以它总是调用 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));
}
可用的解决方案是:
- 直接致电
EntityManager
。 - 从 Spring 实现 Persistable 接口并实现您自己的
isNew
以通知 Spring 存储库您的对象是新的还是已经持久化了。 - 对您的逻辑键使用代理主键(
long
、@GeneratedValue
)和唯一约束
我会推荐第三种解决方案(带有代理主键),因为它很简单并且具有更好的可扩展性。例如,添加引用实体的外键会更容易。
还有一个解决方案是先调用find
,只是为了检查对象是否存在于数据库中。但是这种方案容易出现race问题(两个并发REST请求创建一个新对象,都调用find
,都收到null,因此都保存,一个数据丢失/覆盖)。
对于@UpdateTimestamp,您已经有评论,对于@CreationTimestamp null,请发布您的控制器。