Включить поведение без прокси для всех FetchType.LAZY не-коллекций по умолчанию в Hibernate

При использовании стандартных аннотаций JPA вы можете указать FetchType.LAZY для полей без сбора (т.е. @ManyToOne и @OneToOne). Кажется, Hibernate внутренне использует "прокси" в этом случае. Но выбор прокси-сервера имеет свои проблемы с наследованием, и я думаю, что лучше использовать получение не-прокси в сочетании с инструментами байт-кода. К сожалению, Hibernate по-прежнему требует либо указать "no-proxy" в файле hbm, либо использовать аннотацию @LazyToOne для Hibernate.

Мой вопрос: поддерживает ли Hibernate параметр конфигурации, чтобы использовать стратегию выборки без прокси для всех не-коллекционных полей, которые FetchType.LAZY?

Вот что мне нужно для этого: я хотел бы использовать только аннотации JPA в большинстве случаев, с одной стороны. С другой стороны, я бы хотел избежать проблем с наследованием и ленивыми полями. И мне не нравится идея обертывания всего в интерфейсах, потому что я использую DDD в своем текущем проекте, поэтому я думаю, что в моей модели домена не должно существовать никаких шаблонных мусора, а только чистая бизнес-логика.

У меня есть идея плохого обходного пути: с помощью модификации байт-кода я добавляю @LazyToOne аннотацию везде @ManyToOne. Но я бы предпочел встроенную функцию Hibernate, если она существует.


Здесь (хорошо известная) проблема с извлечением прокси, чтобы сделать вещи немного яснее:

@Entity @DiscriminatorColumn("t") @DiscriminatorValue("")
public abstract class A {
    @Id private Integer id;
}

@Entity @DiscriminatorValue("B")
public abstract class B extends A {
}

@Entity @DiscriminatorValue("C")
public abstract class C extends A {
}

@Entity public class D {
    @Id private Integer id;
    @ManyToOne(fetch = FetchType.LAZY) private A a;
    public A getA() {
        return a;
    }
}

подготовить:

D d = new D();
C c = new C();
d.setA(c);
em.persist(d);

и утверждение об ошибке (в другой EM, другая транзакция):

D d = em.createQuery("select d from D d", D.class).getSingleResult();
List<C> cs = em.createQuery("select c from C c", C.class).getResultList();
assert d.getA() instanceof C;
assert d.getA() == cs.get(0);

Вот что я сделал бы, чтобы зафиксировать вышеприведенные утверждения:

@Entity public class D {
    @Id private Integer id;
    @ManyToOne(fetch = FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY)
    private A a;
    public A getA() {
        return a;
    }
}

И я не хочу, чтобы по умолчанию было включено то же самое, без аннотации @LazyToOne.

Ответы

Ответ 1

Хорошо, я отказался от ответа. Я внимательно изучил исходный код Hibernate и сделал вывод, что сам Hibernate не имеет никакого свойства для достижения того, чего я хочу. Но я придумал небольшой грязный хак, который дает мне именно то, что я хочу. Итак, вот он:

public class DirtyHackedHibernatePersistence extends HibernatePersistence {
    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EntityManagerFactory createEntityManagerFactory(String persistenceUnitName,
            Map properties) {
        properties.put(AvailableSettings.PROVIDER, HibernatePersistence.class.getName());
        Ejb3Configuration cfg = new Ejb3Configuration().configure(persistenceUnitName, properties);
        if (cfg == null) {
            return null;
        }
        cfg.buildMappings();
        hackConfiguration(cfg);
        return cfg.buildEntityManagerFactory();
    }

    @Override
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public EntityManagerFactory createContainerEntityManagerFactory(PersistenceUnitInfo info,
            Map properties) {
        properties.put(AvailableSettings.PROVIDER, HibernatePersistence.class.getName());
        Ejb3Configuration cfg = new Ejb3Configuration().configure(info, properties);
        if (cfg == null) {
            return null;
        }
        cfg.buildMappings();
        hackConfiguration(cfg);
        return cfg.buildEntityManagerFactory();
    }

    private void hackConfiguration(Ejb3Configuration cfg) {
        System.out.println("Hacking configuration");
        String noProxyByDefault = cfg.getProperties().getProperty("hibernate.hack.no-proxy-by-default", "false");
        if (Boolean.parseBoolean(noProxyByDefault)) {
            Iterator<?> iter = cfg.getClassMappings();
            while (iter.hasNext()) {
                hackClass((PersistentClass)iter.next());
            }
        }
    }

    private void hackClass(PersistentClass classMapping) {
        Iterator<?> iter = classMapping.getPropertyIterator();
        while (iter.hasNext()) {
            Property property = (Property)iter.next();
            if (property.getValue() instanceof ToOne) {
                ToOne toOne = (ToOne)property.getValue();
                if (toOne.isLazy()) {
                    toOne.setUnwrapProxy(true);
                }
            }
        }
    }
}

также должен существовать ресурс с именем META-INF/services/javax.persistence.spi.PersistenceProvider, содержащий одну строку с именем класса.

Чтобы использовать этот хак, вы должны указать следующее в persistence.xml:

<provider>packagename.DirtyHackedHibernatePersistence</provider>
<properties>
   <property name="hibernate.hack.no-proxy-by-default" value="true"/>
</properties>

Полный пример доступен здесь.

Обратите внимание, что если вы удаляете свойство hibernate.hack.no-proxy-by-default и перестраиваете проект, оба утверждения ломаются.

Также я отправлю запрос функции команде Hibernate.