使用Hibernate时如何禁止更新指定数据库字段?
为什么@Column(updatable=false)没达到你的预期
你使用@Column(updatable = false)的实际效果是:Hibernate生成UPDATE语句时会排除该字段,也就是说数据库里的ins字段并不会被更新。你的测试之所以看起来“更新成功”,是因为Hibernate的一级缓存(Session缓存)里的实体对象已经被你修改,后续调用findById会直接从缓存返回修改后的对象,而非重新查询数据库。
你可以验证这一点:在测试的save之后调用em.clear()(清除缓存),再执行findById,就能看到数据库里的ins字段并未被修改。
实现“修改已持久化实体的该字段后保存抛异常”的方案
Hibernate本身没有原生支持这种“修改即抛异常”的约束,需要自定义实现,以下是几种可行方案:
方案1:在setter方法中添加检查
直接修改实体的setter,当实体已被持久化(即主键ID不为空)时,调用setter就抛出异常:
public void setIns(String ins) { // 假设你的AbstractAuditingEntity提供了getId()方法 if (getId() != null) { throw new IllegalStateException("ins字段为不可更新字段,禁止修改"); } this.ins = ins; }
这种方式简单直接,新创建的实体(未保存,ID为空)可以正常设置该字段,已持久化的实体调用setter会立即抛出异常。
方案2:使用Hibernate事件监听器
通过Hibernate的PreUpdateEventListener拦截更新操作,检查字段是否被修改,若修改则抛出异常:
public class InsUpdatePreventListener implements PreUpdateEventListener { @Override public boolean onPreUpdate(PreUpdateEvent event) { Object entity = event.getEntity(); if (entity instanceof Application) { Application app = (Application) entity; // 获取字段在实体属性中的索引 int propertyIndex = event.getPersister().getPropertyIndex("ins"); String oldValue = (String) event.getOldState()[propertyIndex]; String newValue = app.getIns(); // 检查字段是否被修改 if (oldValue != null && !oldValue.equals(newValue)) { throw new IllegalStateException("禁止更新已持久化实体的ins字段"); } } return false; // 返回false表示不拦截后续操作,仅抛异常 } }
然后需要将该监听器注册到Hibernate配置中,比如在Spring Boot中可以通过HibernatePropertiesCustomizer实现:
@Component public class HibernateListenerConfig implements HibernatePropertiesCustomizer { @Override public void customize(Map<String, Object> hibernateProperties) { EventListenerRegistry registry = (EventListenerRegistry) hibernateProperties.get("hibernate.event.registry"); registry.appendListeners(EventType.PRE_UPDATE, new InsUpdatePreventListener()); } }
方案3:自定义Spring Data JPA Repository实现
如果使用Spring Data JPA,可以自定义Repository的save方法,在保存前检查字段是否被修改:
首先定义自定义Repository接口:
public interface ApplicationRepositoryCustom { <S extends Application> S save(S entity); }
然后实现该接口:
public class ApplicationRepositoryImpl implements ApplicationRepositoryCustom { @PersistenceContext private EntityManager em; @Override public <S extends Application> S save(S entity) { if (entity.getId() != null) { // 查询数据库中的原始实体 Application original = em.find(Application.class, entity.getId()); if (!original.getIns().equals(entity.getIns())) { throw new IllegalStateException("ins字段不可更新,修改操作被阻止"); } } return em.merge(entity); } }
最后让你的ApplicationRepository继承自定义接口:
public interface ApplicationRepository extends JpaRepository<Application, Long>, ApplicationRepositoryCustom { }
总结
@Column(updatable=false)仅控制SQL生成,不会阻止实体属性修改,也不会抛出异常;- 要实现“修改已持久化实体的指定字段后保存抛异常”的需求,需要通过自定义setter、Hibernate事件监听器或Spring Data自定义Repository来实现。
内容的提问来源于stack exchange,提问作者user1436883

