Hibernate Envers: проблемы полиморфизма

В моей модели предметной области у меня есть абстрактный индикатор сущности, унаследованный от двух конкретных Elementary и Composite.
Абстрактная сущность содержит Фактор сущности, чтобы быть доступной для двух подклассов.
Отношение двунаправленное. Таким образом, сущность Factor содержит экземпляр абстрактного сущности Indicator.
Конечно, реальный экземпляр - элементарный или составной.

@Entity  
@Inheritance(strategy = JOINED)  
public abstract class Indicator implements Serializable {  
    @OneToMany(mappedBy = "indicator")  
    private List<Factor> factors = new ArrayList<Factor>();  
    ...  
}

@Entity
@Audited
public class Factor implements Serializable {
    @ManyToOne(optional = false)
    @JoinColumn(name = "ID_RSK_IND", nullable = false)
    @ForeignKey(name = "FK_FAC__IND")
    private Indicator indicator;
}

@Entity
@Audited
public class Elementary extends Indicator {
    ...
}

@Entity
@Audited
public class Composite extends Indicator {
    ...
}

Я использую Dozer для сопоставления этих сущностей с собой, чтобы "сломать" инструментальные средства гибернации и перенести их на клиентскую сторону (GWT).

С "классическим" Hibernate все работает отлично: Dozer пересек модель бобов, чтобы продублировать ее.

Но когда я использую Envers AuditReader для запроса версионных сущностей, я получаю исключение InstantiationException. Это происходит из-за того, что экземпляр Factor пытается создать экземпляр экземпляра Indicator.

09:36:04,702 - ERROR - org.dozer.MappingProcessor - Field mapping error -->
  MapId: null
  Type: null
  Source parent class: com.sg.rrf.l2r.shared.entity.market.indicator.elementary.Elementary
  Source field name: factors
  Source field type: class org.hibernate.envers.internal.entities.mapper.relation.lazy.proxy.ListProxy
  Source field value: [1]
  Dest parent class: com.sg.rrf.l2r.shared.entity.market.indicator.elementary.Elementary
  Dest field name: factors
  Dest field type: java.util.List
org.dozer.MappingException: java.lang.InstantiationException
    at org.dozer.util.MappingUtils.throwMappingException(MappingUtils.java:82)
    at org.dozer.factory.ConstructionStrategies$ByConstructor.newInstance(ConstructionStrategies.java:280)
    at org.dozer.factory.ConstructionStrategies$ByConstructor.create(ConstructionStrategies.java:245)
    at org.dozer.factory.DestBeanCreator.create(DestBeanCreator.java:65)
    at org.dozer.MappingProcessor.mapCustomObject(MappingProcessor.java:489)
    at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:446)
    at org.dozer.MappingProcessor.mapFromFieldMap(MappingProcessor.java:342)
    at org.dozer.MappingProcessor.mapField(MappingProcessor.java:288)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:248)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:197)
    at org.dozer.MappingProcessor.mapCustomObject(MappingProcessor.java:495)
    at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:446)
    at org.dozer.MappingProcessor.addOrUpdateToList(MappingProcessor.java:776)
    at org.dozer.MappingProcessor.addOrUpdateToList(MappingProcessor.java:850)
    at org.dozer.MappingProcessor.mapListToList(MappingProcessor.java:686)
    at org.dozer.MappingProcessor.mapCollection(MappingProcessor.java:541)
    at org.dozer.MappingProcessor.mapOrRecurseObject(MappingProcessor.java:434)
    at org.dozer.MappingProcessor.mapFromFieldMap(MappingProcessor.java:342)
    at org.dozer.MappingProcessor.mapField(MappingProcessor.java:288)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:248)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:197)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:187)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:124)
    at org.dozer.MappingProcessor.map(MappingProcessor.java:119)
    at org.dozer.DozerBeanMapper.map(DozerBeanMapper.java:120)
    at com.sg.rrf.l2r.server.audit.AuditTransactionalBean.getEntityForRevision(AuditTransactionalBean.java:30)
    at com.sg.rrf.l2r.server.audit.AuditTransactionalBean$$FastClassByCGLIB$$78958945.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
    at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:713)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:98)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:262)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:52)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:52)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:58)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:646)
    at com.sg.rrf.l2r.server.audit.AuditTransactionalBean$$EnhancerByCGLIB$$36312869.getEntityForRevision(<generated>)
    at com.sg.rrf.l2r.server.audit.AuditServiceImpl.getEntityForRevision(AuditServiceImpl.java:37)
    at com.sg.rrf.l2r.server.market.indicator.audit.IndicatorAuditServiceImplTest.assertElementaryValues(IndicatorAuditServiceImplTest.java:120)
    at com.sg.rrf.l2r.server.market.indicator.audit.IndicatorAuditServiceImplTest.testAuditElementary(IndicatorAuditServiceImplTest.java:105)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:232)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:71)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:175)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
