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

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

@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 скрещивает модель beans, чтобы дублировать ее.

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

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 использует отложенную загрузку, даже если указано Eager?

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


person user3154016    schedule 02.01.2014    source источник
comment
У вас есть трассировка стека исключения, или, если она недоступна, вы можете поставить точку останова в конструкторах спящего режима InstantiationException и использовать окно отладчика для копирования полного стека?   -  person Angular University    schedule 03.01.2014


Ответы (2)


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

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

Вы инициализируете factors с помощью ArrayList? Похоже, что иначе отображение не Envers не сработало бы.

Это может быть связано с некоторыми ограничениями, которые 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 во время сериализации другим способом: убедитесь, что сеанс гибернации остается открытым на всем пути вплоть до сериализации запроса, используя Открыть сеанс в представлении, если в приложении Spring, или аналогичный, если в противном случае.

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

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

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

person Angular University    schedule 06.01.2014
comment
Также, если вы изучаете открытую сессию, чтобы избежать необычных исключений инициализации, посмотрите мою запись в блоге об этом blog.jhades.org/open-session-in-view-pattern-pros-and-cons это может помочь - person Angular University; 07.01.2014
comment
Я заменил поддельное имя класса (сущность A, B, X, ...) на настоящие имена классов: Indicator, Factor, ... - person user3154016; 08.01.2014

Вот мое отображение бульдозера:

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 может отображать проксифицированные bean-компоненты Hibernate.
Но с этим отображением Dozer не может отображать проксифицированные bean-компоненты 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 

or

indicator instanceof Composite 

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

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

person user3154016    schedule 08.01.2014