How to cascade delete set of enums in JPA? - java

I have the roles field in User entity:
#Entity
#Table(indexes = {
#Index(columnList = "uuid")
})
public class User
...
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#NotEmpty
private Set<Roles> roles;
when I try to delete user with a query, the referential integrity constraint violation occurs, because user is referenced from User_roles table.
How to solve this in any way?
DDL for related table shows
create or replace table User_roles
(
User_id bigint not null,
roles varchar(255) null,
constraint FKi81fp6mx433heb7dvbxqaqvpv
foreign key (User_id) references User (id)
);
i.e. it doesn't contain ON DELETE CASCADE clause. I need it be there.

You can add ON DELETE CASCADE clause to the FOREIGN KEY constraint in the following way:
import javax.persistence.ForeignKey;
#Entity
#Table(name ="User")
public class User
{
#ElementCollection
#CollectionTable(
name = "User_roles",
joinColumns = #JoinColumn(name = "User_id"),
foreignKey = #ForeignKey(
name = "user_fk",
foreignKeyDefinition = "FOREIGN KEY (User_id) REFERENCES User(id) ON DELETE CASCADE")
)
#Enumerated(EnumType.STRING)
private Set<Roles> roles;
}

Related

How to annotate Map<Entity, Entity> with JPA?

The app's stack: Spring MVC, Spring DataJPA, Hibernate. There are three entities: student, tutor, theme.
Theme:
#Entity
#Table(name = "themes")
public class Theme {
// fields omitted
}
Student:
#Entity
#Table(name = "students")
public class Student {
private Map<Theme, Tutor> tutors;
// other fields omitted
}
Tutor:
#Entity
#Table(name = "tutors")
public class Tutor {
private Map<Theme, Student> students;
// other fields omitted
}
For save student-tutor-theme relationships i want use this table (PostgreSQL):
CREATE TABLE themes_students_tutors
(
theme_id INTEGER NOT NULL,
student_id INTEGER NOT NULL,
tutor_id INTEGER NOT NULL,
FOREIGN KEY (theme_id) REFERENCES themes (id) ON DELETE CASCADE,
FOREIGN KEY (student_id) REFERENCES students (id) ON DELETE CASCADE,
FOREIGN KEY (tutor_id) REFERENCES tutors (id) ON DELETE CASCADE
)
How i can to annotate tutors and students fields in entities, for their content correct persists in this table?
Like #kolossus mentioned: Use the #MapKeyJoinColumn¹ annotation, so that the classes (or the map fields) look like this (you can ignore the extention of AbstractPersistable):
Student:
public class Student extends AbstractPersistable<Long> {
#ManyToMany
#JoinTable(name = "themes_students_tutors", joinColumns = {
#JoinColumn(name = "student_id", referencedColumnName = "id") }, inverseJoinColumns = {
#JoinColumn(name = "tutor_id", referencedColumnName = "id") })
#MapKeyJoinColumn(name = "theme_id")
private Map<Theme, Tutor> tutors;
}
Tutor:
public class Tutor extends AbstractPersistable<Long> {
#ManyToMany
#JoinTable(name = "themes_students_tutors", joinColumns = {
#JoinColumn(name = "tutor_id", referencedColumnName = "id") }, inverseJoinColumns = {
#JoinColumn(name = "student_id", referencedColumnName = "id") })
#MapKeyJoinColumn(name = "theme_id")
private Map<Theme, Student> students;
}
Given that, something like this would be created:
Hibernate: create table students (id bigint not null, primary key (id))
Hibernate: create table themes (id bigint not null, primary key (id))
Hibernate: create table themes_students_tutors (tutor_id bigint not null, student_id bigint not null, theme_id bigint not null, primary key (student_id, theme_id))
Hibernate: create table tutors (id bigint not null, primary key (id))
Hibernate: alter table themes_students_tutors add constraint FKm5l4is34t5gs14p4skkv3aup7 foreign key (student_id) references students
Hibernate: alter table themes_students_tutors add constraint FK8o0mm5ywi0l4hdxi4lgw4dbnu foreign key (theme_id) references themes
Hibernate: alter table themes_students_tutors add constraint FKa0n6jvie0kmk0pmikcuvtepxh foreign key (tutor_id) references tutors
¹: See the Javadoc documentation of #MapKeyJoinColumn for some other samples

JPA mappedBy reference an unknown target entity

I am writing a simple inventory database that contains tables for products, orders and customers. The database definition can be found here:
CREATE TABLE public.customers
(
id integer NOT NULL DEFAULT nextval('customers_id_seq'::regclass),
title character varying(10) COLLATE pg_catalog."default" NOT NULL,
first_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
middle_names character varying(50) COLLATE pg_catalog."default",
last_name character varying(50) COLLATE pg_catalog."default" NOT NULL,
email character varying(50) COLLATE pg_catalog."default" NOT NULL,
phone_number character varying(50) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT customers_pkey PRIMARY KEY (id)
)
CREATE TABLE public.products
(
id integer NOT NULL DEFAULT nextval('products_id_seq'::regclass),
name character varying(100) COLLATE pg_catalog."default" NOT NULL,
sku integer NOT NULL,
inventory_on_hand integer NOT NULL,
reorder_threshold integer NOT NULL,
price numeric(5,2),
inventory_to_be_shipped integer NOT NULL,
CONSTRAINT products_pkey PRIMARY KEY (id)
)
CREATE TABLE public.order_items
(
id integer NOT NULL DEFAULT nextval('order_items_id_seq'::regclass),
product_id integer NOT NULL,
order_id integer NOT NULL,
CONSTRAINT order_items_pkey PRIMARY KEY (id),
CONSTRAINT order_items_order_id_fkey FOREIGN KEY (order_id)
REFERENCES public.orders (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION,
CONSTRAINT order_items_product_id_fkey FOREIGN KEY (product_id)
REFERENCES public.products (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
CREATE TABLE public.orders
(
id integer NOT NULL DEFAULT nextval('orders_id_seq'::regclass),
customer_id integer,
order_date date NOT NULL DEFAULT now(),
arrival_date date,
CONSTRAINT orders_pkey PRIMARY KEY (id),
CONSTRAINT orders_customer_id_fkey FOREIGN KEY (customer_id)
REFERENCES public.customers (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE NO ACTION
)
I am trying to implement a Spring Security Resource server to perform CRUD operations on the database. I have implemented entity classes for each table in the database but when try to start the server I get a
org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: edu.finalyearproject.imsresourceserver.models.Order.customers in edu.finalyearproject.imsresourceserver.models.Customer.orders
My entity and repository classes can be found below:
Product.java:
#Entity
#Table(name = "products")
#Data
public class Product
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String name;
private Integer sku;
private Float price;
private Integer inventory_on_hand;
private Integer reorder_threshold;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
Customer.java
#Entity
#Table(name = "customers")
#Data
public class Customer
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
private String title;
private String first_name;
private String middle_names;
private String last_name;
private String email;
private String phone_number;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
Order.java
#Entity
#Table(name = "orders")
#Data
public class Order
{
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private Integer id;
#ManyToOne
#JoinColumn(name="customer_id", nullable=false)
private Customer customer;
private Date order_date;
private Date arrival_date;
#ManyToMany(mappedBy = "orders", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Set<Product> products = new HashSet<>();
}
I know the problem is related to the relationships between the entities, but I haven't been able to find a solution. Any help would be greatly appreciated.
Try to correct this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "orders", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
to this:
#Entity
public class Customer
{
// ...
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Order> orders;
}
See additional explanation in the documentation.
And you should correct also your Product-Order #ManyToMany association. Only one side of this association should use #JoinTable other side should use mappedBy property of the #ManyToMany annotation. Something like this:
#Entity
public class Product
{
// ...
#ManyToMany(
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY
)
#JoinTable(
name = "order_items",
joinColumns = #JoinColumn(name = "product_id"),
inverseJoinColumns = #JoinColumn(name = "order_id")
)
private Set<Order> orders = new HashSet<>();
}
#Entity
public class Order
{
// ...
#ManyToMany(
mappedBy = "orders",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.LAZY)
private Set<Product> products = new HashSet<>();
}
As it is stated in the documentation:
For #ManyToMany associations, the REMOVE entity state transition doesn’t make sense to be cascaded because it will propagate beyond the link table. Since the other side might be referenced by other entities on the parent-side, the automatic removal might end up in a ConstraintViolationException.
Also as this is explained in this section of the documentation:
If you forget to JOIN FETCH all EAGER associations, Hibernate is going to issue a secondary select for each and every one of those which, in turn, can lead to N+1 query issues.
For this reason, you should prefer LAZY associations.

Connecting the foreign key to an intermediary entity

There are 4 tables in my database. User (which has the role_id column as a foreign key), Role, Permission and an intermediary table named RolePermission (as the relationship between Role and Permission is M:N)
I want to get all the permissions of a specific user based on their role_id.
The intermediary table is defined like this:
CREATE TABLE role_permission
(
role_id TINYINT NOT NULL,
perm_id TINYINT NOT NULL,
CONSTRAINT role_permission_pk PRIMARY KEY (role_id, perm_id),
CONSTRAINT role_permission_f1 FOREIGN KEY (role_id) REFERENCES role (rid),
CONSTRAINT role_permission_f2 FOREIGN KEY (perm_id) REFERENCES permission (pid)
);
and the entity related to this table is like this:
#Entity
#Table(name = "role_permission")
public class RolePermission {
#EmbeddedId
private RolePermissionPK id;
#ManyToOne
#JoinColumn(name = "role_id", referencedColumnName = "rid", insertable = false, updatable = false)
private Role role;
#ManyToOne
#JoinColumn(name = "perm_id", referencedColumnName = "pid", insertable = false, updatable = false)
private Permission permission;
...
}
but when I'm trying to get this field in my User class, it returns nothing:
#OneToMany(mappedBy = "role")
private List<RolePermission> permissions;
The database works properly and all the entities including Role, Permission and RolePermission are working flawlessly too. But I can't figure out the missing link between the RolePermission class and the User class.
The M:N connection between the Role and Permission tables should be defined in the Role table like this:
#ManyToMany
#JoinTable(
name = "role_permission",
joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "perm_id")
)
List<Permission> permissions;
and then the foreign key in the User entity should be connected to the Role entity:
#ManyToOne
#JoinColumn(name = "role_id", referencedColumnName = "rid")
private Role role;

