使hibernate在读取懒加载属性且不在事务中不会抛出LazyInitializationException,而且返回null

hibernate

ToOne(@ManyToOne、@OneToOne)

使之抛出异常是ByteBuddyInterceptor引起的,而ByteBuddyInterceptor是由ByteBuddyProxyFactory构建的,ByteBuddyProxyFactory是由ProxyFactoryFactoryImpl构建的,ByteBuddyProxyFactory是由BytecodeProviderImpl构建的,所以重写这些类

重写BytecodeProviderImpl

public class KuKuBytecodeProvider extends BytecodeProviderImpl {

    @Override
    public ProxyFactoryFactory getProxyFactoryFactory() {
        try {
            Class<BytecodeProviderImpl> clazz = BytecodeProviderImpl.class;
            Field stateField = clazz.getDeclaredField("byteBuddyState");
            stateField.setAccessible(true);
            ByteBuddyState byteBuddyState = (ByteBuddyState) stateField.get(this);
            Field helperField = clazz.getDeclaredField("byteBuddyProxyHelper");
            helperField.setAccessible(true);
            ByteBuddyProxyHelper byteBuddyProxyHelper = (ByteBuddyProxyHelper) helperField.get(this);
            return new KuKuProxyFactoryFactory(byteBuddyState, byteBuddyProxyHelper);
        } catch (Exception e) {
            return null;
        }
    }
}

重写ProxyFactoryFactoryImpl

public class KuKuProxyFactoryFactory extends ProxyFactoryFactoryImpl {

    public KuKuProxyFactoryFactory(ByteBuddyState byteBuddyState, ByteBuddyProxyHelper byteBuddyProxyHelper) {
        super(byteBuddyState, byteBuddyProxyHelper);
    }


    @Override
    public ProxyFactory buildProxyFactory(SessionFactoryImplementor sessionFactory) {
        try {
            Class<ProxyFactoryFactoryImpl> clazz = ProxyFactoryFactoryImpl.class;
            Field field = clazz.getDeclaredField("byteBuddyProxyHelper");
            field.setAccessible(true);
            ByteBuddyProxyHelper byteBuddyProxyHelper = (ByteBuddyProxyHelper) field.get(this);
            return new KuKuByteBuddyProxyFactory(byteBuddyProxyHelper);
        } catch (Exception e) {
            return null;
        }
    }
}

重写ByteBuddyProxyFactory

public class KuKuByteBuddyProxyFactory extends ByteBuddyProxyFactory {

    private static final CoreMessageLogger LOG = messageLogger( ByteBuddyProxyFactory.class );

    public KuKuByteBuddyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) {
        super(byteBuddyProxyHelper);
    }

    @Override
    public HibernateProxy getProxy(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
        try {
            Class<ByteBuddyProxyFactory> clazz = ByteBuddyProxyFactory.class;
            Field entityNameField = clazz.getDeclaredField("entityName");
            entityNameField.setAccessible(true);
            String entityName = (String) entityNameField.get(this);

            Field persistentClassField = clazz.getDeclaredField("persistentClass");
            persistentClassField.setAccessible(true);
            Class persistentClass = (Class) persistentClassField.get(this);

            Field interfacesField = clazz.getDeclaredField("interfaces");
            interfacesField.setAccessible(true);
            Class[]  interfaces = (Class[]) interfacesField.get(this);

            Field getIdentifierMethodField = clazz.getDeclaredField("getIdentifierMethod");
            getIdentifierMethodField.setAccessible(true);
            Method getIdentifierMethod = (Method) getIdentifierMethodField.get(this);

            Field setIdentifierMethodField = clazz.getDeclaredField("setIdentifierMethod");
            setIdentifierMethodField.setAccessible(true);
            Method setIdentifierMethod = (Method) setIdentifierMethodField.get(this);

            Field componentIdTypeField = clazz.getDeclaredField("componentIdType");
            componentIdTypeField.setAccessible(true);
            CompositeType componentIdType = (CompositeType) componentIdTypeField.get(this);

            Field overridesEqualsField = clazz.getDeclaredField("overridesEquals");
            overridesEqualsField.setAccessible(true);
            boolean overridesEquals = (boolean) overridesEqualsField.get(this);

            Field proxyClassField = clazz.getDeclaredField("proxyClass");
            proxyClassField.setAccessible(true);
            Class proxyClass = (Class) proxyClassField.get(this);

            final ByteBuddyInterceptor interceptor = new KuKuByteBuddyInterceptor(
                    entityName,
                    persistentClass,
                    interfaces,
                    id,
                    getIdentifierMethod,
                    setIdentifierMethod,
                    componentIdType,
                    session,
                    overridesEquals
            );

            try {
                final HibernateProxy proxy = (HibernateProxy) proxyClass.getConstructor().newInstance();
                ( (ProxyConfiguration) proxy ).$$_hibernate_set_interceptor( interceptor );

                return proxy;
            }
            catch (NoSuchMethodException e) {
                String logMessage = LOG.bytecodeEnhancementFailedBecauseOfDefaultConstructor(entityName);
                LOG.error( logMessage, e );
                throw new HibernateException( logMessage, e );
            }
            catch (Throwable t) {
                String logMessage = LOG.bytecodeEnhancementFailed(entityName);
                LOG.error( logMessage, t );
                throw new HibernateException( logMessage, t );
            }
        } catch (Exception e) {
            return null;
        }
    }
}

