Using Spring Boot starter, I am trying to create a simple example project that involves a user that has multiple address fields. I am experimenting on using @DiscriminatorColumn and @DiscriminatorValue to differentiate the different types of addresses a user may have.
Here is an abbreviated sample of the tables in my project:
CREATE TABLE user ( id INT AUTO_INCREMENT);
CREATE TABLE user_address ( user_id INT, address_id INT);
CREATE TABLE address ( id INT AUTO_INCREMENT, TYPE VARCHAR(31));
And here are the classes I am trying to join:
@Entity
@DiscriminatorColumn(name = "type")
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String type;
}
@Entity
@DiscriminatorValue("HOME")
public class HomeAddress extends Address {}
@Entity
@DiscriminatorValue("CURRENT")
public class CurrentAddress extends Address{}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String type;
@OneToOne
@JoinTable(
name = "user_address",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "address_id", referencedColumnName = "id")}
)
private HomeAddress homeAddress;
@OneToOne
@JoinTable(
name = "user_address",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "address_id", referencedColumnName = "id")}
)
private CurrentAddress currentAddress;
}
I've tried replacing @OneToOne
with @OneToMany
but still it doesn't work.
The reason I want to be able to do this is that I am thinking of associating an Address with other entities. For example, ShippingAddress for an order or LocationAddress for a Building, etc.
Here is a dump of the error:
Caused by: org.hibernate.boot.spi.InFlightMetadataCollector$DuplicateSecondaryTableException: Table with that name [user_address] already associated with entity
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$EntityTableXrefImpl.addSecondaryTable(InFlightMetadataCollectorImpl.java:1420) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.annotations.EntityBinder.addJoin(EntityBinder.java:972) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.annotations.EntityBinder.addJoin(EntityBinder.java:868) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.ClassPropertyHolder.addJoin(ClassPropertyHolder.java:207) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:1792) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:904) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:731) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.boot.model.source.internal.annotations.AnnotationMetadataSourceProcessorImpl.processEntityHierarchies(AnnotationMetadataSourceProcessorImpl.java:245) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess$1.processEntityHierarchies(MetadataBuildingProcess.java:222) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:265) ~[hibernate-core-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847) ~[hibernate-entitymanager-5.0.9.Final.jar:5.0.9.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874) ~[hibernate-entitymanager-5.0.9.Final.jar:5.0.9.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:338) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:373) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:362) ~[spring-orm-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.3.2.RELEASE.jar:4.3.2.RELEASE]
... 16 common frames omitted
Unfortunately you can't make separate relations for the two address type, as pure JPA is not able to 'discriminate' results of joins in queries, i.e. is not making joins using the discriminator value for getting only objects of a certain type for an association and of another type for the other association. Hibernate, instead, has methods to do it, but out of the JPA specification.
So the only way it works is allowing you to have a @OneToMany
relationship with Address objects, giving you in return a polymorphic collection containing
in turn HomeAddress
and CurrentAddress
objects
I hade a similar issue. The only solution for me was to work a little bit differently (and basically in a way I didn't like so much, but that's it).
Basically you have 4 options (and several alternatives):
1- having only one (@OneToMany
) relationship, scan your collection for objects of type HomeAddress
and/or CurrentAddress
after retrieve and assign them to the correct fields.
2- Change the way your inheritance is designed. In this case you can use Address as @MappedSuperclass
and your subclasses as entities extending the Address Class. The two classes should be mapped in two separated tables. You don't need a join table.
This is the way I proceeded, even if I didn't like it too much.
3- Throw away inheritance and use 2 separate tables
4- use hibernate (non-jpa) annotations.
Sorry for not being pasting code, but I'm unable to do it here & now
Your error is :
Caused by: org.hibernate.boot.spi.InFlightMetadataCollector$DuplicateSecondaryTableException: Table with that name [user_address] already associated with entity
You have forgoten the @Inheritance
annotation.
I suppose that you use a single table for all adress classes. So you should precise that you want to use a SINGLE_TABLE
strategy for handling OOP->SQL mapping. It is which used by default but you can precise it since more readable.
Add it on the class declaration of your super class entity :
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
in this way :
@Entity
@DiscriminatorColumn(name = "type")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String type;
}
Edit :
user_address
is used as jointable in multiple relations in the User
entity. Hibernate gives the impression that it doesn't accept it.
If you use a single relation, you lose benefits to categorize the adresses.
If you may change you schema, you could remove the join table and add a fk in address to specify the userId. Sorry, I have not other clues.
Have you tried the Attribute
@ManyToMany
Because with this definition you will archive what you have
A and B in which A may contain a parent instance for which there are many children in B and vice versa.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With