I have a user entity identified by two natural ids, something like
#Entity
#Table(name = "user", uniqueConstraints =
{ #UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "nick") })
public User()
{}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private id;
#Column(name = "email", unique = true, nullable = false, length = 31)
#NaturalId(mutable = true)
private String email;
#Column(name = "nick", unique = true, nullable = false, length = 31)
#NaturalId(mutable = false)
private String nick;
However, when I try to execute
session.byNaturalId(User.class).with(LockOptions.READ).using("email", "admin#mail.com").load();
it throws an exception
org.hibernate.HibernateException: Entity [pervasive.com.gmail.tigerjack89.forum.shared.model.entities.User] defines its natural-id with 2 properties but only 1 were specified
at org.hibernate.event.spi.ResolveNaturalIdEvent.<init>(ResolveNaturalIdEvent.java:75)
at org.hibernate.event.spi.ResolveNaturalIdEvent.<init>(ResolveNaturalIdEvent.java:52)
at org.hibernate.internal.SessionImpl$BaseNaturalIdLoadAccessImpl.resolveNaturalId(SessionImpl.java:2607)
at org.hibernate.internal.SessionImpl$NaturalIdLoadAccessImpl.load(SessionImpl.java:2722)
at pervasive.com.gmail.tigerjack89.forum.server.model.orm.StorageManager.getByNaturalId(StorageManager.java:217)
at pervasive.com.gmail.tigerjack89.test.local.MyHibernateTest.test1(MyHibernateTest.java:37)
at pervasive.com.gmail.tigerjack89.test.local.MyHibernateTest.main(MyHibernateTest.java:23)
Why is this? I think it's also due to the log of the SQL syntax generated by Hibernate. Indeed, it is strange (redundant) at this point and I think it's the cause of the exception
Hibernate:
alter table user
add constraint UK_t8tbwelrnviudxdaggwr1kd9b unique (email, nick)
Hibernate:
alter table user
add constraint UK_ob8kqyqqgmefl0aco34akdtpe unique (email)
Hibernate:
alter table user
add constraint UK_pvnbxcfihb58o5n2n1fnc7fh1 unique (nick)
EDIT: Reading the code again, I thought that the problem could be related to the #UniqueConstraints annotations. However, even if I try to remove one of them, Hibernate continues to genetate the above SQL syntax.
I would make the email address alone the NaturalId and then your query would work.
When two columns are identified as the NaturalId it creates a Composite Key.
Nick could still be used as a foreign Key.
You've a composite naturalId key (email,nick) so you can have multiples results with simple email arg.
you've to use
session
.byNaturalId(User.class)
.with(LockOptions.READ)
.using("email", "admin#mail.com")
.using("nick", "admin")
.load();
You can also use Metamodel
session
.byNaturalId(User.class)
.with(LockOptions.READ)
.using(User_.email.getName(), "admin#mail.com")
.using(User_.nick.getName(), "admin")
.load();
Related
Something very bizarre have been happening. I have a very simple Entity recipe like so
#Data
#Entity
#Table(name = "recipe", schema = "public")
#AllArgsConstructor
#NoArgsConstructor
public class Recipe {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "name")
private String name;
#Column(name = "instructions")
private String instructions;
#Column(name = "date_added", nullable = false)
private String dateAdded;
#Column(name = "last_edited", nullable = false)
private String lastEdited;
}
and I have this post service that should post the 4 string attribute to the database
public void postRecipe(Recipe recipe){
var sql = """
INSERT INTO public.recipe ("name","instructions","date_added","last_edited")
VALUES (?, ?, ?, ?)
""";
jdbcTemplate.update(
sql,
recipe.getName(),
recipe.getInstructions(),
recipe.getDateAdded(),
recipe.getLastEdited()
);
}
However when the following jason is sent using postman, I get the null value error.
{
"name":"test",
"instructions":"don't eat",
"date_added":"03/04/2017",
"last_edited":"03/04/2017"
}
ERROR: null value in column \"date_added\" of relation \"recipe\" violates not-null constraint\n Detail: Failing row contains (3, null, don't eat, null, test)
The strangest thing is that only the "name" and "instruction" columns receive their data and not the other columns. I have tried adding another String attribute to the Entity class and it also cannot get data from the jason.
Edit 1:
I have tried adding the data directly through pgadmin and it worked fine
INSERT INTO recipe (name, instructions, date_added, last_edited)
VALUES ('test', 'test instruction', '2020/03/05', '2020/05/08');
It looks like your deserialization is broken - transforming your JSON into the Java entity, which results in some null values present. Most likely because date_added != dateAdded (field name), and Jackson cannot properly set a value.
I recommend having a look at Jackson guide: https://www.baeldung.com/jackson-annotations, #JsonProperty specifically. And overall do not mix entities and DTOs
After many trials and errors I was able to come up with a solution but still have no clue as to why this is happening. It turns out the under score in the annotation is the problem.
//won't work
#Column(name = date_added)
//works
#Column(name = dateadded)
This is pretty strange because I am fairly certain that the under score is generated by hibernate.
if anyone know why this is happening please let me know... for now I will just stay away from the under scrolls.
I have an issue join fetching in case of OneToOne relation in the same class. Example follows:
class Data {
...
#Id
#Column(name = "DATA_ID")
Long id;
#Column(name = "DATA_OWNER_ID")
#ForeignKey(entityClass = Owner.class)
Long ownerId;
#Column(name = "DATA_RELATED_ID")
#ForeignKey(entityClass = Data.class)
Long relatedDataId;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DATA_RELATED_ID", referencedColumnName = "DATA_ID", insertable = false, updatable = false)
Data relatedData;
}
I want to select data based on some conditions, while also fetching/initialising the "relatedData", all in one JPQL query:
SELECT owner.something1, data
FROM Data data
JOIN Owner owner on data.ownerId = owner.id
JOIN FETCH data.relatedData
WHERE data.something2 = :expectedSomething2
Executing that JPQL query throws an exception:
Query: ReadObjectQuery(name="relatedData" referenceClass=Data)|Exception:
javax.persistence.PersistenceException: Exception [EclipseLink-6044] (Eclipse Persistence Services - 2.6.2.v20151217-774c696): org.eclipse.persistence.exceptions.QueryException
Exception Description: The primary key read from the row [DatabaseRecord(
DATA_X => something
DATA_Y => something2
...
)] during the execution of the query was detected to be null. Primary keys must not contain null.
Which is somewhat true, as there is no DATA_ID column listed. Changing JOIN FETCH to LEFT JOIN FETCH returns both owner.something1 and data, but the relatedData object is null (relatedDataId is not null).
I can see, that the id for relatedData is returned from DB, but eclipselink trims it in valueFromRowInternalWithJoin and trimRowForJoin methods.
The Id column name attribute value is the reason of this exception. Same issue found in eclipselink version 2.3.2 but it works fine in version 2.0.0
Try with this entry :
eclipselink.jpa.uppercase-column-names=true
OR Try with upper and lower case one by one which on will work for you.
#Id
#Column(name = "UUID") // UUID - uppercase/lowercase one by one
Long id;
I've somehow resolved this issue, but haven't had the time to correctly identify the cause. Final (working) version differences are:
I could've forgotten to add get/set for relatedData
I have specified targetEntity = Data.class in #OneToOne
Fetch is now a LEFT JOIN FETCH and appears before JOIN Owner owner
For simplicity I'll chop the tables down quite a bit
Given the tables REPORT_DOWNLOAD, REPORT
REPORT has ID, NAME
REPORT_DOWNLOAD has ID, FK_REPORT_ID
I essentially want to build the query:
SELECT R.NAME, RD.ID FROM REPORT_DOWNLOAD RD, REPORT R WHERE
RD.FK_REPORT_ID = R.ID
My entity for REPORT_DOWNLOAD essentially looks like
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FK_REPORT_ID", referencedColumnName = "ID", nullable = false)
private ReportOrm report;
But that queries the whole REPORT table. Is there a way to reference just the NAME column from the entity?
#SomeAnnotationThatMagicallyPulls("FK_REPORT_ID", "NAME")
private String reportName;
I feel like there must be some Hibernate / JPA magic here that I could use
I’m looking for some help in converting the following tables into Spring Data Entities. I was somewhat able to do it but I can’t figure out enforcing the on delete and on update constraints. I have a users table that will contain info about a user. The conversations table will then have a conversation id and 2 columns to represent both id’s of the participating users. I’m writing my Solution in Kotlin, but I’m fine with advice in Java as well. Using Postgres. At the end of the day, I just want to end up with these 2 tables with the ability to change the primary key in the users table and have that carry over to the conversations table.
CREATE TABLE users (
id uuid PRIMARY KEY,
bio character varying(255),
first_name character varying(255) NOT NULL,
last_name character varying(255) NOT NULL,
password character varying(255) NOT NULL,
username character varying(255) NOT NULL UNIQUE,
zip_code character varying(255) NOT NULL
);
CREATE TABLE conversations (
convoid BIGSERIAL PRIMARY KEY,
user_id1 uuid REFERENCES users(id) on delete cascade on update cascade,
user_id2 uuid REFERENCES users(id) on delete cascade on update cascade
);
Here's what I tried:
#Data
#NoArgsConstructor
#Entity
#Table(name = "users")
data class User(
#Id //primary key
var id: UUID = UUID.randomUUID(),
#Column(nullable = false)
var firstName: String,
#Column(nullable = false)
var lastName: String,
#Column(nullable = false, unique = true)
var username: String,
#Column(nullable = false)
var password: String,
#Transient
var confirmPassword: String,
#Column(nullable = false)
var zipCode: String,
#Column(nullable=true)
var bio: String?
)
#Data
#NoArgsConstructor
#Entity
#Table(name = "conversations")
data class Conversation(
#Id //primary key
#GeneratedValue(strategy = GenerationType.IDENTITY)
val convoID: Long,
#ManyToOne(cascade = [CascadeType.ALL])
#JoinColumn(name = "user_id1")
val user1: User, //FK of user table PK
#ManyToOne(cascade = [CascadeType.ALL])
#JoinColumn(name = "user_id2")
val user2: User, //FK of user table PK
#Transient
var participants: List<WebSocketSession>
)
I'm trying to update the primary key in the users table but it isn't allowing me due to foreign key constraint. Clearly the Cascade isn't working.
In JPA relations only work in 1 way, in other to have Converation deleted when User is deleted you need to have #OneToMany relation to Converation from User. Same thing applies for update as well.
So adding property like this inside User should solve your problem:
#OneToMany(cascade = CascadeType.ALL)
val conversations: List<Conversation>
UPDATE
JPA creates separate table, it is usually solved by adding mappedBy = "mappingFieldName" to #OneToMany, but considering your case where you have 2 fields this is a bit tricky and much broader problem. You will either have to do a lot of custom querying or simply keep duplicate entries, meaning that for each new Conversation you will actually have 2 of them, so then you can say mappedBy = "user1", just remember to every time save 2 Conversations, 1 where user1 is actual user1 and another where you swap user1 and user2.
I have an entity called 'Instructions'. Sometimes each Instructions has to keep track Instructions before and after it. For Example I have new instruction B which is continuing from existing instruction A, Instruction B has to aware that Instruction A is the previous instruction, while Instruction A also has to know that Instruction B is the next after it. Not every Instruction will have before and after Instruction.
How to implement this in JPA(EclipseLink): [one-to-one + self referential + bidirectional] relation?
So far (not working yet) i came up with this:
mysql db:
CREATE TABLE instructions (
instruction_id int(11) NOT NULL AUTO_INCREMENT,
instruction_title varchar(100) NOT NULL,
instruction_text varchar(999) NOT NULL,
instruction_previous_id int(11) DEFAULT NULL,
PRIMARY KEY (instruction_id),
CONSTRAINT instructions_ibfk_3
FOREIGN KEY (instruction_previous_id)
REFERENCES instructions (instruction_id));
entity:
#Entity
#Table(name = "instructions")
public class Instructions implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "instruction_id")
private Integer instructionId;
#Basic(optional = false)
#Column(name = "instruction_title")
private String instructionTitle;
#Basic(optional = false)
#Column(name = "instruction_text")
private String instructionText;
#JoinColumn(name="instruction_previous_id", referencedColumnName = "instruction_id", nullable = true)
#OneToOne(optional = true)
private Instructions instructionPrevious;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "instructionPrevious")
private Collection<Instructions> instructionNextCollection;
// other properties, setter & getter
}
Currently no problem at create new Instruction, had error at reading
Instructions instruction = em.find(Instructions.class, instructionId);
instruction.getInstructionNextCollection().size(); //error this line
Local Exception Stack:
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Table 'atiits.instructions_instructions' doesn't exist
Error Code: 1146
Call: SELECT t1.instruction_id, t1.instruction_urgent, t1.instruction_uploaded_by, t1.instruction_translate, t1.instruction_title, t1.instruction_type, t1.instruction_translate_received, t1.instruction_is_cancelled, t1.instruction_translate_sent, t1.instruction_had_workorder, t1.instruction_text, t1.instruction_update_date, t1.instruction_update_by, t1.instruction_create_by, t1.instruction_translator, t1.instruction_create_date, t1.instruction_company_id, t1.instruction_previous_id, t1.instruction_status_id FROM instructions_instructions t0, instructions t1 WHERE ((t0.Instructions_instruction_id = ?) AND (t1.instruction_id = t0.instructionNextCollection_instruction_id))
bind => [874]
Query: ReadAllQuery(name="instructionNextCollection" referenceClass=Instructions sql="SELECT t1.instruction_id, t1.instruction_urgent, t1.instruction_uploaded_by, t1.instruction_translate, t1.instruction_title, t1.instruction_type, t1.instruction_translate_received, t1.instruction_is_cancelled, t1.instruction_translate_sent, t1.instruction_had_workorder, t1.instruction_text, t1.instruction_update_date, t1.instruction_update_by, t1.instruction_create_by, t1.instruction_translator, t1.instruction_create_date, t1.instruction_company_id, t1.instruction_previous_id, t1.instruction_status_id FROM instructions_instructions t0, instructions t1 WHERE ((t0.Instructions_instruction_id = ?) AND (t1.instruction_id = t0.instructionNextCollection_instruction_id))")
at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:333)
at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:687)
There's some confusion in your example whether each Instruction can be followed by a single Instruction or by many.
If it's a single, then don't use a collection for instructionNext.
If it's many, then the example code in JPA: How to have one-to-many relation of the same Entity type should help. You need #ManyToOne for the preceding instruction and #OneToMany for the following, rather than #OneToOne.