2

I have a many to many relationship betweeen two tables via a third table:

TABLE_A                      A_TO_B                    TABLE_B
---------                    -------------             ------------
KEY (PK)                     KEY_A (PK, FK)            KEY (PK)
--some more Atributes        KEY_B (PK, FK)            START_DATE (PK)
                             --some Attributes         END_DATE
                                                       --some Attributes

As you can see the problem with this Table structure is that Table B has a composed primary key that contains the startDate of the entry. This is to be able to have entry versions. I am not able to change the table structure. I want to join TABLE_A and TABLE_B in JPA via a @ManyToMany attribute. But JPA demands, that A_TO_B knows the full primary key of TABLE_B. But I would be fully satisfied to ignore the START_DATE and just get all entries of TABLE_B with KEY_B for a specific KEY_A.
This is the error:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [spring_hibernate_configuration.xml]: Invocation of init method failed; nested exception is org.hibernate.AnnotationException: referencedColumnNames(KEY_B) of de.mycompany.entity.model.TableA.listOfBs referencing de.mycompany.entity.model.TableB not mapped to a single property
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1628)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1078)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:857)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
    at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
    at de.mycompany.Start.main(Start.java:14)
Caused by: org.hibernate.AnnotationException: referencedColumnNames(KEY_B) of de.mycompany.entity.model.TableA.listOfBs referencing de.mycompany.entity.model.TableB not mapped to a single property
    at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:320)
    at org.hibernate.cfg.annotations.CollectionBinder.bindManytoManyInverseFk(CollectionBinder.java:1680)
    at org.hibernate.cfg.annotations.CollectionBinder.bindManyToManySecondPass(CollectionBinder.java:1560)
    at org.hibernate.cfg.annotations.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:800)
    at org.hibernate.cfg.annotations.CollectionBinder$1.secondPass(CollectionBinder.java:725)
    at org.hibernate.cfg.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:54)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1621)
    at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1589)
    at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:278)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:858)
    at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:885)
    at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
    at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:353)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:370)
    at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:359)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
    ... 11 more


This is my code:

Table A

@Entity
@Table(name = "TABLE_A")
public class TableA {
   @Id
   @Column(name = "KEY", nullable = false)
   private int key;

   @ManyToMany(fetch = FetchType.EAGER)
   @JoinTable(
        name = "A_TO_B", 
        joinColumns = @JoinColumn(name = "KEY_A", 
            referencedColumnName = "KEY"), 
        inverseJoinColumns = @JoinColumn(name = "KEY_B", 
            referencedColumnName = "KEY"))
   private List<TableB> listOfBs;

   /* More Fields, Getters and setters*/
}

Table B

@Entity
@Table(name = "TABLE_B")
public class TableB {
    @EmbeddedId
    private TableBId id;

    @Column(name = "END_DATE", nullable = false)
    private LocalDate endDate;

    @ManyToMany(mappedBy = "listOfBs")
    private List<TableA> listOfAs;

    /* More fields, Getters and Setters*/
}

Embedded ID for B

public class TableBId implements Serializable {
    @Column(name = "KEY", nullable = false)
    private int key;

    @Column(name = "START_DATE", nullable = false)
    private LocalDate startDate;

    /* Getters and Setters*/
}

Table A to B

@Entity
@Table(name = "A_TO_B")
public class AtoB {

    @EmbeddedId
    private AtoBId id;

    /* Getters and Setters*/
}

Embedded ID for A to B

public class AtoBId implements Serializable {

    @Column(name = "KEY_A", nullable = false)
    private int keyA;

    @Column(name = "KEY_B", nullable = false)
    private int keyB;

    /* Getters and Setters */
}

When I define the @ManyToMany with another fake join column containing a date the application starts just fine.

@ManyToMany(fetch = FetchType.EAGER)
       @JoinTable(
            name = "A_TO_B", 
            joinColumns = @JoinColumn(name = "KEY_A", 
                referencedColumnName = "KEY"), 
            inverseJoinColumns = {@JoinColumn(name = "KEY_B", 
                referencedColumnName = "KEY"),
                                  @JoinCokumn(name = "SOME_DATE", 
                referencedColumnName = "START_DATE")})
       private List<TableB> listOfBs;

But this wont give me the result I want. I just want to join A_TO_Band TABLE_B over KEY_B.

Marvin Schön
  • 67
  • 1
  • 6
  • 1
    are you able to use the creation of entity classes from database ? something like this https://stackoverflow.com/questions/5833329/generate-jpa-2-entities-from-existing-database – DevJava Nov 29 '17 at 16:10
  • Thanks for your suggestion. I tried it with Dali. The configuration Dialog for a relationship also requests both PKs: `KEY` and `START_DATE` – Marvin Schön Nov 30 '17 at 14:47

1 Answers1

0

For anyone with a similar problem here is what I did in the end:

I made the list of Bs in A transient, which means that it is not saved in the DB. Then I fill it lazyly via the getter:

@Entity
@Configurable
@Table(name = "TABLE_A")
public class TableA {
   @Id
   @Column(name = "KEY", nullable = false)
   private int key;

   @Transient
   private List<TableB> listOfBs;

   @Transient
   @Autowired
   AtoBRepository aToBRepository;

   @Transient
   @Autowired
   TableBRepository tableBRepository;

   public List<TableB> getListOfBs(){
      if (listOfBs == null){
         listOfBs = new ArrayList<>();
         List<AtoB> AtoBs = aToBRepository.findByIdKeyA(key);
         for (AtoB aToB : AtoBs){
            listOfBs.addAll(tableBRepository.findByIdKey(aToB.getKeyB));
         }
      }
      return listOfBs;
   }

   /* More Fields, Getters and setters*/
}

To autowire the repository in the entity I you load time weaving as described in this thread: load Time weaving with @configurable

Marvin Schön
  • 67
  • 1
  • 6