public class KuKuByteBuddyProxyFactory extends ByteBuddyProxyFactory {

    public KuKuByteBuddyProxyFactory(ByteBuddyProxyHelper byteBuddyProxyHelper) {
        super(byteBuddyProxyHelper);
    }

    @Override
    public HibernateProxy getProxy(Object id, SharedSessionContractImplementor session) throws HibernateException {
        try {
            Class<ByteBuddyProxyFactory> clazz = ByteBuddyProxyFactory.class;
            Field entityNameField = clazz.getDeclaredField("entityName");
            entityNameField.setAccessible(true);
            String entityName = (String) entityNameField.get(this);

            Field persistentClassField = clazz.getDeclaredField("persistentClass");
            persistentClassField.setAccessible(true);
            Class persistentClass = (Class) persistentClassField.get(this);

            Field interfacesField = clazz.getDeclaredField("interfaces");
            interfacesField.setAccessible(true);
            Class[]  interfaces = (Class[]) interfacesField.get(this);

            Field getIdentifierMethodField = clazz.getDeclaredField("getIdentifierMethod");
            getIdentifierMethodField.setAccessible(true);
            Method getIdentifierMethod = (Method) getIdentifierMethodField.get(this);

            Field setIdentifierMethodField = clazz.getDeclaredField("setIdentifierMethod");
            setIdentifierMethodField.setAccessible(true);
            Method setIdentifierMethod = (Method) setIdentifierMethodField.get(this);

            Field componentIdTypeField = clazz.getDeclaredField("componentIdType");
            componentIdTypeField.setAccessible(true);
            CompositeType componentIdType = (CompositeType) componentIdTypeField.get(this);

            Field overridesEqualsField = clazz.getDeclaredField("overridesEquals");
            overridesEqualsField.setAccessible(true);
            boolean overridesEquals = (boolean) overridesEqualsField.get(this);

            final ByteBuddyInterceptor interceptor = new KuKuByteBuddyInterceptor(
                    entityName,
                    persistentClass,
                    interfaces,
                    id,
                    getIdentifierMethod,
                    setIdentifierMethod,
                    componentIdType,
                    session,
                    overridesEquals
            );
            Method proxyMethod = clazz.getDeclaredMethod("getHibernateProxy");
            final HibernateProxy instance = (HibernateProxy) proxyMethod.invoke(this);
            final ProxyConfiguration proxyConfiguration = instance.asProxyConfiguration();
            if ( proxyConfiguration == null ) {
                throw new HibernateException( "Produced proxy does not correctly implement ProxyConfiguration" );
            }
            proxyConfiguration.$$_hibernate_set_interceptor( interceptor );
            return instance;
        } catch (Exception e) {
            return null;
        }
    }
}

重写ByteBuddyInterceptor

public class KuKuByteBuddyInterceptor extends ByteBuddyInterceptor {

    public KuKuByteBuddyInterceptor(String entityName, Class persistentClass, Class[] interfaces, Serializable id, Method getIdentifierMethod, Method setIdentifierMethod, CompositeType componentIdType, SharedSessionContractImplementor session, boolean overridesEquals) {
        super(entityName, persistentClass, interfaces, id, getIdentifierMethod, setIdentifierMethod, componentIdType, session, overridesEquals);
    }

    @Override
    public Object intercept(Object proxy, Method thisMethod, Object[] args) throws Throwable {
        try {
            return super.intercept(proxy, thisMethod, args);
        } catch (Exception e) {
            return null;
        }
    }
}

替换Provider

jdk8

BytecodeProviderImpl是在Environment生成的一个常量,所以在开始替换掉BytecodeProviderImpl为我们重写的

try {
   Class<Environment> clazz = Environment.class;
   Field field = clazz.getDeclaredField("BYTECODE_PROVIDER_INSTANCE");
   field.setAccessible(true);
   Field modifiers = field.getClass().getDeclaredField("modifiers");
   modifiers.setAccessible(true);
   modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);//fianl标志位置0
   field.set(null, new KuKuBytecodeProvider());
} catch (Exception e) {
   e.printStackTrace();
}

jdk17

