I have two entities:
#Entity
#Table(name = "entity_a")
#Audited
public class EntityA {
#Column(name = "entity_a_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
/**
* #deprecated in favor of uuid
*/
#Deprecated
#Id
#GeneratedValue
#Column(name = "entity_a_id")
private Integer id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "entity_a_id", nullable = false)
#BatchSize(size = 100)
#NotAudited
private List<EntityB> entityBs = new ArrayList<>();
}
and
#Entity
#Audited
#Table(name = "entity_b")
public class EntityB {
#Id
#Column(name = "entity_b_uuid", columnDefinition = "char", updatable = false)
#Type(type = "uuid-char")
private UUID uuid = UUID.randomUUID();
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "entity_a_id", nullable = false, insertable = false, updatable = false)
private EntityA entityA;
}
Each is correctly audited into two tables entity_a_audit and entity_b_audit. However, the entity_a_id field in entity_b_audit is always null.
Some details:
If I do not have the #NotAudited in EntityA, I will get an error that says something to the effect of: The table EntityA_EntityB_audit does not exist. This seems like it's trying to audit them as a single table, which I do not want.
I have tried applying #Audited(targetAuditMode = elationTargetAuditMode.NOT_AUDITED) to each side. If applied only in EntityA, I get the above error. If applied only in EntityB, nothing changes. If applied in both, I get the error above. If applied to neither, I get the error above.
I suspect the entity_a_id is null in entity_b_audit because the id isn't generated until EntityA hits the DB. entity_a_id is auto-incrementing in the entity_a table.
Using hibernate-envers-5.4.32.Final.jar
Ultimately, I would like for entity_a_id to not be null in entity_b_audit. Alternatively, if I could somehow get entity_a_uuid to be captured instead, that would also suffice.
Any help would be greatly appreciated! Thanks.
You marked the column as insertable = false, updatable = false, so there is nothing to audit here, because Hibernate can never change the value of that column.
Related
I am using PostgreSQL 12.11, JPA 3.1.0, and Hibernate 5.6.10. This might become important because I am doing things that apparently do not work with JPA 2.0.
My goal is to add an attribute to a many-to-many relationship. I found this posting. #Mikko Maunu states that "There is no concept of having additional persistent attribute in relation in JPA (2.0)." To me, this sounds like what I want to do is not possible. However, the answer is rather old and might not be complete anymore.
Beside the time gap and the version gap, this is, in my opinion, a new question because I am doing something that is probably questionable and not part of the original thread.
What I did is this:
Create a #ManyToMany relationship in JPA and specify a #JoinTable.
Manually define an entity with identical table name to the table specified in 1. For this table, I chose a composite primary key using #IdClass. I also added my attribute.
Inside one of the n:m-connected entities, create a #OneToMany relationship to the connection-table-entity created in 2. However, I did not create a corresponding #ManyToOne relationship as that would have created an error.
As a result, I can access the original entities and their relation as many-to-many, but also the relation itself, which is not an entity in the original ERM, but it is for JPA. First tests show this seems to be working.
I am aware, however, that I basically access the same part of the persistence (the PostgreSQL database) through two different ways at the same time.
Now my questions are:
Is this a valid way to do it? Or will I get in bad trouble at one point?
Is there a situation where I will need to refresh to prevent trouble?
Is this something new in JPA > 2.0, or just an extension to the original answer?
This should help.
Here is how I do it:
#Entity
#Table(name = "person", schema = "crm")
public final class Person implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "person", orphanRemoval = true)
private Set<PersonEmail> emails = new HashSet<>();
}
#Entity
#Table(name = "email", schema = "crm")
public final class Email implements Serializable {
#Id
#Column(name = "id", unique = true, nullable = false, updatable = false, columnDefinition = "bigserial")
private Long id;
#Column(name = "email", nullable = false, length = 64, columnDefinition = "varchar(64)")
private String localPart;
#Column(name = "domain", nullable = false, length = 255, columnDefinition = "varchar(255)")
private String domain;
}
#Entity
#Table(name = "person_email", schema = "crm")
public final class PersonEmail implements Serializable {
#EmbeddedId
private PersonEmailId id;
// The mapped objects are fetched lazily.
// This is a choice.
#ToString.Exclude
#MapsId("personId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Person person;
#ToString.Exclude
#MapsId("emailId")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Email email;
// Here's an extra column.
#Column(name = "type", nullable = false, columnDefinition = "email_type_t")
#Convert(converter = EmailType.EmailTypeConverter.class)
private EmailType type;
public final void setPerson(final Person person) {
this.person = person;
id.setPersonId(this.person.getId());
}
public final void setEmail(final Email email) {
this.email = email;
id.setEmailId(this.email.getId());
}
#Embeddable
public static final class PersonEmailId implements Serializable {
#Column(name = "person_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long personId;
#Column(name = "email_id", nullable = false, insertable = false, updatable = false, columnDefinition = "bigint")
private Long emailId;
}
Spent 3 days looking for a solution and finally I came here for community wisdom.
I have self-referencing entity like follows:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#Entity
#IdClass(CompositeUserId.class)
#Table(name = "user", schema = "dbo")
public class User implements Serializable {
#Id
#Column(name = "id")
private Integer id;
#Id
#Column(name = "first_name")
private String firstName;
#Id
#Column(name = "last_name")
private String lastName;
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id", insertable = false, updatable = false)
#JoinColumn(name = "first_name", referencedColumnName = "first_name", insertable = false, updatable = false)
#JoinColumn(name = "last_name", referencedColumnName = "last_name", insertable = false, updatable = false)
private User parent;
#OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
private Set<User> children;
my CompositeUserId.class:
#Getter
#Setter
#EqualsAndHashCode
#AllArgsConstructor
#NoArgsConstructor
public class UserCompositeId implements Serializable {
private Integer id;
private String firstName;
private String lastName;
When I try retrieve all data from my user table I get error:
org.springframework.orm.jpa.JpaObjectRetrievalFailureException: Unable to find ...User with id UserCompositeId#19e66569; nested exception is javax.persistence.EntityNotFoundException:
I suppose there might be some kind of mistake in the #JoinColumn block.
Here is the sql query causing the error:
SELECT *
FROM dbo.user ur1 LEFT OUTER JOIN dbo.user ur2 ON ur1.first_name=ur2.first_name AND ur1.parent_id=ur2.id AND ur1.last_name=ur2.last_name
WHERE ur1.first_name='First Name' AND ur1.id=130 AND ur1.last_name='Last Name'
I ensured that the request does not return anything in the database by running it manually, but found out that if I will change id to parent_id it will return data, so again, probably some mistake in #JoinColumn block
You need you use #NotFound(action=NotFoundAction.IGNORE). If there is no records then it will assign null to it.
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id", insertable = false, updatable = false)
#JoinColumn(name = "first_name", referencedColumnName = "first_name", insertable = false, updatable = false)
#JoinColumn(name = "last_name", referencedColumnName = "last_name", insertable = false, updatable = false)
#NotFound(action=NotFoundAction.IGNORE)
private User parent;
The foreign key columns need different names than the primary key columns, e.g. like this (notice the 'parent' prefix in name):
#JoinColumn(name = "parent_id", referencedColumnName = "id", updatable = false, insertable = true)
#JoinColumn(name = "parent_firstname", referencedColumnName = "firstname", updatable = false, insertable = true)
#JoinColumn(name = "parent_lastname", referencedColumnName = "lastname", updatable = false, insertable = true)
#ManyToOne(fetch = FetchType.EAGER)
private UserEntity parent;
Additionally, the insertable value should be true, since otherwise no associations are persisted during the insert.
Be aware that a parent needs to be saved before a child can be associated. Since the parent fk is not updatable, is must be set on a newly created child before it is saved.
Feel free to have a look at this project containing the complete sample code:
Entity:
https://github.com/fladdimir/many-to-one-self-ref-composite-id/blob/master/src/main/java/org/demo/UserEntity.java
Integration-Test:
https://github.com/fladdimir/many-to-one-self-ref-composite-id/blob/master/src/test/java/org/demo/DemoApplicationTests.java
Also it is good practice to synchronize bidirectional associations:
https://vladmihalcea.com/jpa-hibernate-synchronize-bidirectional-entity-associations/
The sample entity could e.g. use some methods like these:
public void addChild(UserEntity child) {
child.parent = this; // sync owning side of the association
this.children.add(child);
}
public void setParent(UserEntity parent) {
parent.addChild(this);
}
I have 2 models with one to one relationship
#Entity
#Table(name = "Form_Item_Production")
public class FormItemProduction {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", nullable = false)
private Long itemId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "shift_lookup_id", insertable = false, updatable = false)
private AppLookup appLookup;
getter and setter
}
The other one
#Entity
#Table(name = "App_Lookup")
public class AppLookup {
#Id
#GeneratedValue
#Column(name = "Lookup_Id", nullable = false)
private Long lookupId;
#Column(name = "Lookup_Name", length = 30, nullable = false)
private String lookupName;
getter and setter
}
When I try to persist to save the formitemproduction values
public boolean insertItem(List<FormItemProduction> f) {
for (FormItemProduction i : f) {
System.out.println("A" + i.getAppLookup().getLookupId()); // prints the correct id of applookup
i.setItemId(null);
entityManager.persist(i);
}
entityManager.flush();
return true;
}
I get this error
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: com.mamee.factory.security.entity.AppLookup
From my understanding this is unidirectional one to one mapping so I don't quite understand why I'm getting the error detached?
You have itemId which can't be null.
#Column(name = "item_id", nullable = false)
private Long itemId;
and you actually set itemId to null
i.setItemId(null);
So this line
entityManager.persist(i);
Is not able to persist your data.
Fixed by changing from:
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "shift_lookup_id", insertable = false, updatable = false)
private AppLookup appLookup;
to:
#OneToOne
#JoinColumn(name = "shift_lookup_id", insertable = true, updatable = true)
private AppLookup appLookup;
You have set cascade to ALL:
#OneToOne(cascade = CascadeType.ALL)
Which means you are cascading all operations down to the related field AppLookup.
You can set cascade to none, and you will no longer get your error, but no db operations will be executed for AppLookup field.
I have an entity Job as below.
#Entity
#Getter
#Setter
#NoArgsConstructor
#Immutable
#ToString
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
#Table(name = "JOB")
public class Job extends BaseEntity implements IEntity, IDto {
#Id
#Column(name = "JOB_ID", unique = true, nullable = false)
private Long id;
#Column(name = "PRINT_JOB_ID", length = 30)
private String printJobId;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "PRINT_JOB_ID", nullable = false, insertable = false, updatable = false)
private Set<PrintFile> printFileInfos = new HashSet<PrintFile>();
}
I also have another entity PrintFile.
#Entity
#Getter
#Setter
#NoArgsConstructor
#Immutable
#Table(name = "PRINT_FILE")
public class PrintFile implements Serializable {
#Id
#Column(name = "FILE_ID", unique = true, nullable = false, length = 50)
private String fileId;
#Column(name = "PRINT_JOB_ID", nullable = false, length = 30)
private String printJobId;
}
Here are my tables.
Job
JOB_ID NOT NULL NUMBER
PRINT_JOB_ID VARCHAR2(30)
Print_File
PRINT_JOB_ID NOT NULL VARCHAR2(30)
FILE_ID NOT NULL VARCHAR2(50)
When trying fetch Job data using Sprint boot rest API, I'm getting java.sql.SQLSyntaxErrorException: ORA-01722: invalid number error. All the dataype mapping seems to be correct, what else could have gone wrong ?
EDIT:
The Job entity fetches without any issues when I get rid of the join. i.e, the entire declaration of printFileInfos in Job entity. This makes me think the issue is either with the join or in PrintFile entity.
I would recommend you to try below given code. After adding referencedColumnName attribute, it worked for me.
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "PRINT_JOB_ID", referencedColumnName = "PRINT_JOB_ID", nullable = false, insertable = false, updatable = false)
private Set<PrintFile> printFileInfos = new HashSet<PrintFile>();
I'm new in hibernate. So, I don't know how to do this:
I have 3 tables:
Table Person:
#Entity
#Table(name = "ASD_PERSON")
public class AsdPerson implements Serializable {
#Id
#SequenceGenerator(name="seq_name", sequenceName="gen_id_value", allocationSize = 1)
#GeneratedValue(generator="seq_name")
#Column(name="F_PERSON_ID", nullable = false)
private Long fPersonId;
#OneToMany(mappedBy = "AsdPerson",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<AsdPersonEvent> asdPersonEventList;
... setters and getters ...
}
Table Event:
#Entity
#Table(name = "ASD_EVENT")
public class AsdEvent implements Serializable {
#Id
#SequenceGenerator(name="seq_name", sequenceName="gen_id_value", allocationSize = 1)
#GeneratedValue(generator="seq_name")
#Column(name="F_EVENT_ID", nullable = false)
private Long fEventId;
#OneToMany(mappedBy = "AsdEvent",
cascade = CascadeType.ALL,
orphanRemoval = true)
private List<AsdPersonEvent> asdPersonEventList;
... setters and getters ...
}
Table Person-Event:
#Entity
#Table(name = "ASD_PERSON_EVENT")
#IdClass(AsdPersonEventPK.class)
public class AsdPersonEvent implements Serializable {
#Id
#GenericGenerator(name = "generator", strategy = "foreign",
parameters = #Parameter(name = "property", value = "asdPerson"))
#GeneratedValue(generator = "generator")
#Column(name="F_PERSON_ID", nullable = false, insertable = false,
updatable = false)
private Long fPersonId;
#Id
#GenericGenerator(name = "generator", strategy = "foreign",
parameters = #Parameter(name = "property", value = "asdEvent"))
#GeneratedValue(generator = "generator")
#Column(name="F_EVENT_ID", nullable = false, insertable = false,
updatable = false)
private Long fEventId;
#ManyToOne
#JoinColumn(name = "F_PERSON_ID", insertable = false,
updatable = false)
private AsdPerson asdPerson;
#ManyToOne
#JoinColumn(name = "F_EVENT_ID", insertable = false,
updatable = false)
private AsdEvent asdEvent;
... setters and getters ...
}
Everything works perfectly (adding new records, creating new objects) except the case, when I try to delete associated records from Event table or Person table:
...
AsdEvent ev = getService().get(115); // get record from Event table by id = 115 (for example)
ev.getAsdPersonEventList().remove(1); // delete some existing records
getService().merge(ev);
...
After that I get error:
deleted object would be re-saved by
cascade (remove deleted object from
associations):
[database.AsdPersonEvent#database.AsdPersonEventPK#12908fc]
How to configure Hibernate with annotations or some other way to get rid of this error?
If you have a complex graph of persistent entities, I think you need to give up using orphanRemoval and remove your entities manually using em.remove().
orphanRemoval is designed for simple parent-child relationships, where child doesn't make sense without parent. If in your case child may have ohter relationships, perhaps it's not a good case for orphanRemoval.
Try once again by removing orphanRemoval = true