如何在自定义Hibernate BasicType中访问实体标识符?
在Hibernate 5.6自定义BasicType中访问实体标识符的解决方案
一、写入操作:在nullSafeSet中获取实体ID
要在nullSafeSet方法中拿到实体ID,需要借助Hibernate的事件监听器和ThreadLocal传递上下文:
- 定义ThreadLocal存储当前实体ID
public class EntityIdHolder { private static final ThreadLocal<Object> CURRENT_ENTITY_ID = new ThreadLocal<>(); public static void setCurrentEntityId(Object id) { CURRENT_ENTITY_ID.set(id); } public static Object getCurrentEntityId() { return CURRENT_ENTITY_ID.get(); } public static void clear() { CURRENT_ENTITY_ID.remove(); } }
- 实现PreInsert/PreUpdate事件监听器
public class EntityIdTrackingListener implements PreInsertEventListener, PreUpdateEventListener { @Override public boolean onPreInsert(PreInsertEvent event) { Object entityId = event.getSession().getIdentifier(event.getEntity()); EntityIdHolder.setCurrentEntityId(entityId); return false; // 不拦截原有逻辑,继续执行 } @Override public boolean onPreUpdate(PreUpdateEvent event) { Object entityId = event.getSession().getIdentifier(event.getEntity()); EntityIdHolder.setCurrentEntityId(entityId); return false; } }
- 注册监听器到SessionFactory
SessionFactory sessionFactory = new Configuration() .addAnnotatedClass(YourEntity.class) // 替换为你的实体类 .registerTypeOverride(new MyType()) // 注册自定义类型 .setListener("pre-insert", new EntityIdTrackingListener()) .setListener("pre-update", new EntityIdTrackingListener()) .buildSessionFactory();
- 在
MyType的nullSafeSet中使用ID
public class MyType extends AbstractStandardBasicType<MyCustomType> { // 构造方法及其他实现... @Override public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session) throws HibernateException { try { Object entityId = EntityIdHolder.getCurrentEntityId(); // 这里用entityId计算自定义类型的数据 if (entityId != null) { // 你的业务逻辑,比如根据ID处理value } // 执行原有nullSafeSet逻辑 super.nullSafeSet(st, value, index, settable, session); } finally { // 必须清理ThreadLocal,避免内存泄漏 EntityIdHolder.clear(); } } }
二、读取操作:在wrap方法中获取实体ID
wrap方法是在从数据库读取数据封装为自定义类型时调用的,此时实体ID已被Hibernate加载,可通过两种方式获取:
方式1:通过@PostLoad注解(简单直接)
在实体类中添加@PostLoad方法,将ID传递给自定义类型实例:
- 修改
MyCustomType,添加ID存储字段
public class MyCustomType { private Object entityId; // 你的其他字段和方法 public void setEntityId(Object entityId) { this.entityId = entityId; } public Object getEntityId() { return entityId; } }
- 在实体类中添加
@PostLoad方法
@Entity public class YourEntity { @Id private Long id; @Type(type = "com.example.MyType") private MyCustomType customField; @PostLoad public void afterLoad() { if (customField != null) { customField.setEntityId(id); } } // getter/setter... }
这种方式下,wrap方法本身无法直接拿到ID,但自定义类型实例在实体加载完成后会被注入ID,后续使用时可直接调用getEntityId()。
方式2:通过PostLoadEventListener(通用型)
如果需要适配多个实体类,可使用事件监听器统一处理:
- 实现PostLoadEventListener
public class PostLoadEntityIdInjector implements PostLoadEventListener { @Override public void onPostLoad(PostLoadEvent event) { Object entity = event.getEntity(); Object entityId = event.getSession().getIdentifier(entity); // 反射获取实体中的自定义类型字段,注入ID try { Method getCustomField = entity.getClass().getMethod("getCustomField"); Object fieldValue = getCustomField.invoke(entity); if (fieldValue instanceof MyCustomType) { ((MyCustomType) fieldValue).setEntityId(entityId); } } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Failed to inject entity ID into MyCustomType", e); } } }
- 注册监听器到SessionFactory
SessionFactory sessionFactory = new Configuration() .addAnnotatedClass(YourEntity.class) .registerTypeOverride(new MyType()) .setListener("post-load", new PostLoadEntityIdInjector()) .buildSessionFactory();
注意事项
- ThreadLocal必须在使用后清理,否则会引发内存泄漏,尤其是在使用线程池的场景下。
- 如果实体使用生成式ID(如IDENTITY),在PreInsert事件中已经可以获取到生成后的ID,Hibernate 5.6会在插入前生成并设置ID。
- 自定义类型是单例模式,不能存储实例级别的状态,所有上下文数据必须通过ThreadLocal或实体关联传递。
内容的提问来源于stack exchange,提问作者François Rosière
相关产品推荐
相关产品推荐

