Spring Boot Hibernate inserts Data into wrong Columns in Postgres - java

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;

Related

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)

Persisting not working when ManyToOne used with OneToMany thru relation table

I have 3 entities User, Order, Item with a mapping like this:
#Entity
public class Item {
#Id
#Access(AccessType.PROPERTY)
private long id; // item id predefined
//..getters and setters
}
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
//...getters and setters
}
#Entity
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
#Access(AccessType.PROPERTY)
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.DETACH
})// without removing
//#JoinColumn(name = "client_id") - works fine
#JoinTable(name = "client_cr_order_cr_item",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client client;
#NotEmpty
#OneToMany(fetch = FetchType.LAZY, cascade = {
CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.DETACH
})// without removing
#JoinTable(name = "client_cr_order_cr_item",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "item_id"))
private List<Item> items;
//...getters and setters
}
When I persist order it fails with error:
ERROR: null value in column "item_id" violates not-null constraint
it generate query like this:
insert into client_cr_order_cr_item (client_id, order_id) values ()
i.e. it do not fill out segment_id field by some reason I don't know. But I refer to client not thru 3th table but using FK #JoinColumn(name = "client_id") , then it generate correct query:
insert into order_cr_item (order_id, item_id) values ()
Please, could you explain this behavior? Why mapping of client affect items? Is there any hint to make hibernate persists items to 3th table?

Hibernate loads list collection twice

I have 3 tables in my database, container, item and item_container which is a join_table of many to one between container and item. Entities in code are mapped using 2 classes: Container and Item.
The relation is unidrectional from Container to Item.
join_table in hibernate is mapped only by annotations in Container class.
#Entity
#Table(name="container")
public class Container {
#Id
#Column(name = "container_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinTable(name = "item_container",
joinColumns = #JoinColumn(name = "container_id"),
inverseJoinColumns = #JoinColumn(name = "item_id"))
private Set<Item> items;
//getter/setters
}
My problem is: when i have the type of collection Set<Item> items changed to List<Item>, the list contain every Item entity twice. I cant find the reason why, the hashCode and equals methods of Item are based only on the item_id field.
UPDATE:
Code for Item class:
#Entity
#Table(name = "item")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "item_template_id", nullable = false)
private ItemTemplate itemTemplate;
#Column(name = "item_group")
#Enumerated(EnumType.STRING)
private ItemGroup itemGroup;
#Column(name = "amount")
private int amount;
//getter/setters
}

Why Hibernate tries to insert new record with existed id?

Why Hibernate tries to insert new record with existing id?
Category table before save (was filled manually with insert)
ID | NAME | IMAGE
1 | 'NAME'| 'IMAGE'
Save code
//Category{id=0, name='NAME', image='IMAGE', parent=null}
getCurrentSession().save(category);
Should be inserted with id 2.
Category Java code
#Entity
#Table(name = "CATEGORY",
indexes = {
#Index(name = "CATEGORY_NAME_INDEX",
columnList = "CATEGORY_NAME")})
public class Category implements NamedModel {
#Id
#Column(name = "CATEGORY_ID")
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#NotNull
#Size(min = 1, max = 256)
#Column(name = "CATEGORY_NAME", length = 256)
private String name;
#OneToOne(fetch = FetchType.EAGER)
#JoinTable(name = "CATEGORY_RELATIONS",
joinColumns = {
#JoinColumn(name = "CATEGORY_RELATIONS_CATEGORY_ID",
referencedColumnName = "CATEGORY_ID")},
inverseJoinColumns = {
#JoinColumn(name = "CATEGORY_RELATIONS_PARENT_ID",
referencedColumnName = "CATEGORY_ID")})
private Category parent;
#OneToMany(cascade = {CascadeType.REMOVE, CascadeType.PERSIST},
fetch = FetchType.LAZY, mappedBy = "parent")
private List<Category> children;
}
SOLUTION
//Category
#GeneratedValue(strategy = GenerationType.IDENTITY)
//CategoryRelations
#Entity
#IdClass(CategoryRelationsPrimaryKey.class)
public static class CategoryRelationsPrimaryKey implements Serializable {
private Long categoryId;
private Long parentId;
instead of long.
This is because you had deleted/added a record from database directly instead from the application i.e. ORM, that's why values in hibernate_sequence is no longer maintained.
Hibernate maintains values in a hibernate_sequence table which would be inserted on creating a new record.
update next_val column value in hibernate_sequence to resolve the problem.
You can use Annotation #GeneratedValue(strategy = GenerationType.IDENTITY) to delegate primary key generation to database.

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