JPA entity with composite primary and foreign keys - java

I have an entity with a composite primary key consisting of two fields, one of which is also part of a composite foreign key.
Background: I have entities Person,Area, and Session.
Person has many-to-many relationships with Area and Session, using join entities called PersonArea and PersonSession.
So, I have PersonSession, with primary key of (personId, sessionId).
PersonId and SessionId are themselves foreign keys to Person and Session.
PersonSession also has a field areaId.
I want (personId, areaId) to be a composite foreign key to PersonArea.
My code for PersonSession:
#Entity
#Table(name="person_session")
#IdClass(PersonSession.ID.class)
public class PersonSession {
#Id
private int personId ;
#Id
private int sessionId;
#ManyToOne
#JoinColumn(name = "personId", updatable = false, insertable = false, referencedColumnName = "id")
private Person person;
#ManyToOne
#JoinColumn(name = "sessionId", updatable = false, insertable = false, referencedColumnName = "id")
private Session session;
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId", updatable = false, insertable = false),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
}
Code for PersonSession.Id
public static class ID implements Serializable {
private int personId;
private int sessionId;
}
This seems OK, it creates all the correct relationships in the database. The problem comes when I try to insert PersonSession objects - the areaId column is always null, I think that is because it's defined a updatable=false, insertable=false.
However, if I try and make it updatable and insertable, I get an exception complaining the personId is a repeated column:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: foo.bar.PersonSession column: personId (should be mapped with insert="false" update="false")
How can I have the required relationships AND have areaId updatable and insertable?

I should be able to do what I want with this:
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId"),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
But Hibernate does not support mixing updatable and non updatable Join Columns. The accepted answer to this question indicates that it might be supported at some time, but it seems the developers aren't very worried about that shortcoming.
I have how ditched Hibernate in favour of Eclipselink and it works!

I know I am late but I faced the same problem and I used #JoinColumnsOrFormulas to resolve it. Here is what you could do:
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name="personId", referencedColumnName = "personId")),
#JoinColumnOrFormula(formula = #JoinFormula(value="areaId", referencedColumnName = "areaId"))})
private PersonArea personArea;

Related

Mapping two columns in JPA

I have the following 3 tables
table: project
id
company_code
number
contract_type_code
account_type_code
other columns…
table: contract_type
id
company_code
code
name
table: account_type
id
company_code
code
name
The project table references contract_type and account_type tables through contract_type_code/company_code and account_type_code/company_code respectively.
The company_code and code columns are what make a contract_type and account_type unique.
I'm struggling with modelling and mapping this in JPA. I've tried with the #JoinColumn and #JoinColumns annotation and there's no way for me to make it work.
This is one of the ways I've been trying with no success:
public class Project implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long companyCode;
private Long number;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "contract_type_code", referencedColumnName = "code"),
#JoinColumn(name = "company_code", referencedColumnName = "company_code")
})
private ContractType contractType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "account_type_code", referencedColumnName = "code"),
#JoinColumn(name = "company_code", referencedColumnName = "company_code")
})
private AccountType accountType;
This is the issue I'm getting with the mapping above:
Caused by: org.hibernate.MappingException: Unable to find column with logical name company_code in table contract_type
For this mapping:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "contract_type_code", referencedColumnName = "code", insertable = false, updatable = false),
#JoinColumn(name = "company_code", referencedColumnName = "company_code2", insertable = false, updatable = false)
})
private ContractType contractType;
I get:
Caused by: org.hibernate.DuplicateMappingException: Table [account] contains physical column name [company_code] referred to by multiple logical column names: [company_code], [companyCode]
I assume you have companyCode fields in the AccountType and ContractType. Annotate them as below (first error suggests JPA can't find them):
#Column(name = "company_code")
In your Project class modify companyCode field as follows (to avoid the second error):
#Column(name = "company")
private Long companyCode;
and keep mapping with:
insertable = false, updatable = false
Hope this will help. If not please add AccountType and ContractType classes to your question. Maybe then it will be easier to sort it out

JPA Mapping of Association table where one of the entities has composite key

I have the following model that I need to annotate using JPA:
Merchant(merchant_id, ...).
MerchantType(id1, id2, ...)
MerchantMerchantTypeAssociationTable(merchant_id, id1, id2)
I cannot figure out how to map the association table. Mapping Merchant is straitghtforward, so I will leave it outside of the mappings. The other mappings are as follows:
MerchantType:
#Entity
class MerchantType {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "e1_id", column=#Column(name="e1_id")),
#AttributeOverride(name = "another_id", column=#Column(name="another_id"))
})
MerchantTypePk id;
#ManyToOne
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id", insertable = false, nullable = false)
#MapsId("e1_id")
AnotherEntity1 e1;
#Column(name = "another_id", referencedColumnName = "another_id", insertable = false, nullable = false)
Long anotherId;
//Two other local fields irrelevant to the discussion here
public MerchantType(){
this.id = new MerchantTypePk();
}
//Getters and setters here.
}
//MerchantTypePk is a simple Embeddable class here below with two Long fields:
//e1_id and another_id
MerchantMerchantTypeAssociation:
#Entity
class MerchantMerchantTypeAssociation {
#EmbeddedId
#AttributeOverrides({
#AttributeOverride(name = "e1_id", column = #Column(name = "e1_id")),
#AttributeOverride(name = "another_id", column = #Column(name = "another_id"))
#AttributeOverride(name = "offer_id", column = #Column(name = "merchant_id"))
})
private MerchantMerchantTypeAssociationPk id;
//******** HERE IS THE QUESTION
//******** HERE IS THE QUESTION
//******** HERE IS THE QUESTION
#ManyToOne
#JoinColumns({
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id", insertable = false, updatable = false),
#JoinColumn(name = "another_id", referencedColumnName = "another_id", insertable = false, updatable = false)
})
#MapsId("e1_id")
#MapsId("another_id")
private MerchantType merchantType;
//Similar mapping to the one above, but with only one Join Column
private Merchant merchant;
//One more local field that is irrelevant to the mapping
//but is the one that is forcing me to map a many - to - many relationship
//in this way.
}
//MerchantMerchantTypeAssociationPk as a simple embeddable
Question: How can I make a mapping for this kind of entities when the annotation '#MapsId' cannot be repeated and it does not accept more than one value?
You did not include the code for MerchantMerchantTypeAssociationPk, but I'm guessing it looks like this:
#Embeddable
public class MerchantMerchantTypeAssociationPk {
public MerchantPk merchantPK;
public MerchantTypePk merchantTypePK;
}
#MapsId is used to specify the attribute within the composite key to which the relationship attribute corresponds, not the columns. So MerchantMerchantTypeAssociation should look like this:
#Entity class MerchantMerchantTypeAssociation {
#EmbeddedId
private MerchantMerchantTypeAssociationPk id;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "e1_id", referencedColumnName = "e1_id",...),
#JoinColumn(name = "e2_id", referencedColumnName = "e2_id",...)
})
#MapsId("merchantTypePK") // <<< *attribute* in Embeddable
private MerchantType merchantType;
#ManyToOne
#JoinColumn(name = "m_id", referencedColumnName = "merchant_id",...)
#MapsId("merchantPK") // <<< *attribute* in Embeddable
private Merchant merchant;
}
Derived identities are discussed in the JPA 2.1 spec, section 2.4.1.

