How to name the #ForeignKey when using #IdClass composite primary key? - java

#Entity
#IdClass(MyPK.class)
public class SubEntity {
#Id private int id;
#Id private String name;
}
#Entity
public class ParentEntity {
#OneToOne(cascade = ALL)
#JoinColumn(name="fk_sub_id", foreignKey = #ForeignKey(name="fk_sub"))
private SubEntity sub;
}
Caused by: org.hibernate.AnnotationException: A Foreign key refering SubEntity from ParentEntity has the wrong number of column. should be 2
This fails due to the #JoinColumn annotation. But how do I have to write it for a composite PK? I have to use this annotation to set the foreignkey constraint name during hibernate auto generation.

For composite keys you should use #JoinColumns
#JoinColumns(value = {
#JoinColumn(name = "fk_sub_id", referencedColumnName = "id"),
#JoinColumn(name = "fk_name", referencedColumnName = "name")},
foreignKey = #ForeignKey(name = "fk_sub"))
Update:
Hibernate seems to not have followed the JPA 2.1 specification in this instance. You'll need to additionally include hibernates own annotation which is deprecated but still functions. This should be added like:
#JoinColumns(value = {
#JoinColumn(name = "fk_sub_id", referencedColumnName = "id"),
#JoinColumn(name = "fk_name", referencedColumnName = "name")},
foreignKey = #ForeignKey(name = "fk_sub"))
#org.hibernate.annotations.ForeignKey(name = "fk_sub")

Related

Spring Boot Hibernate inserts Data into wrong Columns in Postgres

Initial Situation:
I have these two entities, GroceriesList and Product.
GroceriesList:
A GroceriesList can have several products.
#Entity
#Table(name = "lists")
public class GroceriesList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "list_id")
private Long listId;
#Column(name = "list_name")
private String listName;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "product_id", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "list_id", referencedColumnName = "product_id")
)
private Set<Product> products;
}
Products:
A Product can be allocated to several GroceriesLists
#Entity
#Table(name = "products")
public class Product {
public enum Category {
Dairy,
Fruit,
Vegetable,
Meat,
Grains
}
// Product ID Column, GenerationType.Identity refers to auto incr in Postgres
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "product_id")
private Long productId;
#Column(name = "product_name")
private String productName;
#Column(name = "product_vendor")
private String productVendor;
#Enumerated(EnumType.STRING)
#Column(name = "product_category")
private Category productCategory;
#Column(name = "product_storedquantity")
private Integer productStoredQuantity;
#JsonIgnore
#ManyToMany(mappedBy = "products")
private List<GroceriesList> groceriesLists;
}
The entities and relationships are stored in three different tables in Postgres(One for Lists, one for products, and one mapping table for the relationships).
The mapping table:
Mapping Table "lists_products"
A sample Product:
Sample Product with Id=4
The problem: I'm trying to create new lists, and that works. However, as you can spot from the Mapping Table Image, Hibernate inserts the IDs into the wrong columns. The list_id in the mapping table is currently getting the product_id, and the product_id in the mapping table is getting the list_id.
I have tried to change the order of column names & references in the #JoinTable annotation in the GroceriesList Entity. However, that throws a mapping Error. I assume my error lies somewhere in that annotation. Interestingly, Hibernate uses the correct SQL-Query. What am I missing here?
After consulting fladdimir's solution, it gave me an idea and I solved my issue. The problem lied within the #JoinColumn annotation, where I thought the "name = ..." property refers to the column name in the database.
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "product_id", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "list_id", referencedColumnName = "product_id")
)
private Set<Product> products;
However, that property refers to the declared variable inside the Entity in Spring Boot, i.e. listId
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "list_id")
private Long listId;
So, the working implementation of the #JoinTable Annotation should look like the following, where "name = ..." uses that variable name:
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "lists_products",
joinColumns = #JoinColumn(name = "listId", referencedColumnName = "list_id"),
inverseJoinColumns = #JoinColumn(name = "productId", referencedColumnName = "product_id")
)
private Set<Product> products;

How I can Join this 3 Tables with JPA

