解决JPA save null的问题
- 解决JPA save null的问题
- 利用hibernate本身的机制来重写部分逻辑
解决JPA save null的问题
如果调用jpa的save方法进行更新, 会将null字段也更新到数据库中, 这要求你在进行更新的时候, 必须先进行查询, 然后将要更新的字段数据set到实体类上, 最后save, 在事务中甚至不需要调用save即可更新到数据库.
例如:
public User update(Long id, User user) {
var entity = userRepository.findById(id).orElseThrow();
var optional = Optional.of(user);
optional.map(User::getUsername).ifPresent(entity::setUsername);
optional.map(User::getDepartment).ifPresent(entity::setDepartment);
// ...set其它值
userRepository.save(entity);
}
这样更新更加规范, 是一种更健壮的编码方式.
缺点也很明显: 每次更新都要先查询, 查询后还要手动调用实体类的setter来更新对应的数据. 很难为不同的类提取公共代码出来.
为了简化这样的过程, 部分框架可能利用反射. 例如利用hutool的BeanUtils忽略复制null字段.
public User update(Long id, User user) {
var entity = userRepository.findById(id).orElseThrow();
BeanUtil.copyProperties(user, entity, CopyOptions.create().setIgnoreNullValue(true));
userRepository.save(entity);
}
反射的缺点也很明显, 不可避免的会影响到性能, 个人也不太喜欢在业务代码中使用反射.
利用hibernate本身的机制来重写部分逻辑
hibernate为定义orm提供了很多注解.
例如@Column可以配置与列的映射关系, 其中nullable可以配置该列是否为空.
然而即便配置了@Column(nullable = false), 该配置也不会在save时发挥任何作用, 该字段为null的数据仍然会被更新到数据库然后报错.
从语义上说, 当指定@Column(nullable = false)时, 那么字段值为空就不更新到数据库, 这是非常符合直觉的理解.
利用hibernate提供的一些配置, 可以自己重写部分处理该部分数据的逻辑来实现这样的逻辑.
创建一个CustomerDefaultMergeEventListener, 继承自DefaultMergeEventListener
public class CustomDefaultMergeEventListener extends DefaultMergeEventListener {
}
然后创建一个CustomIntegratorProvider继承自IntegratorProvider
public class CustomIntegratorProvider implements IntegratorProvider {
@Override
public List getIntegrators() {
return List.of(
new Integrator() {
@Override
public void integrate(
Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
var serviceRegistryService = serviceRegistry.getService(EventListenerRegistry.class);
// 直接设置EventType.MERGE的listener是自定义的CustomDefaultMergeEventListener
serviceRegistryService.setListeners(
EventType.MERGE, CustomDefaultMergeEventListener.class);
}
@Override
public void disintegrate(
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {}
});
}
}
然后在在springboot配置文件中添加配置:
spring.jpa.properties.hibernate.integrator_provider=com.wymc.demo.jpa.CustomerDefaultMergeEventListener
此时配置已经可用, 但逻辑还没重写. 编辑CustomDefaultMergeEventListener添加以下代码:
public class CustomDefaultMergeEventListener extends DefaultMergeEventListener {
@Override
protected void copyValues(
EntityPersister persister,
Object entity,
Object targetEntity,
SessionImplementor session,
Map copyCache) {
var original = persister.getPropertyValues(entity);
var target = persister.getPropertyValues(targetEntity);
var types = persister.getPropertyTypes();
// 获取列是否可以为空, 即@Column中的nullable配置
var propertyNullability = persister.getPropertyNullability();
Object[] copied = new Object[original.length];
for (int i = 0; i < types.length; i++) {
// 修改此处的判断条件, 你可以不验证@Column注解的nullable
// 下面这种用法既可以给可以为null的字段设置null, 又不会更新不能为null的字段
if (!propertyNullability[i] && original[i] == null) {
// 如果nullable为false, 并且传入的值为null, 则使用原数据, 即不更新null
copied[i] = target[i];
} else
// 下面这部分是原有的代码, 不做改动
if (original[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY
|| original[i] == PropertyAccessStrategyBackRefImpl.UNKNOWN) {
copied[i] = target[i];
} else if (target[i] == LazyPropertyInitializer.UNFETCHED_PROPERTY) {
copied[i] = types[i].replace(original[i], null, session, targetEntity, copyCache);
} else {
copied[i] = types[i].replace(original[i], target[i], session, targetEntity, copyCache);
}
}
persister.setPropertyValues(targetEntity, copied);
}
}