Hibernate relationship issues on no bidirectional entities

Hi there I have an entity
OfferItem with this two relationship manyToOne
#ManyToOne
#JoinColumn(name = "offer_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Offer offer;
#ManyToOne
#JoinColumn(name = "item_id", nullable = false, insertable = false, updatable = false)
#XmlTransient
#Getter
#Setter
private Item item;
And the foreign keys configuration on the database
<addForeignKeyConstraint baseTableName="offer_item"
baseColumnNames="item_id"
constraintName="item_offer_item_fk"
referencedTableName="item"
onDelete="CASCADE" onUpdate="CASCADE"
referencedColumnNames="id"/>
<addForeignKeyConstraint baseTableName="offer_item"
baseColumnNames="offer_id"
constraintName="offers_offer_item_fk"
onDelete="CASCADE" onUpdate="CASCADE"
referencedColumnNames="id"
referencedTableName="offer"/>
the relationship with offer is bidireccional, so on the Offer entity I have this relationship with OfferItem
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL})
#JoinColumn(name = "offer_id", nullable = false)
#XmlElementWrapper
#Getter
private List<OfferItem> offerItems = new ArrayList<>();
On Item I dont have the bidireccional relationship since is not necessary.
Now the problem is, that when I made the set of Item on OfferItem, and I add the offerItem on the OfferItems list of offer, and I save Offer. I can see how the offerItem id is generated because is persisted on cascade. But the link between the offerItem and item is lost.
I just try to set the item on OfferItem and save the entity with his own service, but nothing. I cannot persist this link between them.
Any idea or suggestion.
Regards.
Remove insertable = false, updatable = false from join columns. Also, the mapping for offerItems is wrong. It should be
#OneToMany(orphanRemoval = true, cascade = {CascadeType.ALL}, mappedBy = "offer")
#XmlElementWrapper
#Getter
private List<OfferItem> offerItems = new ArrayList<>();

Mapping to a composite unique (not primary) key in Hibernate