I want to join these 3 Tables.
Here you see my Person Entity
#Entity
#Table(name = "Person", schema = "public")
public class PatientEntity {
#Id
#Column(name = "id")
private Long id;
#Column(name = "lastname")
private String name;
#OneToMany
#JoinTable(name = "person_contact", joinColumns = { #JoinColumn(name = "person_id") }, inverseJoinColumns = { #JoinColumn(referencedColumnName = "id") })
#Column(name = "contact")
private Set<ContactEntity> contacts;
//Getter Setters
And here is my contact entity:
#Entity
#Table(name="contact",schema="public")
public class ContactEntity {
#Id
#Column(name="id")
private Long id;
#Column(name="phone")
private String phone;
//Getter Setters
I just read the Persons from the Table with findById with a Spring JPARepository, but there is no Contact mapped. There is no error during my HTTP request, but instead of a Contact there is null and this error message:
com.sun.jdi.InvocationException occurred invoking method.
The business case is, that every Person can have one or more contact. Is it possible to make it with JPA Annotations or do I need to map it by myself with a JPQL? Or should I create an Entity for the middle table? (person_contact)
The Database is a PostgreSQL Database.
There is this notification too in the Log:
ERROR: column contacts0_.contacts_id does not exist
Perhaps you meant to reference the column "contacts0_.contact_id".
Position: 306
Your #JoinTable has incorrect #JoinColumn specifications and corresponds to the following ddl.
create table person_contact (person_id bigint not null, contacts_id bigint not null, primary key (person_id, contacts_id))
To map your db structure, use following (note removed #Column annotation)
#OneToMany
#JoinTable(name = "person_contact", joinColumns =
{
#JoinColumn(name = "person_id", referencedColumnName = "id"),
},
inverseJoinColumns = {
#JoinColumn(name = "contact_id", referencedColumnName = "id")
})
private Set<ContactEntity> contacts;
I also encourage you to read https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/ and reconsider a db structure without a join table (depending on your load and the effort to make this db change)

JPA - #ManyToMany with duplicate reference to the same foreign key

I have 3 entities like this:
#Entity
public class A {
#Id
private String id;
}
#Entity
#IdClass(B.BPK.class)
public class B {
#Id
private String id;
#Id
#ManyToOne
private A a;
}
#Entity
public class C {
#Id
private int refOne;
#Id
private int refTwo;
#Id
private A a;
#ManyToMany
private Set<B> bs;
}
I have a question about the table generated for the relation #ManyToMany of the entity C. There are 5 columns generated: refOne, refTwo, a_id, b_id, b_a_id.
As you could see, a_id and b_a_id are foreign key on the same column and in my model, it's not possible that a_id is different from b_a_id.
Is there a solution to not duplicate the column?
EDIT:
I insist on this point, A is part of the id of C, and bs in C could be empty.
I think about several solutions but none of them are really satisfying:
Use a Converter that used the b_id and a_id to recover bs entities
I tried to manage the columns with #JoinTable like this:
JoinTable approach:
#JoinTable(name = "bs", joinColumns = {
#JoinColumn(name = "refOne", referencedColumnName = "refOne"),
#JoinColumn(name = "refTwo", referencedColumnName = "refTwo"),
#JoinColumn(name = "a_id", referencedColumnName = "a_id")
}, inverseJoinColumns = {
#JoinColumn(name = "b_id", referencedColumnName = "b_id"),
#JoinColumn(name = "b_a_id", referencedColumnName = "b_a_id", insertable = false, updatable = false)
But I face theses problems Mixing insertable and non insertable columns in a property is not allowed orParameter index out of range`
})
Since C has already a reference to A, you can fetch A.bs, without having to declare a collection of bs inside C. You need, however, to have a Set<B> in entity A.
Unless, of course, you can't change the mapping...
You could annotate private A a; with #JoinColumn that references b_a_id. There are also other annotations which let you you configure the mapping even more. Yet there is no issue with having 2 FK columns containing the same value.
Also you should always create your tables by hand. DOn't let it be generated in production.
For more info read the docs. Maybe you have to search for a more specific problem, but resources are out there

How to specify foreign key with the join column?

My code is as below. I am using the spring boot with jpa and postgresql database
I need user friendly name as foreign key.
#Entity
#Table(name="course_table")
public class Course extends BaseAuditingEntity {
#ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
#JoinTable(name = "course_program_table", joinColumns = #JoinColumn(name = "course_id", referencedColumnName = "course_id", foreignKey = #ForeignKey(name = "fk_program_id")), inverseJoinColumns = #JoinColumn(name = "program_id", referencedColumnName = "program_id", foreignKey = #ForeignKey(name = "fk_course_id")))
private List programs;
}
I have given the name of foreign key using the #ForeignKey annotation but when I see db it is showing the randomly created foreignkey name.
CREATE TABLE course_program_table
(
course_id integer NOT NULL,
program_id integer NOT NULL,
CONSTRAINT fk_28c95hl4nqclyvyxuduei5nbf FOREIGN KEY (program_id)
REFERENCES public.program_table (program_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT fk_5sainywquv8yyu24pjk3jptn7 FOREIGN KEY (course_id)
REFERENCES public.course_table (course_id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
I need foreign key as mentioned in the annotation like fk_program_id and fk_course_id.
Thanks in advance.
With a join table this is how you should specify it
#ManyToMany
#JoinTable(name = "course_program_table",
joinColumns = #JoinColumn(name = "course_id", ...)
foreignKey = #ForeignKey(name = "fk_program_id"),
inverseJoinColumns = #JoinColumn(name = "program_id", ...)
inverseForeignKey = #ForeignKey(name = "fk_course_id"))
private List programs;
This is how I do it with the JPA provider I use (not Hibernate), and that is why the #JoinTable has the "foreignKey"/"inverseForeignKey" attributes (the FKs are on/owned by the join table).
If that doesn't work then you need to be looking at raising a bug on your chosen JPA provider.
#ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
#JoinTable(name = "tten_courseservice_course_program_table", joinColumns = #JoinColumn(name = "course_id", referencedColumnName = "course_id"), inverseJoinColumns = #JoinColumn(name = "program_id", referencedColumnName = "program_id"))
#ForeignKey(name="fk_tten_courseservice_course_table_course_id",inverseName="fk_tten_courseservice_program_table_program_id")
private List<ProgramEntity> programs;``
I have tried this and now I am able to generate foreign key name properly.
Hope it will help other.

JoinTable in JPA for 3 different entities

I'm trying to create a join table with #JoinTable from 3 different entities. Below is the code sample I'm using. While creating join table I'm getting below error. Please help to resolve.
There are 3 entities in my design. Credential, Category and Tenant.
Now I'm trying to make a join table that will contain the pk of these 3 tables using #ManyToMany annotation between them. Below is the relationship which I'm trying to make.
#Entity
#Table(name = "CREDENTIALS")
public class CredentialsEntity {
/** The credential id. */
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int credentialId;
#ManyToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(table="CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"),
#JoinColumn(table ="TENANT", name = "TENANT_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table="CREDENTIALS", name = "CREDENTIAL_ID", referencedColumnName = "ID"))
private List<TenantEntity> tenantEntities;
}
==========================
#Entity
#Table(name = "TENANT")
public class TenantEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID")
private int tenantId;
#ManyToMany(mappedBy = "tenantEntities", cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
==========================
#Entity
#Table(name = "CATEGORY")
public class NodeCategory {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private int categoryId;
#ManyToMany(cascade = CascadeType.ALL)
private List<CredentialsEntity> credentialsEntities;
}
Caused by:
org.hibernate.AnnotationException: Cannot find the expected secondary
table: no TENANT available for
com.aricent.aricloud.entity.CredentialsEntity at
org.hibernate.cfg.Ejb3Column.getJoin(Ejb3Column.java:363) at
org.hibernate.cfg.Ejb3Column.getTable(Ejb3Column.java:342) at
org.hibernate.cfg.Ejb3Column.checkPropertyConsistency(Ejb3Column.java:584)
at
org.hibernate.cfg.annotations.CollectionBinder.buildCollectionKey(CollectionBinder.java:1018)
at
org.hibernate.cfg.annotations.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:1336)
EDIT
I'm able to do the jointable like below, as mentioned in link
is this correct approach or I'm doing something wrong?
#ManyToMany(cascade={CascadeType.ALL})
#JoinTable(name = "CATEGORY_HAS_CREDENTIALS",
joinColumns = {
#JoinColumn(name = "CREDENTIAL_ID", referencedColumnName = "ID")},
inverseJoinColumns = #JoinColumn(table = "CATEGORY", name = "CATEGORY_ID", referencedColumnName = "ID"))
#MapKeyJoinColumn(name = "TENANT_ID", referencedColumnName ="ID")
#ElementCollection
private Map<TenantEntity, NodeCategory> tenantCategoryMap = new HashMap<TenantEntity, NodeCategory>();

Categories