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

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.

Related

JPA OneToOne and ManyToOne relationship with same entity

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)
)

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.

JPA #JoinTable - Three ID Columns

I have a situation that is quite similar to the one outlined in this question's diagram: JPA. JoinTable and two JoinColumns, although with different issues.
I have three tables: Function, Group, and Location. Currently, I have a join table set up between Location and Group using #JoinTable. It is #ManyToMany on both sides, and works perfectly fine.
I am attempting to add the constraint that no Location should be associated with more than one Group that has the same Function. So I added a column for Function to my join table in my SQL schema and a uniqueness constraint across the Location and Function columns, like so:
create table function_table (
id varchar(50),
primary key(id)
);
create table group_table (
id varchar(50),
function_id varchar(50) not null,
primary key(id)
);
alter table group_table add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
create table location_table (
id varchar(50),
primary key(id)
);
create table group_location_join (
location_id varchar(50) not null,
group_id varchar(50) not null,
function_id varchar(50) not null,
primary key(location_id, group_id, function_id),
unique(location_id, function_id)
);
alter table group_location_join add constraint FK_TO_LOCATION foreign key (location_id) references location_table;
alter table group_location_join add constraint FK_TO_GROUP foreign key (group_id) references group_table;
alter table group_location_join add constraint FK_TO_FUNCTION foreign key (function_id) references function_table;
I then attempted to set up the following in my model entities:
#Entity
#Table(name = "function_table")
public class Function {
#Id
#Column(name = "id", length = 50)
private String id;
}
#Entity
#Table(name = "group_table")
public class Group {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToOne
#JoinColumn(name = "function_id", referencedColumnName = "id", nullable = false)
private Function function;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")},
inverseJoinColumns = #JoinColumn(name="location_id", referencedColumnName = "id"))
private Set<Location> locations;
}
#Entity
#Table(name = "location_table")
public class Location {
#Id
#Column(name = "id", length = 50)
private String id;
#ManyToMany
#JoinTable(name = "group_location_join",
joinColumns = #JoinColumn(name="location_id", referencedColumnName = "id")
inverseJoinColumns = {#JoinColumn(name = "group_id", referencedColumnName = "id"),
#JoinColumn(name = "function_id", referencedColumnName = "function_id")})
private Set<Group> groups;
}
(Obviously, there is more to these entities, but I stripped them down to only the parts relevant to this question.)
This does not work. When I write a simple test to create a Location associated with a Group that is associated with a Function, the minute I try to flush the session to commit the transaction, Hibernate gives me this:
java.lang.ClassCastException: my.package.Group cannot be cast to java.io.Serializable
I think what's happening is that Hibernate is getting confused, throwing up its hands, and saying "I'll just serialize it, send it to the database, and hope it knows what's going on."
When I add implements Serializable and add a serialVersionUID to Group, I then get this:
org.hibernate.exception.SQLGrammarException: user lacks privilege or object not found: FUNCTION_ID
I'm not really sure how to proceed at this point, or if perhaps I have already proceeded too far down the wrong path. Maybe I'm not thinking about the SQL correctly, and there is a much easier way to ensure this constraint that doesn't involve all this ridiculousness.
Edit: In my system, the DAOs for the tables involved have no save capabilities. Which means that as long as my constraint is set up in the database, my application doesn't care; it can't insert things that violate the constraint because it can't insert things at all.
Edit 2: I never originally solved the stated problem, and instead simply added a third column in my database schema without touching the Java code, as stated in my first Edit section above. But I have since experimented with creating an explicit join table object with an #Embedded compound key, and it seems to work.
You are trying to create a composite primary key. In Hibernate you can do it using the #Embeddable annotation. In the example below you can find the way to use a composite key for two entities.
I believe you can move forward with this example and create your own version of primary key.
Mapping ManyToMany with composite Primary key and Annotation:

Categories