niedziela, 24 czerwca 2012

Mapowanie opakowywacza kolekcji

Opakowywacze kolekcji, które zostały opisane przez Koziołka, uważam za bardzo dobre i przydatne podejście/wzorzec. Jednak w pewnym momencie natrafiłem na problem podczas mapowania takich tworów. Mamy w systemie encje BankAccount, wielu różnych aktorów (Client, Manager, itd) może mieć zbiór BankAccount. Warto opakować taki zbiór komponentem BankAccounts, który miałby wspólne metody dla wszystkich aktorów, takie jak np. znajdź konto z zadanego banku, itd.

Wyglądałyby to mniej więcej tak:
@Embeddable
public class BankAccounts {

    @OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
    private Set<BankAccount> bankAccounts = new HashSet<>();
    //...
}
I w jednym z aktorów:
@Entity
public class Client{
    //...

    @Embedded
    @AssociationOverride(name = "bankAccounts", 
        joinColumns = @JoinColumn(name = "client_id"))
    private BankAccounts bankAccounts = new BankAccounts();
    //...
}
I tu powstaje problem, w postaci wyjątku:
javax.persistence.PersistenceException: [PersistenceUnit: testPU] Unable to configure EntityManagerFactory
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:378)
    at org.hibernate.ejb.HibernatePersistence.createEntityManagerFactory(HibernatePersistence.java:56)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:63)
    at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:47)
    at pl.cqbroker.utils.DaoTest.setUp(DaoTest.java:39)
    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:45)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:42)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:27)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:30)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:263)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:68)
    at ...
Caused by: org.hibernate.AnnotationException: Illegal attempt to define a @JoinColumn with a mappedBy association: bankAccounts.bankAccounts
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumn(Ejb3JoinColumn.java:253)
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumnsWithDefaultColumnSuffix(Ejb3JoinColumn.java:227)
    at org.hibernate.cfg.Ejb3JoinColumn.buildJoinColumns(Ejb3JoinColumn.java:194)
    at org.hibernate.cfg.ColumnsBuilder.extractMetadata(ColumnsBuilder.java:124)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1495)
    at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:2433)
    at org.hibernate.cfg.AnnotationBinder.fillComponent(AnnotationBinder.java:2336)
    at org.hibernate.cfg.AnnotationBinder.bindComponent(AnnotationBinder.java:2285)
    at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:2021)
    at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:796)
    at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:707)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processAnnotatedClassesQueue(Configuration.java:4035)
    at org.hibernate.cfg.Configuration$MetadataSourceQueue.processMetadata(Configuration.java:3989)
    at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1398)
    at org.hibernate.cfg.Configuration.buildMappings(Configuration.java:1375)
    at org.hibernate.ejb.Ejb3Configuration.buildMappings(Ejb3Configuration.java:1519)
    at org.hibernate.ejb.EventListenerConfigurator.configure(EventListenerConfigurator.java:193)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:1100)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:282)
    at org.hibernate.ejb.Ejb3Configuration.configure(Ejb3Configuration.java:366)
    ... 28 more
Rozwiązanie jest w generalnie proste, ale trochę czasu mi zajęło zanim zrozumiałem co jest nie tak, więc gdyby ktoś miał podobny problem, wystarczy dodać adnotację @JoinColumn(). Może być wypełniona jakimiś parametrami, bądź nie - nie ma to znaczenia bo i tak zostanie nadpisana przez Clienta lub inną encję.
@Embeddable
public class BankAccounts {

    @OneToMany(cascade = { CascadeType.ALL }, orphanRemoval = true)
    @JoinColumn
    private Set<BankAccount> bankAccounts = new HashSet<>();
    //...
}

2 komentarze: