JPA OneToOne and ManyToOne relationship with same entity - java

I'm new in Java world, and JPA. I have a problem with OneToMany relationships.
So I have two entities: UserEntity and ManagerEntity.
The Manager is a User that was promoted, so here we have OneToOne relationship where manager_uuid in managers table reference uuid in the users table. This relationship works fine.
Now each Manager has many clients => UserEntity and it is mapped by managers table.
The problem is that it keeps looking for client_uuid in the users table instead of managers and I don't know why...
#Entity
#Table(name = "users")
public class UserEntity {
#Id
#GeneratedValue(strategy = AUTO)
private UUID uuid;
#OneToOne
#JoinColumn(
name = "manager_uuid",
referencedColumnName = "uuid")
private ManagerEntity managerReference;
#ManyToOne
#JoinColumn(name = "client_uuid",
referencedColumnName = "uuid")
private ManagerEntity manager;
}
#Entity
#Table(name = "managers")
public class ManagerEntity {
#Id
#GeneratedValue(strategy = AUTO)
private UUID uuid;
#OneToOne(mappedBy = "managerReference")
private UserEntity actingМanager;
#OneToMany(mappedBy = "manager")
private List<UserEntity> clients = new ArrayList<>();
}
Migration:
CREATE TABLE IF NOT EXISTS managers
(
uuid uuid DEFAULT gen_random_uuid() PRIMARY KEY,
manager_uuid uuid NOT NULL,
client_uuid uuid NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
FOREIGN KEY (manager_uuid) REFERENCES users (uuid),
FOREIGN KEY (client_uuid) REFERENCES users (uuid)
)
The error:
ERROR: column userentity0_.client_uuid does not exist
Any idea what I'm doing wrong?

It's working as expected. The client_uuid is something which needs to be in user table. As one manager will be having multiple clients, you can't insert multiple client_uuid in manager table single field. But when it comes the other way around than one user will be having only one manager. So they can store the manager_uuid easily.
You can try make your code like below:
CREATE TABLE IF NOT EXISTS managers
(
uuid uuid DEFAULT gen_random_uuid() PRIMARY KEY,
manager_uuid uuid NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
FOREIGN KEY (manager_uuid) REFERENCES users (uuid)
)
CREATE TABLE IF NOT EXISTS users
(
uuid uuid DEFAULT gen_random_uuid() PRIMARY KEY,
client_uuid uuid NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
FOREIGN KEY (client_uuid) REFERENCES managers(uuid)
)

Related

JPA composite key (FKs + date) set date to auto_increment

I am working on a Java play project and trying to create a time tracker for users on projects. I have the following class for tracking time:
#Entity(name = "trackings")
public class Tracking extends Model {
#EmbeddedId
public TrackingPK id;
#ManyToOne
#MapsId("user")
#JoinColumn(name="user", referencedColumnName="id")
public User user;
#ManyToOne
#MapsId("project")
#JoinColumn(name="project", referencedColumnName="id")
public Project project;
#Required
#Enumerated(EnumType.STRING)
public ETrackingDuration duration;
#Embeddable
public class TrackingPK implements Serializable {
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id", insertable = false, updatable = false)
private User user;
#ManyToOne
#JoinColumn(name = "project_id", referencedColumnName = "id", insertable = false, updatable = false)
private Project project;
#Column(name="date", columnDefinition="DATE NOT NULL")
#Temporal(TemporalType.DATE)
private Date date;
}
}
Unfortunately, JPA keeps making date as an auto_increment and I have no idea why.
Here are my logs:
09:14:16,939 ERROR ~ HHH000388: Unsuccessful: create table trackings (date datetime not null auto_increment, project_id bigint not null, user_id bigint not null, id bigint not null, duration varchar(255), primary key (date, project_id, user_id, id)) ENGINE=InnoDB DEFAULT CHARSET=utf8
09:14:16,939 ERROR ~ Incorrect column specifier for column 'date'
09:14:16,941 ERROR ~ HHH000388: Unsuccessful: alter table trackings add index FK_fjady3y2u9ej76atpce4djsu5 (project_id), add constraint FK_fjady3y2u9ej76atpce4djsu5 foreign key (project_id) references projects (id)
09:14:16,941 ERROR ~ Table 'trackings' doesn't exist
09:14:16,944 ERROR ~ HHH000388: Unsuccessful: alter table trackings add index FK_rdi49oyr8mro9t6vjj5towo0b (user_id), add constraint FK_rdi49oyr8mro9t6vjj5towo0b foreign key (user_id) references users (id)
Foreign keys are correct but not date...
There are a couple of issues with the mapping that I can see:
You need #MapsId("user") and #MapsId("project") on Tracking.user and Tracking.project, respectively
The #Column annotations on TrackingPK.user and TrackingPK.project are redundant with #MapsId in place (you may actually run into problems if you define the same column twice in JPA, without declaring it insertable=false, updatable=false in one of the definitions)
JPA allows java.util.Date as long as the temporal type is set to TemporalType.DATE (see: https://docs.oracle.com/javaee/7/tutorial/persistence-intro001.htm; your JPA provider might allow it, though, but you would need to check)
As regards the schema definition problem, if all else fails, try using columnDefinition="date not null" to override the default SQL for that column.
EDIT It turns out that play.db.jpa.Model defines an #Id column. Mixing #Id and #EmbeddedId is what causes the problem. You'll want to extend from play.db.jpa.GenericModel instead.

HIbernate Can't delete Entity with foreign key. Foreign key gets set to null

This question has been asked in many forms here but none of the solutions seem to work for me. I'm trying to delete the parent entity and I want all of the child entities to also be deleted.
My entities:
#Entity
#Table(name = "item", catalog = "myshchema")
public class Item implements java.io.Serializable {
#JoinColumn(name = "item_id", insertable = false, updatable = false, nullable = false)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
private Set<ItemCategory> categories;
/* Getters and Setters and other fields*/
}
Table for Item:
CREATE TABLE `item` (
`item_id` int(11) NOT NULL AUTO_INCREMENT,
`store_id` int(11) NOT NULL,
PRIMARY KEY (`item_id`),
UNIQUE KEY `item_id_UNIQUE` (`item_id`),
KEY `FK_ITEM_STORE_ID_idx` (`store_id`),
CONSTRAINT `FK_ITEM_STORE_ID` FOREIGN KEY (`store_id`) REFERENCES `store` (`store_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=84 DEFAULT CHARSET=utf8;
And my other entity
#Entity
#Table(name = "item_category", catalog = "myschema")
#IdClass(ItemCategoryIndex.class)
public class ItemCategory implements java.io.Serializable {
#Id
#Column(name = "category_id", unique = true, nullable = false, insertable = false, updatable = false)
private Integer categoryId;
#Id
private Store store;
#Id
private Item item;
#Id
private String categoryName;
/* Getters and Setters */
}
Table for ItemCategory:
CREATE TABLE `item_category` (
`category_id` int(11) NOT NULL AUTO_INCREMENT,
`store_id` int(11) NOT NULL,
`item_id` int(11) NOT NULL,
`category_name` varchar(45) NOT NULL,
PRIMARY KEY (`category_id`),
UNIQUE KEY `category_id_UNIQUE` (`category_id`),
UNIQUE KEY `IDX_UNIQUE_STORE_CATEGORY` (`store_id`,`item_id`,`category_name`) USING BTREE,
KEY `FK_CATEGORY_STORE_ID_idx` (`store_id`),
KEY `FK_ITEM_CATEGORY_ID_idx` (`item_id`),
CONSTRAINT `FK_CATEGORY_STORE_ID` FOREIGN KEY (`store_id`) REFERENCES `store` (`store_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `FK_ITEM_CATEGORY_ID` FOREIGN KEY (`item_id`) REFERENCES `item` (`item_id`) ON DELETE CASCADE ON UPDATE NO ACTION
) ENGINE=InnoDB AUTO_INCREMENT=162 DEFAULT CHARSET=utf8;
I try to delete the item like this:
Item item = entityManager.find(Item.class, idList.get(i));
entityManager.remove(item);
My logs show that Hibernate is trying to set the primary key for ItemCategory to null:
Hibernate: update myschema.item_category set item_id=null where item_id=?
ERROR o.h.e.jdbc.spi.SqlExceptionHelper.logExceptions 146 - Column 'item_id' cannot be null
I even tried looping through the child records and deleting them manually, but Hibernate still issues this update to null query. What am I doing wrong?
I have to break your problem down to two parts
First - let's talk about your database schema design.
According to your schema, item and item_category has a one-to-many relationship meaning an item can have/be-assigned-to different categories but different items cannot have/be-assigned-to the same category.
That is totally fine if it is indeed your business requirement, I mention it because it does not make sense to me and this circumstance rarely happens.
If what you want is that a category can have multiple items and vice versa, itemand item_category must be a many-to-many relationship. There should be a join table additionally.
Second - let's say the schema don't change
ItemCategory is the owner of the relationship because it has a foreign key item_id refering to item table. So the ItemCategoy should look roughly like this:
#Entity
#Table(name = "item_category")
public class ItemCategory {
#Id
private Integer categoryId;
private Store store;
#ManyToOne
#JoinColumn(name="item_id", /*cascade = ...*/)
private Item item;
private String categoryName;
/* Getters and Setters */
}
Your Item entity will be roughly like this:
#Entity
#Table(name = "item", catalog = "myshchema")
public class Item implements java.io.Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true, mappedBy="item")
private Set<ItemCategory> categories; //`mappedBy`used here because this entity is not the owner of the relationship according to what mentioned above
/* Getters and Setters and other fields*/
}
To remove all the child entities(ItemCategory) from Item , simply
em.remove(item);
The orphanRemoval is true, deleting the parent, the children will be deleted as well.
In Hibernate, you need to decide who is owning the relationship. If you have the parent side (ItemCategory) owning the relationship, you will find insertion/deletion of Item+ ItemCategory will involve update of item_id in ItemCategory table (which is what I observed from your exception). In most case it is not preferable. We usually let the children own the relationship. This is done by using mappedBy
(pseudo-code)
class Item {
//...
#OneToMany(mappedBy = "item", cascade=ALL, orphanRemoval=true)
private Set<ItemCategory> categories;
}
class ItemCategory {
//...
#ManyToOne
#JoinColumn(name="item_id")
Item item;
}
The trick here is mappedBy

Hibernate: #OneToOne not producing "one to one" relationship in database

I'm relatively new to JPA and Hibernate and am trying to see how the #OneTo One annotation works, let's say I have an entity "Task" with the following relation:
#OneToOne
#JoinColumn(name = "manager_id")
private Manager manager;
And there's the entity "Manager":
#Entity
#Table(name = "manager")
public class Manager {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
public Manager() {
}
When I run the test file along with the "hibernate.hbm2ddl.auto" set to "update" I get a Many to One relation in the database (as you can see, there is no unique constraint of any kind that'd make it a one to one relation):
CREATE TABLE IF NOT EXISTS `timesheet`.`task` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`completed` BIT(1) NOT NULL,
`description` VARCHAR(255) NULL DEFAULT NULL,
`manager_id` BIGINT(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`),
INDEX `FK3635851B178516` (`manager_id` ASC),
CONSTRAINT `FK3635851B178516`
FOREIGN KEY (`manager_id`)
REFERENCES `timesheet`.`manager` (`id`))
ENGINE = InnoDB
DEFAULT CHARACTER SET = utf8;
To be sure of this I tried adding two records with the same manager id and were indeed added, I also tried setting the unique constraint like "#Table(name = "Task",uniqueConstraints = #UniqueConstraint(columnNames =..." but no luck.
So Why is this happening and what's exactly the pros of using #OneToOne annotaion if no application logic is applied to validate this?
Also, Is there any chance that Hibernate is not able to do the DDL generation properly?
(I know that generation of schemas through hibernate is only meant for testing)
In a unidirectional relationship you will get the expected unique constraint if you mark it as "optional=false". You also get it if you set the join column explicitly as unique, of course.
So either
#OneToOne(optional=false)
#JoinColumn(name = "manager_id")
private Manager manager;
or
#OneToOne
#JoinColumn(name = "manager_id", unique=true)
private Manager manager;
So why do you need to mark it as not optional?
My guess is that, when a value is optional, the column can contain many null values, but in many databases this can not be done when a unique constraint is present. You can do it in MySQL though, so maybe the Hibernate generator is not taking the database into account in this case (a bug?).
See a discussion about MySQL handling of nulls here.
I had this issue too and I just needed to add the referenced column so I can get a generated table:
#Entity(name = "news")
public class News extends BaseEntity {
#Column(length = 500)
private String title;
#Column(length = 2000)
private String description;
#OneToOne(optional = false, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "file_id", referencedColumnName = "id", unique = true)
private Picture picture;
}

How to set the foreign key as a primary key in java hibernate

I am using java and hibernate annotations to define the database schema and want to specify the foreign key in one table as the primary key.
I am getting an error when I set this up and think it could be down to how I am setting up the foreign key as the primary key because when I use a normal primary key I don't get an error.
What is the correct way to set up a foreign key as the primary key?
My current code set up is :
#Table(name="BATCH_STEP_EXECUTION_CONTEXT")
public class BatchStepExecutionContext implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JoinColumn(name = "STEP_EXECUTION_ID" , columnDefinition="BIGINT NOT NULL", referencedColumnName="STEP_EXECUTION_ID")
#ForeignKey(name="STEP_EXEC_CTX_FK ")
#IndexColumn(name="IDX_STEP_EXEC_CTX")
private BatchStepExecution batchStepExecution;
and is referenced by the Batch Step Execution table as:
// bi-directional many-to-one association to Batch Step Execution Context
#OneToMany(mappedBy = "batchStepExecution", cascade = {CascadeType.ALL})
private List<BatchStepExecutionContext> batchStepExecutionContext;
the error I'm getting when I try to run the code is:
Unable to read the mapped by attribute for batchStepExecutionContext in com.ccs.nbook.domain.model.BatchStepExecutionContext!
The tables I'm trying to model in the java code are:
CREATE TABLE BATCH_STEP_EXECUTION
(
STEP_EXECUTION_ID BIGINT NOT NULL GENERATED BY DEFAULT AS IDENTITY (MAXVALUE 9223372036854775807),
VERSION BIGINT NOT NULL,
STEP_NAME VARCHAR (100) NOT NULL,
JOB_EXECUTION_ID BIGINT NOT NULL,
STATUS VARCHAR (10),
COUNT BIGINT,
CONSTRAINT JOB_EXEC_STEP_FK FOREIGN KEY (JOB_EXECUTION_ID) REFERENCES BATCH_JOB_EXECUTION (JOB_EXECUTION_ID),
PRIMARY KEY (STEP_EXECUTION_ID)
)
;
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT
(
STEP_EXECUTION_ID BIGINT NOT NULL,
SHORT_CONTEXT VARCHAR (2500) NOT NULL,
SERIALIZED_CONTEXT LONG VARCHAR,
PRIMARY KEY (STEP_EXECUTION_ID),
CONSTRAINT STEP_EXEC_CTX_FK FOREIGN KEY (STEP_EXECUTION_ID) REFERENCES BATCH_STEP_EXECUTION (STEP_EXECUTION_ID)
)
;
So I am trying to model the relationship of STEP_EXECUTION_ID between both tables where it is a primary key in BATCH_STEP_EXECUTION and is a primary key and foreign key in BATCH_STEP_EXECUTION_CONTEXT
Not pretty sure you mean with
want to specify the foreign key in one table as the primary key.
Don't get exactly what you mean... this foreign key is already primary key, right?
Anyway, to know what's going on (and not only in this case), read the Exception:
Caused by: org.hibernate.MappingException: Unable to read the mapped by attribute for batchJobExecutionContext in com.domain.model.BatchJobExecutionContext!
It says: There's a missing mappedby in BatchJobExecutionContext!
How to fix it? Reading this answer with a simple example:
#Entity
public class Company {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "company")
private List<Branch> branches;
}
#Entity
public class Branch {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "companyId")
private Company company;
}
I can see you are missing the #ManyToOne side of the relation #OneToMany and it's mappedby attribute. As long you used: #OneToMany(mappedBy = "batchStepExecution", cascade = {CascadeType.ALL}) you must add #ManyToOne anottation in the other side. So your code need to be like this:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "STEP_EXECUTION_ID" , columnDefinition="BIGINT NOT NULL", referencedColumnName="STEP_EXECUTION_ID")
#ForeignKey(name="STEP_EXEC_CTX_FK ")
#IndexColumn(name="IDX_STEP_EXEC_CTX")
private BatchStepExecution batchStepExecution;
NOTES:
Check this tutorial for further info

Add a foreign key to composite primary key and changes in JPA Entity class

I have a class User with primary key (id) which corresponds to 'user' table in SQL Server database. My User class has a many to one relationship with Project Entity.
public class User{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#JoinColumn(name = "project", referencedColumnName = "id_project")
#ManyToOne(optional = false)
private Project project;
}
database:
CREATE TABLE [dbo].[user](
[id] [int] IDENTITY(27,1) NOT NULL,
[name] [varchar](45) NULL,
[project] [int] NOT NULL,
CONSTRAINT [PK_user_1] PRIMARY KEY CLUSTERED
(
[id] ASC)
Now in my database I need to have two rows with same user ids but different projects so I changed my primary key to a composite primary key in my user table.
user table: id name id_project
---------------------------
1 John 5
1 John 6
project table: id name
---------------
5 Project5
6 Project6
and changed my table like this
CREATE TABLE [dbo].[user](
[id] [int] IDENTITY(27,1) NOT NULL,
[name] [varchar](45) NULL,
[project] [int] NOT NULL,
CONSTRAINT [PK_user_1] PRIMARY KEY CLUSTERED
(
[id] ASC,
[project_mode] ASC)
My question is that: Was this step required in my table? and if it was, then how can I change the #id of my User class?
JPA 2.1 allows derived IDs, allowing you to mark relationships as being part of the ID. User would look like this:
#Entity
#IdClass(UserPK.class)
public class User{
#Id
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Id
#JoinColumn(name = "project", referencedColumnName = "id_project")
#ManyToOne(optional = false)
private Project project;
}
public class UserPK{
private Integer id;
private Integer project;//use the same type as the Project's ID.
}
I'd seriously recommend to have a look at the normal forms of relational databases. Especially the Third Normal Form.
In this case I'd keep the table user without the column id_project. Instead you can create a third table user_project with the column id_user and id_project, both part of the primary key. This is the usual way to model an n:m relationship in a relational database.

Categories