JPA, How to add the value of entity type in Map to primary key?

I have 3 entity classes like below:-
Role Entity
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#LazyCollection(LazyCollectionOption.FALSE)
#JoinTable(name = "roles_privileges", joinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "privilege_id", referencedColumnName = "id"))
private Set<Privilege> privileges;
// getters, setters etc
}
Privilege Entity
#Entity
public class Privilege {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "privileges", fetch = FetchType.EAGER)
#JsonIgnore
private Set<Role> roles;
// getters, setters etc
}
UrlsMapper Entity
#Entity(name = "urls_mapper")
#Table(name = "urls_mapper")
public class UrlsMapper {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
#Column(name = "http_method")
private HttpMethod httpMethod;
#Column(name = "path")
private String path;
#ManyToMany(fetch = FetchType.EAGER)
#MapKeyJoinColumn(name = "role_id", referencedColumnName = "id")
#JoinTable(
name = "u_r_p",
inverseJoinColumns = #JoinColumn(name = "privilege_id")
)
Map<Role, Privilege> privilegeMap;
// getters, setters etc
}
The keys, primary and foreign that get created are as below
The logs while table generation is as below:-
Hibernate: create table u_r_p (urls_mapper_id bigint not null, privilege_id bigint not null, role_id bigint not null, primary key (urls_mapper_id, role_id)) engine=InnoDB
Hibernate: alter table u_r_p add constraint FKgd7gd9f9ded1s28swdudqs0ro foreign key (privilege_id) references Privilege (id)
Hibernate: alter table u_r_p add constraint FKrryprkx4j60lyjti16eysn5g5 foreign key (role_id) references Role (id)
Hibernate: alter table u_r_p add constraint FKfkthdnoca59a18ba96183p7ov foreign key (urls_mapper_id) references urls_mapper (id)
And I just want to know how can I add the privilege_id also into the JoinTable u_r_p and if there can be other best options for this. Manually doing in the database is a obvious alternate, but i wanted to know the hbm2ddl.auto based solution, so that code manages it itself
I don't think you've modeled your concepts properly. You have a ManyToMany between Role and Priviledge but what makes UrlMapper an entity? You have a Map<Role, Privilege> field in UrlMapper but that is the purpose of the join table so there should be no need to duplicate that. Instead it seems to be that HttpMethod and Path are attributes of the relationship.
However, I might also note that you seem to be expecting there be a Role/Privilege join for many different HttpMethod/Path combinations. This seems incredibly fine grained and an operations nightmare, but whatever. Anyway, what you seem to be saying is you want unique combinations of Role/Privilege/HttpMethod/Path so you should just make a entity for that and the table represents your set. Make a Permission entity that holds a unique Role/Privilege/HttpMethod/Path. Role, Privilege, HttpMethod, and even Path are essentially enumerations so you should have a table for each for each of them with ManyToOne mappings in the Permission entity. You could add bidirectional OneToMany mappings in each of the lookup tables but I'm not sure I see a need for that. It's up to you.
I assume Privilege would be {allow, deny} but it seems like less of a tangle if you assume deny unless a Role/HttpMethod/Path permission specifically exists. If that's the case then I would leave out the Privilege entity. Anyway, just a thought. Hope this helps.

Hibernate 5 gives error: must have same number of columns as the referenced primary key

I have been migrating from hibernate 4 to hibernate 5. It is ok in hibernate 4, but doesn't work in hibernate 5.
I am getting exception:
Caused by: org.hibernate.MappingException: Foreign key
(FKf6eo63yo42ylh7vl5klap2eum:ProductParent [parent_id])) must have
same number of columns as the referenced primary key (ProductParent
[parent_id,product_id])
This is my hibernate mapping:
#Entity
public class ProductParent implements Serializable {
#Id
#OneToOne()
#JoinColumn(name = "product_id")
private AbstractProduct product = new AbstractProduct();
#ManyToMany(cascade = ALL, fetch = EAGER)
#JoinTable(name = PRODUCTPARENT, joinColumns = { #JoinColumn(name = "product_id") }, inverseJoinColumns = { #JoinColumn(name = "parent_id") })
private Set<ProductParent> parents = new HashSet<>();
and table structure:
CREATE TABLE productparent (
product_id bigint NOT NULL,
parent_id bigint,
CONSTRAINT fk_parent_id FOREIGN KEY (parent_id) REFERENCES abstractproduct (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT fk_product_id FOREIGN KEY (product_id) REFERENCES abstractproduct (id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT product_id_parent_id_should_be_unique UNIQUE (product_id, parent_id)
)
Could you help me that?
Solution:
To add:
#OneToOne
#JoinColumn(name = PARENT_ID)
private AbstractProduct parent = new AbstractProduct();

Categories