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?
Related
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;
I have two entities, one of UserEntity and the other RoleEntity, the user can have multiple roles and the role can be used by multiple users, my entities look like:
#Entity
public class UsersEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Long id;
//...
#ManyToMany(mappedBy = "users")
private Set<RolesEntity> roles;
//...
// Getters and setters
}
#Entity
public class RolesEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(nullable = false)
private Integer id;
#NotNull
#Enumerated(EnumType.STRING)
#Column(length = 20)
private RoleEnum name;
#JoinTable(name = "user_roles", joinColumns = {
#JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)})
#ManyToMany
private List<UsersEntity> users;
}
Generally roles are fixed they don't change a lot. Now I have a service:
public void removeUser(Long id) {
if (userRepository.findById(id).isPresent()) {
userRepository.deleteById(id);
} else {
throw new IllegalArgumentException("User not found!");
}
}
My requirement is to remove only the user and not the roles related with this user, which mean remove the user and the relation ship. When I call the previews method I got.
org.postgresql.util.PSQLException: ERROR: update or delete on table "users" violates foreign key constraint "constraint_user_id" on table "user_roles"
Detail: Key (id)=(4) is still referenced from table "user_roles".
Is there any trick to solve this please?
You need to make any references to that UsersEntity to be null.
So basically what is the problem? While RolesEntity has a reference to that UsersEntity class you cannot delete that. The most trivial thing to do is to make a loop for each RolesEntity in your UsersEntity class and remove everything from it.
Then you can successfully delete that user from your db.
Check this out to get more info: How to remove entity with ManyToMany relationship in JPA (and corresponding join table rows)?
I solved my issue like this, I'm not sure if this is a best approach to solve this:
public void removeUser(Long id) {
Optional<UsersEntity> userById = usersRepository.findById(id);
if (userById.isPresent()) {
UsersEntity user = userById.get();
for (RolesEntity role : user.getRoles()) {
role.setUsers(null);
rolesRepository.save(role);
}
usersRepository.delete(user);
} else {
throw new IllegalArgumentException("User not found!");
}
}
I think you can do this by using CascadeType.REMOVE
#ManyToMany(cascade = CascadeType.REMOVE, fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles;
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)
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (ravermeister.artist_recordlabel, CONSTRAINT FK_9dgdyft45droyopxsqijwb1dx FOREIGN KEY (artist_id) REFERENCES artist (id))
Artist class
#Entity
#Repository
#Data
#NoArgsConstructor(force = true)
public class Artist {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "artist_firstname")
private String artist_firstname;
#Column(name = "artist_secondname")
private String artist_secondname;
#Column(name = "artist_nickname")
private String artist_nickname;
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "artist_recordlabel", joinColumns = #JoinColumn(name = "artist_id"),
inverseJoinColumns = #JoinColumn(name = "label_id"))
private Set<RecordLabel> recordLabels;
#ManyToMany (fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable (name = "artist_musicrelease", joinColumns = #JoinColumn (name = "artist_id"),
inverseJoinColumns = #JoinColumn (name = "musicrelease_id"))
private Set <MusicRelease> musicReleaseSet;
and RecordLabel class
#Entity
#Repository
#Data
#NoArgsConstructor(force = true)
public class RecordLabel {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "label_name")
private String label_name;
#Column(name = "label_country")
private String label_country;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name = "artist_recordlabel", joinColumns = #JoinColumn(name = "label_id"),
inverseJoinColumns = #JoinColumn(name = "artist_id"))
private Set<Artist> artistsList;
Please follow this guide for a many-to-many relation
http://www.baeldung.com/hibernate-many-to-many
I think this error means that you are trying to insert/update into RecordLabel table a artist_id value that does not exist in Artist table.
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>();