jdk17限制了反射,获取不到Field的属性,所以使用另外的方法,方法来自 https://stackoverflow.com/questions/3301635/change-private-static-final-field-using-java-reflection

    try {
        val clazz = Environment::class.java
        val field = clazz.getDeclaredField("BYTECODE_PROVIDER_INSTANCE")
        field.isAccessible = true
        val lookup = MethodHandles.privateLookupIn(Field::class.java, MethodHandles.lookup())
//        val handle = lookup.findVarHandle(Field::class.java, "modifiers", Int::class.java)
        val setter = lookup.findSetter(Field::class.java, "modifiers", Int::class.java)
        setter.invoke(field, field.modifiers and Modifier.FINAL.inv())
//        handle.set(field, field.modifiers and Modifier.FINAL.inv())
        field.set(null, KuKuBytecodeProvider())

    } catch (e: Exception) {
        e.printStackTrace()
    }

添加vm启动参数--add-opens java.base/java.lang.reflect=ALL-UNNAMED

ToMany(@OneToMany、@ManyToMany)

https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#collections

ToMany的话是一个集合,这个Hibernate提供自定义集合类,我一般都是使用Set,所以以Set为例子

HibernateSet提供的类是PersistentSet,在其执行read方法时,如果是懒加载(ToMany注解默认为懒加载)且不在事务中,会抛出LazyInitializationException异常,但是read方法及其内部真正抛出异常的方法都是final,不能被重写,那就只能重写其调用了read方法的方法了

继承于PersistentSet,重写其方法,在其重写Set方法中处理异常,并返回空Set对应的方法

class KuKuPersistentSet<E>: PersistentSet<E> {
    constructor(session: SharedSessionContractImplementor): super(session)

    constructor(session: SharedSessionContractImplementor, set: Set<E>): super(session, set)

    override fun iterator(): MutableIterator<E> {
        return try {
            super.iterator()
        } catch (e: Exception) {
            mutableSetOf<E>().iterator()
        }
    }

    override fun toArray(): Array<Any> {
        return try {
            super.toArray()
        } catch (e: Exception) {
            hashSetOf<Any>().toArray()
        }
    }

    override fun <A : Any?> toArray(array: Array<out A>): Array<A> {
        return try {
            super.toArray(array)
        } catch (e: Exception) {
            hashSetOf<A>().toArray(array)
        }
    }

    override fun containsAll(elements: Collection<E>): Boolean {
        return try {
            super.containsAll(elements)
        } catch (e: Exception) {
            false
        }
    }

    override fun toString(): String {
        return try {
            super.toString()
        } catch (e: Exception) {
            mutableSetOf<E>().toString()
        }
    }

    override fun equals(other: Any?): Boolean {
        return try {
            super.equals(other)
        } catch (e: Exception) {
            false
        }
    }

    override fun hashCode(): Int {
        return try {
            super.hashCode()
        } catch (e: Exception) {
            0
        }
    }

然后写一个CollectionType,实现接口UserCollectionType,并使用重写的PersistentSet

class KuKuSetType: UserCollectionType{

    override fun getClassification(): CollectionClassification {
        return CollectionClassification.SET
    }

    override fun getCollectionClass(): Class<*> {
        return Set::class.java
    }

    override fun instantiate(
        session: SharedSessionContractImplementor,
        persister: CollectionPersister
    ): PersistentCollection<*> {
        return KuKuPersistentSet<Any>(session)
    }

    override fun instantiate(anticipatedSize: Int): Any {
        return mutableSetOf<Any>()
    }

    override fun wrap(session: SharedSessionContractImplementor, collection: Any): PersistentCollection<*> {
        return KuKuPersistentSet<Any>(session, collection as Set<Any>)
    }

    override fun getElementsIterator(collection: Any): MutableIterator<*> {
        val set = collection as MutableSet<*>
        return set.iterator()
    }

    override fun contains(collection: Any, entity: Any): Boolean {
        val set = collection as MutableSet<*>
        return set.contains(entity)
    }

    override fun indexOf(collection: Any?, entity: Any?): Any {
        val set = collection as MutableSet<*>
        return set.indexOf(entity)
    }

    override fun replaceElements(
        original: Any?,
        target: Any?,
        persister: CollectionPersister?,
        owner: Any?,
        copyCache: MutableMap<Any?, Any?>?,
        session: SharedSessionContractImplementor?
    ): Any {
        val set = target as MutableSet<Any>
        set.clear()
        set.addAll(original as Set<out Any>)
        return set
    }
}

在对应的ToMany属性上使用自定义的CollectionType

    @OneToMany(mappedBy = "byLazy")
    @CollectionType(type = KuKuSetType::class)
    var lazy: Set<LazyEntity> = mutableSetOf()

使用jackson模块

当然,在json序列化的时候这种问题也可以使用jackson的一个模块来实现,这个模块会把懒加载的全部序列化为null

// jakarta包的hibernate
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5-jakarta:2.14.1")
// javax包的hibernate
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:2.14.1")

如果为spring,直接把模块加入到容器中

@Component
class RegisterConfig {
    @Bean
    fun ss(): Hibernate5JakartaModule {
        return Hibernate5JakartaModule()
    }
}
最后修改:2023 年 01 月 30 日
如果觉得我的文章对你有用,请随意赞赏