Caused by: java.lang.InstantiationException
    at sun.reflect.InstantiationExceptionConstructorAccessorImpl.newInstance(InstantiationExceptionConstructorAccessorImpl.java:30)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at org.dozer.factory.ConstructionStrategies$ByConstructor.newInstance(ConstructionStrategies.java:276)
    ... 74 more

Это происходит из-за того, что Envers использует Lazy loading, даже если указан Eager?

PS: Конечно, мне нужна двунаправленная навигация от индикатора к фактору.

2 ответа

Решение

Поле factors была предпринята попытка сопоставления с полем factors нового объекта типа Elementary, но свойство здесь является интерфейсом List, для которого конкретный тип не известен.

Отображение этого поля работает для реальных типов моделей домена, но не для прокси-серверов гибернации.

Вы инициализируете factors с ArrayList? Кажется, в противном случае отображение не Энверса не будет работать.

Это может быть связано с некоторыми ограничениями, которые dozer имеет при отображении универсальных типов, таких как списки, из-за того, что универсальная информация недоступна во время выполнения, поэтому dozer не знает тип объектов, содержащихся в списке, поэтому он пытается вывести его из содержимого исходной коллекции.

Согласно документации Dozer:

Если Подсказка не указана для поля назначения, целевая Коллекция будет заполнена объектами того же типа, что и элементы в Коллекции src.

Так что решить эту проблему можно несколькими способами:

1 - Поместите подсказку бульдозера на отображение этого свойства, чтобы указать тип цели, таким образом, он не будет пытаться вывести его:

<field>
  <a>factors</a> 
  <b>factors</b> 
  <b-hint>your.target.class.Here</b-hint> 
</field>

2 - напишите и примените к этому свойству пользовательский конвертер Dozer, где вы отображаете этот список вручную, это всегда будет работать (используйте специальный API-интерфейс конвертера, основанный на обобщениях).

3 - Избегайте необходимости отображения и Dozer в целом, решая LazyInitialization во время сериализации другим способом: убедитесь, что сеанс гибернации остается открытым вплоть до сериализации запроса, используя Open Session In View, если он находится в Spring приложение, или подобное, если иное.

Один из этих способов должен решить эту проблему, если вы все еще сомневаетесь, вы всегда можете:

  • опубликовать карту бульдозера и код для типа factors?

  • С помощью отладчика вы можете установить точку останова в строке 280 ConstructionStrategies, чтобы увидеть, что представляет собой абстрактный класс или интерфейс, который он пытается создать.

Вот мое отображение Dozer:

protected final static DozerBeanMapper MAPPER = new DozerBeanMapper();
static {
    BeanMappingBuilder builder = new BeanMappingBuilder() {
        @Override
        protected void configure() {
            mapping(Elementary.class, Elementary.class);
            mapping(Composite.class, Composite.class);
        }
    };

    MAPPER.addMapping(builder);
}

Благодаря этому отображению Dozer может отображать бины Hibernate-прокси.
Но с этим отображением Dozer не может отобразить бины-прокси Envers.

Я не думаю, что проблема связана с отображением Коллекции. Когда я помещаю точку останова в Instanciation, я нахожусь в методе org.dozer.factory.ConstructionStrategies.newInstance(Class clazz). Параметром clazz является Indicator.class.
Кроме того, предположим, что я не использую Dozer и я называю этот код в моем DAO:

Indicator indicator = elementary.getFactors().get(0).getIndicator();
System.out.println(indicator.getClass().getSimpleName());

Он печатает "Индикатор _$$_javassist_6".

Тест

indicator instanceof Elementary 

или же

indicator instanceof Composite 

вернуть ложь.

Те же самые тесты с классическим Hibernate (без Envers) возвращают true для одного из них и печатают правильное имя класса (даже с FetchType.LAZY в атрибуте индикатора ManyToOne!)

Другие вопросы по тегам