I have a many to many relationship between user and group with addtional columns in the join table. It looks like this:
When I delete an user, it should remove all his references from user_to_group(which works) and all the groups created by him should remain and have their created_by field updated to NULL(this doesn't happen, all the entries are deleted).
DDL for schema:
CREATE TABLE user (
user_id int NOT NULL AUTO_INCREMENT,
first_name varchar(100) NOT NULL,
last_name varchar(100) NOT NULL,
username varchar(100) NOT NULL,
email_address varchar(100) UNIQUE NOT NULL,
phone_number varchar(100) NOT NULL,
password varchar(255) NOT NULL,
notification_type varchar(30) NOT NULL DEFAULT "email",
date_created datetime NOT NULL,
is_active bool NOT NULL DEFAULT false,
CONSTRAINT user_pk PRIMARY KEY (user_id)
);
CREATE TABLE `group` (
group_id int NOT NULL AUTO_INCREMENT,
name varchar(100) NULL,
date_created datetime NOT NULL,
is_private bool NOT NULL DEFAULT false,
created_by int NULL,
CONSTRAINT group_pk PRIMARY KEY (group_id),
CONSTRAINT group_user_fk FOREIGN KEY(created_by)
REFERENCES user (user_id) ON DELETE SET NULL
);
CREATE TABLE user_to_group (
user_id int NOT NULL,
group_id int NOT NULL,
user_type_id int NOT NULL,
is_blocked bool NOT NULL DEFAULT false,
CONSTRAINT user_to_group_pk PRIMARY KEY (user_id,group_id),
CONSTRAINT user_to_group_group_fk FOREIGN KEY(group_id)
REFERENCES `group` (group_id),
CONSTRAINT user_to_group_user_type_fk FOREIGN KEY(user_type_id)
REFERENCES user_type (id),
CONSTRAINT user_to_group_user_fk FOREIGN KEY(user_id)
REFERENCES user (user_id)
);
User Entity:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "username",
unique = true)
private String username;
#Column(name = "email_address",
unique = true)
private String emailAddress;
#Column(name = "phone_number")
private String phoneNumber;
#Column(name = "password")
private String password;
#Column(name = "notification_type",
insertable = false)
private String notificationType = "email";
#Column(name = "date_created")
private Date dateCreated;
#Column(name = "is_active",
insertable = false)
private Boolean active = false;
#OneToMany(
mappedBy = "user",
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST},
orphanRemoval = true
)
private List<UserGroup> groups;
}
Group Entity:
#Entity
#Table(name = "`group`")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "date_created")
private Date dateCreated;
#Column(name = "is_private",
insertable = false)
private Boolean privateG = false;
#OneToOne(fetch = FetchType.LAZY,
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST},
orphanRemoval = true)
#JoinColumn(name = "created_by")
private User createdBy;
#OneToMany(
mappedBy = "group",
cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST},
orphanRemoval = true
)
private List<UserGroup> users = new ArrayList<>();
}
UserGroup(join table):
#Entity
#Table(name = "user_to_group")
public class UserGroup {
#EmbeddedId
private UserGroupId id;
#ManyToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#MapsId("userId")
#JoinColumn(name = "user_id", insertable = false, updatable = false)
private User user;
#ManyToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#MapsId("groupId")
#JoinColumn(name = "group_id", insertable = false, updatable = false)
private Group group;
#Column(name = "is_blocked",
insertable = false)
private boolean isBlocked = false;
}
Ignore user_type_id field on the join table. If I delete an user with on the workbench, it works as expected(created_by field updates to NULL). But if i use this:
#Override
#Transactional
public User deleteUser(Long id) {
Optional<User> userToDelete = userRepository.findById(id);
userToDelete.ifPresent(user -> userRepository.delete(user));
return userToDelete.orElseThrow(() -> new UserNotFoundException("User not found"));
}
the entire row in the group table is deleted. What am I doing wrong?
#ManyToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#MapsId("groupId")
#JoinColumn(name = "group_id", insertable = false, updatable = false)
private Group group;
exclude CascadeType.REMOVE and group will be intact.
Related
Im learning Hibernate and cant solve problem.
Why Hibernate does delete in table "writer_post" after update entity Writer?
Table writer_post have writer_id and post_id entity(Writer, Post) and have annotation #MoreToOne.
What did I do wrong?
Update:
I updating existing Writer. Same in table existing row for Writer.
Example:
Writer: id: 1, first_name: Dmitry, last_name: Polischuk.
Writer_post: writer_id: 1, post_id: 1.
I does update parameter only last_name.
Create tables
CREATE TABLE IF NOT EXISTS Label(
id SERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE
);
CREATE TABLE IF NOT EXISTS Writer(
id SERIAL PRIMARY KEY,
first_name VARCHAR(100) NOT NULL,
last_name VARCHAR(100) NOT NULL
);
CREATE TABLE IF NOT EXISTS Post(
id SERIAL PRIMARY KEY,
content VARCHAR(100) NOT NULL,
create_date TIMESTAMP DEFAULT NULL,
updated_date TIMESTAMP DEFAULT NULL,
status VARCHAR(30) NOT NULL
);
CREATE TABLE IF NOT EXISTS Post_Label(
label_id INT NOT NULL,
post_id INT NOT NULL,
FOREIGN KEY (post_id) REFERENCES Post(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (label_id) REFERENCES Label(id) ON DELETE CASCADE ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS Writer_Post(
post_id INT NOT NULL,
writer_id INT NOT NULL,
FOREIGN KEY (post_id) REFERENCES Post(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (writer_id) REFERENCES Writer(id) ON DELETE CASCADE ON UPDATE CASCADE
);
Entity Writer
#Getter
#Setter
#RequiredArgsConstructor
#Entity
#Table(name = "Writer")
public class Writer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#ManyToOne(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
Entity Post
#Getter
#Setter
#RequiredArgsConstructor
#Entity
#Table(name = "Post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "Content")
private String content;
#Column(name = "create_date")
private Date created;
#Column(name = "updated_date")
private Date updated;
#Enumerated(EnumType.STRING)
#Column(name = "status")
private PostStatus status;
#ManyToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "post_label",
joinColumns = #JoinColumn(name = "post_id"),
inverseJoinColumns = #JoinColumn(name = "label_id"))
private List<Label> labels;
Code for update Writer
#Override
public Writer update(Writer writer) {
try(Session session = HibernateUtil.getSessionFactory().openSession()){
session.beginTransaction();
session.saveOrUpdate(writer);
session.getTransaction().commit();
return writer;
}
}
Result stacktrace
Hibernate: update Writer set first_name=?, last_name=? where id=?
Hibernate: delete from writer_post where writer_id=?
Your mapping looks like one post can be mapped to many writers. I guess you meant vice-versa. So, it's worth trying to change this:
#ManyToOne(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
to this:
#OneToMany(targetEntity = Post.class, cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
#JoinTable(name = "writer_post",
joinColumns = #JoinColumn(name = "writer_id"),
inverseJoinColumns = #JoinColumn(name = "post_id"))
private List<Post> posts;
I'm currently mapping a complex database schema wqith HIbernate and I have hit a wall with an entity which has a composite key with another composite key.
I have this table for roles with a composite key (site_id, id)
CREATE TABLE IF NOT EXISTS core.roles
(
id uuid NOT NULL DEFAULT gen_random_uuid(),
name character varying(100) NOT NULL,
is_system_role boolean NOT NULL DEFAULT FALSE,
site_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid NOT NULL,
updated_by uuid NOT NULL,
CONSTRAINT roles_pkey PRIMARY KEY (site_id, id),
CONSTRAINT roles_name_key UNIQUE (site_id, name),
CONSTRAINT roles_site_id_fkey FOREIGN KEY (site_id)
REFERENCES core.sites (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT roles_created_by_fkey FOREIGN KEY (created_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT,
CONSTRAINT roles_updated_by_fkey FOREIGN KEY (updated_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT
);
And I have this table with a composite key which also uses the previous one.
CREATE TABLE IF NOT EXISTS core.user_site_roles
(
user_id uuid NOT NULL,
site_id uuid NOT NULL,
role_id uuid NOT NULL,
created_at timestamp with time zone NOT NULL DEFAULT now(),
created_by uuid NOT NULL,
CONSTRAINT user_site_roles_pkey PRIMARY KEY (site_id, user_id, role_id),
CONSTRAINT user_site_roles_site_id_fkey FOREIGN KEY (site_id)
REFERENCES core.sites (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_site_roles_role_id_fkey FOREIGN KEY (site_id, role_id)
REFERENCES core.roles (site_id, id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE CASCADE,
CONSTRAINT user_site_roles_user_id_fkey FOREIGN KEY (user_id)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
CONSTRAINT user_site_roles_created_by_fkey FOREIGN KEY (created_by)
REFERENCES core.users (id) MATCH SIMPLE
ON UPDATE NO ACTION
ON DELETE RESTRICT
);
My current mapping for the roles one which is working is:
#Embeddable
public class CommonId implements Serializable {
#Type(type = "pg-id-uuid")
#Column(columnDefinition = "uuid", updatable = false)
private UUID id;
#Type(type = "pg-id-uuid")
#Column(name = "site_id", columnDefinition = "uuid", updatable = false)
private UUID siteId;
}
#Entity
#Table(name = "roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class Role extends AuditAtBy implements Serializable {
#EmbeddedId
private CommonId roleId;
#ManyToOne
#MapsId("siteId")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#Column(nullable = false, unique = true, length = 100)
private String name;
#Column(name = "is_system_role", nullable = false)
private boolean isSystemRole;
}
I was trying something similar with the composite Key for the UserSiteRole but Hibernate tells me that it needs to columns to map the roleId when in the table I have just the id but the PK is form by the two values as you can see in the script, not sure how to map it to be honest.
#Embeddable
public class UserSiteRoleId implements Serializable {
#Type(type = "pg-id-uuid")
#Column(columnDefinition = "uuid", updatable = false)
private UUID userId;
#Type(type = "pg-id-uuid")
#Column(name = "site_id", columnDefinition = "uuid", updatable = false)
private UUID siteId;
#Type(type = "pg-id-uuid")
#Column(name = "role_id", columnDefinition = "uuid", updatable = false)
private UUID roleId;
}
#Entity
#Table(name = "user_site_roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
public class UserSiteRole extends AuditCreated implements Serializable {
#EmbeddedId
private UserSiteRoleId userSiteRoleId;
#ManyToOne
#MapsId("userId")
#JoinColumn(name = "user_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne
#MapsId("siteId")
#JoinColumn(name = "site_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#ManyToOne
#MapsId("roleId")
#JoinColumn(name = "role_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Role role;
}
I would appreciate any ideas about how to map it, I had never had to map such a complex relationship so not sure how to proceed in this case.
Does this answer your question? jpa hibernate composite foreign key mapping
Actually that was useful as it made it clear that we could change the mapping from embeddedId to IdClass and make it work.
This is our new IdClass, pretty simple:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class UserSiteRoleId implements Serializable {
private User user;
private Site site;
private Role role;
}
And the entity itself working just inf e is as follows.
#Entity
#Table(name = "user_site_roles", schema = "core")
#Data
#TypeDefs({
#TypeDef(name = "pg-id-uuid", typeClass = PostgresIdUUIDType.class)
})
#IdClass(UserSiteRoleId.class)
public class UserSiteRole extends AuditCreated implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "user_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#Id
#ManyToOne
#JoinColumn(name = "site_id", columnDefinition = "uuid", nullable = false)
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Site site;
#Id
#ManyToOne
#JoinColumns({
#JoinColumn(name = "role_id", referencedColumnName="id", columnDefinition = "uuid", insertable = false, updatable = false),
#JoinColumn(name = "site_id", referencedColumnName="site_id", columnDefinition = "uuid", insertable = false, updatable = false)
})
#Type(type = "pg-id-uuid")
#OnDelete(action = OnDeleteAction.CASCADE)
private Role role;
}
I have 2 tables employee and employee_document. Here is the mysql query for two tables -
CREATE TABLE employee (
id int(11) unsigned NOT NULL,
name varchar(100) COLLATE utf8_unicode_ci NOT NULL,
email varchar(50) COLLATE utf8_unicode_ci NOT NULL,
password_hash varchar(100) COLLATE utf8_unicode_ci NOT NULL,
status int(11) NOT NULL,
creation_date bigint(20) NOT NULL,
created_by int(11) DEFAULT NULL,
update_date bigint(20) NOT NULL,
updated_by int(11) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
CREATE TABLE employee_document (
id int(11) unsigned NOT NULL AUTO_INCREMENT,
employee_id int(11) unsigned NOT NULL,
file_id int(11) unsigned NOT NULL,
document_type varchar(50) COLLATE utf8_unicode_ci DEFAULT '',
status int(11) DEFAULT NULL,
creation_date bigint(20) DEFAULT NULL,
created_by int(11) unsigned NOT NULL,
update_date bigint(20) DEFAULT NULL,
updated_by int(11) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
In my spring boot project I used hibernate with JPA Data. Here are the java interpretation of these tables.
#MappedSuperclass
public abstract class AbstractTimestampEntity {
#Transient
Logger log = LoggerFactory.getLogger(AbstractTimestampEntity.class);
#Column(name = "creation_date", nullable = false, updatable=false)
private Long creationDate;
#Column(name = "update_date", nullable = false)
private Long updateDate;
#PrePersist
protected void onCreate() {
log.debug("onCreate");
updateDate = creationDate = System.currentTimeMillis();
}
#PreUpdate
protected void onUpdate() {
log.debug("onUpdate");
updateDate = System.currentTimeMillis();
}
}
#Entity
#Table(name = "employee")
public class Employee extends AbstractTimestampEntity implements Serializable {
private static final long serialVersionUID = 1L;
public static final Integer STATUS_INACTIVE = 0;
public static final Integer STATUS_ACTIVE = 1;
public static final Integer STATUS_ARCHIVED = -1;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer id;
#Column(name = "name", nullable = false)
public String name;
#Email
#Column(name = "email", nullable = false, unique = true)
public String email;
#JsonIgnore
#Column(name = "password_hash", nullable = false)
public String passwordHash;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee", fetch = FetchType.EAGER)
private Set<EmployeeDocument> documents;
#Max(1)
#Min(-1)
#Column(name = "status", nullable = false )
public Integer status;
#Column(name = "created_by", updatable = false)
public Integer createdById;
#Column(name = "updated_by")
public Integer updatedById;
}
#Entity
#Table(name = "employee_document")
public class EmployeeDocument extends AbstractTimestampEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Transient
public static final Integer STATUS_INACTIVE = 0;
#Transient
public static final Integer STATUS_ACTIVE = 1;
#Transient
public static final Integer STATUS_ARCHIVED = -1;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Integer id;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
public Employee employee;
#OneToOne
#JoinColumn(name = "file_id")
public EmployeeFile employeeFile;
#Max(1)
#Min(-1)
#Column(name = "status", nullable = false )
public Integer status;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "created_by", updatable = false)
#JsonBackReference
public Employee createdBy;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "updated_by")
#JsonBackReference
public Employee updatedBy;
}
This code does not execute, the app fails to start and throws DuplicateMappingException. Here's the full exception stack -
org.hibernate.DuplicateMappingException: Table [employee_document] contains physical column name [created_by] referred to by multiple physical column names: [createdBy], [created_by]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$TableColumnNameBinding.bindPhysicalToLogical(InFlightMetadataCollectorImpl.java:922)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl$TableColumnNameBinding.addBinding(InFlightMetadataCollectorImpl.java:891)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.addColumnNameBinding(InFlightMetadataCollectorImpl.java:961)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.addColumnNameBinding(InFlightMetadataCollectorImpl.java:942)
at org.hibernate.cfg.Ejb3Column.addColumnBinding(Ejb3Column.java:407)
at org.hibernate.cfg.Ejb3Column.linkWithValue(Ejb3Column.java:369)
at org.hibernate.cfg.annotations.SimpleValueBinder.linkWithValue(SimpleValueBinder.java:431)
at org.hibernate.cfg.annotations.SimpleValueBinder.make(SimpleValueBinder.java:407)
at org.hibernate.cfg.annotations.PropertyBinder.makePropertyAndValue(PropertyBinder.java:187)
at org.hibernate.cfg.annotations.PropertyBinder.makePropertyValueAndBind(PropertyBinder.java:199)
at org.hibernate.cfg.AnnotationBinder.processElementAnnotations(AnnotationBinder.java:2225)
at org.hibernate.cfg.AnnotationBinder.processIdPropertiesIfNotAlready(AnnotationBinder.java:911)
at org.hibernate.cfg.AnnotationBinder.bindClass(AnnotationBinder.java:738)
at org.hibernate.boot.model.source.internal.annotations.AnnotationMetadataSourceProcessorImpl.processEntityHierarchies(AnnotationMetadataSourceProcessorImpl.java:245)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess$1.processEntityHierarchies(MetadataBuildingProcess.java:222)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:265)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:847)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:874)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:60)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:353)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:373)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:362)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
... 19 common frames omitted
What am I doing wrong? Thanks in advance.
You are trying to reference the Empolyee three times from the EmployeeDocument but only one of those is by primary key. The other two are referenced by non-primary key columns and you would need to use the referencedColumnName option additionally to make this work:
#JsonIgnore
#ManyToOne
#JoinColumn(name = "employee_id", nullable = false)
public Employee employee;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "created_by", referencedColumnName="created_by", updatable = false)
#JsonBackReference
public Employee createdBy;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "updated_by". , referencedColumnName="updated_by")
#JsonBackReference
public Employee updatedBy;
I'm doing Spring Boot project and use spring-boot-jpa (Hibernate implementation). I'm having trouble configuring following relation between entities.
Let's assume I need many-to-one (and reversly one-to-many) relation between two tables (MySQL in this example, table1 logically stores description for codes in various other tables) :
CREATE TABLE `table1` (
`id` INT NOT NULL AUTO_INCREMENT,
`ref_table` VARCHAR(50) NOT NULL,
`ref_column` VARCHAR(50) NOT NULL,
`code` VARCHAR(10) NOT NULL,
`description` VARCHAR(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `u_composite1` (`ref_table` ASC, `ref_column` ASC, `code` ASC));
CREATE TABLE `table2` (
`id` INT NOT NULL AUTO_INCREMENT,
`field1` VARCHAR(100) NULL,
`code` VARCHAR(10) NOT NULL,
PRIMARY KEY (`id`));
The way I join these two tables in SQL is like this:
SELECT t2.*, t1.description
FROM table2 t2
JOIN table1 t1
ON ( t1.ref_table = 'table2'
AND t1.ref_column = 'code'
AND t1.code = t2.code);
So, I created entities like this (minus the getters an setters):
#Entity
#Table(name = "table1")
public class Table1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private int id;
#Column(nullable = false, length = 10)
private String code;
#Column(length = 100)
private String description;
#Column(name = "ref_column", nullable = false, length = 50)
private String refColumn;
#Column(name = "ref_table", nullable = false, length = 50)
private String refTable;
#OneToMany(mappedBy = "table1")
private List<Table2> table2;
}
#Entity
#Table(name = "table2")
public class Table2 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(unique = true, nullable = false)
private int id;
#Column(nullable = false, length = 45)
private String field1;
#ManyToOne(fetch=FetchType.LAZY)
#Column(name = "code")
#JoinColumns({
#JoinColumn(name = "code", referencedColumnName = "code", nullable = false, updatable = false),
#JoinColumn(name = "'table2'", referencedColumnName = "ref_table", nullable = false, updatable = false),
#JoinColumn(name = "'code'", referencedColumnName = "ref_column", nullable = false, updatable = false)
})
private Table1 table1;
}
But it doesn't work. :(
Can this kind of relation even be defined in JPA?
If so, please, how?
Pertaining to the "join with constant values" problem I managed to make it work using the #Where Hibernate annotation:
How to replace a #JoinColumn with a hardcoded value?
#Entity
#Table(name = "a")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public long id;
#OneToMany
#JoinColumn(name = "id", referencedColumnName = "id")
#Where(clause = "blah = 'CONSTANT_VALUE'")
public Set<B> b;
protected A() {}
}
#Entity
#Table(name = "b")
public class B {
#Id
#Column(nullable = false)
public Long id;
#Column(nullable = false)
public String blah;
protected B() {}
}
I have a tables called Category and CategorySet.
CREATE TABLE CATEGORY
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(40) NOT NULL,
description VARCHAR(255),
uuid VARCHAR(40)
);
CREATE TABLE CATEGORY_SET
(
id INT NOT NULL PRIMARY KEY AUTO_INCREMENT,
parent INT NOT NULL,
child INT NOT NULL UNIQUE,
FOREIGN KEY (parent) REFERENCES CATEGORY (id),
FOREIGN KEY (child) REFERENCES CATEGORY (id)
);
Category.java
#Entity
#Table(name = "CATEGORY")
public class Category implements Serializable{
#Id
#Column(name = "ID")
#GeneratedValue
private Integer id;
#Column(name = "CATEGORY_NAME", nullable = false, length = 40)
private String name;
#Column(name = "DESCRIPTION", nullable = false, length = 255)
private String description;
#Column(name = "UUID", nullable = false, length = 40)
private String uuid;
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "CATEGORY_SET", joinColumns = {#JoinColumn(name = "PARENT")}, inverseJoinColumns = {#JoinColumn(name = "CHILD")})
private Collection<Category> subCategories = new LinkedHashSet<Category>();
public Category(String name, String description, String uuid, Collection<Category> categorySets) {
this.name = name;
this.description = description;
this.uuid = uuid;
this.categorySets = categorySets;
}
}
I want to get all root categories which means they are not child category of any.
sql to get the result is:
SELECT DISTINCT CATEGORY.id
FROM CATEGORY
JOIN CATEGORY_SET
ON CATEGORY.id = CATEGORY_SET.parent
WHERE CATEGORY.id NOT IN (SELECT child
FROM CATEGORY_SET);
Can someone help me write HQL for this.
The HQL query is:
select c
from Category c
where c.id not in (select child from CategorySet)