I have two classes - USER and ROLE. There are corresponding tables in the database, and a table called USER_ROLE that links the two.
USER has a primary key on column "id", and a composite unique key on columns "realm" and "realm_user_id". ROLE has a primary key on column "role_id"
USER_ROLE has 3 columns - role_id, realm, realm_user_id.
role_id references role_id in the ROLE table, and (realm,realm_user_id) reference the composite key in USER. I don't have access to the DB, so I can't update the schema.
There is a many-to-many relationship between users and roles. I want to be able to call getRoles() on User, and getUsers() on Role.
class User:
#Entity
#Table(name="USER")
public class User {
..other fields..
#ManyToMany(fetch = FetchType.LAZY, targetEntity=Role.class)
#JoinTable(name = "USER_ROLE",
joinColumns = {
#JoinColumn(name = "realm", nullable = false, updatable = false),
#JoinColumn(name = "realm_user_id", nullable = false, updatable = false)
},
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles;
Class Role:
#ManyToMany(fetch = FetchType.LAZY,
cascade={CascadeType.PERSIST,CascadeType.MERGE},
targetEntity=User.class
)
#JoinTable(name = "USER_ROLE",
joinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) },
inverseJoinColumns={
#JoinColumn(name="realm",nullable=false, updatable=false),
#JoinColumn(name="realm_user_id",nullable=false, updatable=false)
})
private Set<User> users;
I'm getting the following error:
A Foreign key refering User from Role has the wrong number of column. should be 1
I tried creating a separate class containing realm and realm_user_id, referenced in the #IdClass annotation, but that failed as User already has a primary key on id. I tried using the mappedBy annotation, but that didn't work either.

Using a JoinColumn Twice for CompositeKey getting Repeated Column Exception

I have the following situation:
I´m trying to build an application which is multi-tenant with the same tenants in one database
with the same tables. As by know Hibernate is not supporting this variant before 5.0 as I found.
I`m trying to solve this by adding a brandId field to every table.
As I build Many To Many relationships I also added this brandId to the ManyToMany Join Table and here (dont know if I can do this, mysql is not complaining) I made a foreign key to both tables while both include the brandid
So now for example I have a table Text(ID,name,brandId) and a Tag(ID,name,brandId) and a join table (text_id,tag_id,brand_id) where the foreign keys are
CONSTRAINT FK_TAG_TEXTS_TAG FOREIGN KEY (TAG_ID,BRAND_ID) REFERENCES TAG (ID,brand),
CONSTRAINT FK_TAG_TEXTS_TEXT FOREIGN KEY (TEXT_ID,BRAND_ID) REFERENCES TEXT (ID,brand)
As you can see Brand ID is used twice.
Then I generated my classes with Hibernate Tools, which created a Composite Primary Key Class as it should and the association in the Tag Class.
#ManyToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST,CascadeType.MERGE })
#JoinTable(name = "tag_texts", , joinColumns = {
#JoinColumn(name = "TAG_ID", nullable = false, insertable = false, updatable = false),
#JoinColumn(name = "BRAND_ID", nullable = false, insertable = false, updatable = false) }, inverseJoinColumns = { #JoinColumn(name = "TEXT_ID", insertable = false, nullable = false, updatable = false),#JoinColumn( name = "BRAND_ID", insertable = false, nullable = false, updatable = false) })
public List<Text> getTexts() {
return this.texts;
}
The problem is now that I get the following exception:
org.hibernate.MappingException: Repeated column in mapping for collection: de.company.domain.Tag.texts column: brand_id
I looked into the Hibernate code in the Collection class which raises the exception.
Here a method 'checkColumnDupliation' is called which uses a Set and inserts the name,
what means that a second time inserting "BRAND_ID" as column leads to this behaviour.
As I found the most common solution for the Repeated column error is by inserting 'insertable = false and updateable = false' when using the same column in several references. This is described here:
Hibernate: Where do insertable = false, updatable = false belong in composite primary key constellations involving foreign keys?
But this seems to be not the same problem as mine.
So my question is: Is there a possibility to fix this with the JPA Annotations and use the Brand ID in both joinColumns and inverseJoinColumns?
The problem is that you want a JoinTable between 3 entities: Text, Tag and Brand.
Probably you will have to use an IdClass, something like :
public class AssociationId implements Serializable {
private long textId;
private long tagId;
private long brandId;
hash and equals function
...
}
Id Class Entity:
#Entity
#Table(name="tag_text_brand")
#IdClass(AssociationId.class)
public class TagTextBrandAssociation {
#Id
private long tagId;
#Id
private long textId;
#Id
private long textId;
#ManyToOne
#PrimaryKeyJoinColumn(name="TAG_ID", referencedColumnName="ID")
private Tag tag;
#ManyToOne
#PrimaryKeyJoinColumn(name="TEXT_ID", referencedColumnName="ID")
private Text text;
#ManyToOne
#PrimaryKeyJoinColumn(name="BRAND_ID", referencedColumnName="ID")
private Brand brand;
...
}
You can use this in your 3 entities like this:
#Entity
public class Text {
#Id
private long id;
...
#OneToMany(mappedBy="text")
private List<TagTextBrandAssociation> tagsAndBrands;
...
}
See here for more information.

Categories

Resources