See the following relations:
The Table RECIPE_USERCATEGORY_REL has an ON UPDATE CASCADE trigger, so if I would execute the following command in psql console, the value of ruc_ucat_category will also be updated automatically.
update usercategory set ucat_category = 'OldCategory' where ucat_category = 'NewCategory';
This works.
The problem is now Hibernate. I have this method in my service class:
public void renameCategory(String userId, String fromCategory, String toCategory)
{
TypedQuery<UserCategory> query = entityManager.createNamedQuery("UserCategory.findAllCaseSensitiveByUserIdAndPrefix", UserCategory.class);
query.setParameter(ApplicationConstants.PARAM_USER_ID, userId);
query.setParameter("category", fromCategory);
List<UserCategory> resultList = query.getResultList();
if (resultList == null || resultList.isEmpty())
{
return;
}
UserCategory userCategory = resultList.get(0);
userCategory.setCategory(toCategory);
}
I can assure that userCategory has the value 'OldCategory'. In my opinion, the update should work, because the trigger of the database should update the value of the relation table, but nothing happens. Why is this so?
Additional information: In my Entities, there is no #OneToMany and #ManyToOne declaration on the USERCATEGORY <-> RECIPE_USERCATEGORY_REL relationship (only on RECIPE <-> RECIPE_USERCATEGORY_REL relationship it is). This is because RECIPE_USERCATEGORY_REL is not a real join table. USERCATEGORY is similar to a growing lookup table, so Hibernate must not interfere the workflow here. The only relation of USERCATEGORY <-> RECIPE_USERCATEGORY_REL is the referential integrity in the database.
This is what the entity looks like, but as I said, there is no hibernate relation to the category table since Hibernate should not take care about this relation:
#Table(name = "RECIPE_USERCATEGORY_REL")
public class RecipeUserCategoryRel implements Serializable
{
private static final long serialVersionUID = 1L;
#EmbeddedId
private RecipeUserCategoryRelPk recipeUserCategoryRelPk = new RecipeUserCategoryRelPk();
#MapsId("rcpId")
#ManyToOne
#JoinColumn(name = "ruc_rcp_id", referencedColumnName = "rcp_id", insertable = false, updatable = false)
private Recipe recipe;
...
}
and...
#Embeddable
public class RecipeUserCategoryRelPk implements Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "ruc_rcp_id")
private Long rcpId;
#Column(name = "ruc_ucat_category")
private String category;
#Column(name = "ruc_ucat_acc_identifier")
private Long identifier;
public RecipeUserCategoryRelPk()
{
}
...
//getters, setters, hashcode, equals
}
I read in some other postings that it is not allowed in JPA to change the primary key. However, my use case is definitely changing the primary key, but in my case, it's not a common use case and no 'real' part of the application, but there are cases where users need to modify old data, so I need to provide this functionality.
(As workaround, I made a native update query)
Related
I'm starting a project to know more in detail JPA.
Context:
At the end of his internship, the student has a report to make and a presentation in front of his professor to do about the internship.
I've a database, which is called "grade_management". It must contains a "student", "presentation", "report", "professor" and a "mark" (there are several rating criteria such as expression, quality of powerpoint ...) table. But now it's empty, since I want to make it throught JPA.
I've a "Presentation" class. Which countain this:
#Entity
public class Presentation implements Serializable {
#Id
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
private mark_id;
private int professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
}
But the Presentation table contain 2 foreign key: professor_id and mark_id.
My question is: How can I indicate that both of them are foreign key ?
I'm sorry if I'm not clear, don't hesitation to ask question.
Cordially
You shouldn't reference other entities by their ID, but by a direct reference to the entity.
Something like that :
#ManyToOne
#JoinColumn(name = "mark_id", referencedColumnName = "id")
private Mark mark; // supposed here that mark_id if link to entity `Mark`
#ManyToOne
#JoinColumn(name = "professor_id", referencedColumnName = "id") // suppose "id" is the column name of the PK inside the table Professor.
private Professor professor; // supposed here that professor_id if link to entity `Professor`
This code is supposing that you use an unidirectional relation.
For bidirectional you have to define this in the other side (Mark/Professor type)
#OneToMany(mappedBy = "professor")
private Presentation presentation;
From your explanation, it looks like you have a Database named grade_management and in that database you have "student", "presentation", "report", "professor" and a "mark" tables (i.e: which are #Entity by themselves defined in their separate respective classes )
I'm not sure whether you have defined them or not. If not then you have to define them first and then use the refactored code mentioned below.
So, you will have many-to-one relation mapping. You can annotate your foreign keys belonging to different tables using #ManyToOne annotation to indicate relation type and #JoinColumn annotation to indicate that this entity has a foreign key to the referenced table.
You can redefine your Presentation class show below:
#Entity
#Table(name = "Presentation")
public class Presentation implements Serializable {
#Id
#Column(name="presentation_id")
#GeneratedValue (strategy=GenerationType.AUTO)
private int presentation_id;
private Date date;
#ManyToOne
#JoinColumn(name = "mark_id")
private Mark mark_id;
#ManyToOne
#JoinColumn(name = "professor_id")
private Professor professor_id;
public Soutenance() {}
public Soutenance(Date date) {
this.date = date;
}
//getter and setter
}
Also, if you need more information to read upon for yourself you can always checkout this Hibernate Documentation that explains everything you'll need to know.
I'm trying to delete the parent student or parent course and I get this error:
Caused by: org.postgresql.util.PSQLException: ERROR: update or delete on table "student" violates foreign key constraint "fkeyvuofq5vwdylcf78jar3mxol" on table "registration"
RegistrationId class is a composite key used in Registration class. I'm using Spring data jpa and spring boot.
What am I doing wrong? I know that putting cascadetype.all should also remove the children when the parent is deleted but it is giving me an error instead.
#Embeddable
public class RegistrationId implements Serializable {
#JsonIgnoreProperties("notifications")
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "student_pcn", referencedColumnName="pcn")
private Student student;
#JsonIgnoreProperties({"teachers", "states", "reviews"})
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "course_code", referencedColumnName="code")
private Course course;
Registration class
#Entity(name = "Registration")
#Table(name = "registration")
public class Registration {
#EmbeddedId
private RegistrationId id;
When you're using a relational DB, you are setting entities with relationships between these entities.
The error that you're getting means that:
You're trying to delete a record that its primary key is functioning as a foreign key in another table, thus you can't delete it.
In order to delete that record, first, delete the record with the foreign key, and then delete the original that you wanted to delete.
I made it work by using hibernate #OnDelete annotation. Some how the JPA.persistence CascadeTypes were not working. They had no effect for whichever I chose.
Just like below. Now I can remove the parent Student or the parent Course and all children(Registrations) are deleted with them.
#Embeddable
public class RegistrationId implements Serializable {
#JsonIgnoreProperties("notifications")
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne
#JoinColumn(name = "student_pcn", referencedColumnName="pcn")
private Student student;
#JsonIgnoreProperties({"teachers", "states", "reviews"})
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne
#JoinColumn(name = "course_code", referencedColumnName="code")
private Course course;
Foreign keys guarantee that an entry will exist in another table. This is a way of ensuring data integrity. SQL will never allow you to delete this entry while it still deletes in the other table. Either (1) this is letting you know you would have made a grave mistake by deleting this thing which is required or (2) you would like to put in a cascading delete so that not only is this entry deleted but so is what is supposed to be referencing it in the other table. Information on cascading deletes can be found here and written fairly easily (https://www.techonthenet.com/sql_server/foreign_keys/foreign_delete.php). If neither of these two descriptions fits you, evaluate why your foreign key relationship exists in the first place because it probably should not.
Try this method too. I got the answer with this method,This is just a test to remove.
Pay attention to the cascade!
MyUser Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String firstname;
private String lastname;
private String mobile;
#Column(unique = true)
private String email;
private Long date;
private LocalTime localiime;
private LocalTime localiimeend;
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
#JoinColumn(foreignKey = #ForeignKey(name = "role_fk"))
private Role role;
Role Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String description;
#OneToMany(mappedBy = "role", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<MyUser> users;
#ManyToOne (fetch = FetchType.LAZY,cascade = CascadeType.MERGE)
#JoinColumn(foreignKey = #ForeignKey(name = "rolecat_fk"))
private rolecat rolecat;
rolecat Entity
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "rolecat", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Role> roles;
I have theses entity and I do this query.
select r from RentAmount r Join r.lodger l join l.bailList b where r.unpaidBalance > 0 and (r.paymentDueDate > :date or r.paymentDueDate is null ) and b.paymentPeriod= order by r.rentAmountId")
Is there a way to feed Lodger.bailList only with the last bail or i would need to loop on every record to get this information?
#Entity
public class RentAmount {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long rentAmountId;
#OneToOne
private Lodger lodger;
}
#Entity
public class Lodger{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long lodgerId;
#OneToOne(fetch = FetchType.LAZY, mappedBy="lodger")
private RentAmount rentAmount;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY, mappedBy = "lodger", orphanRemoval = true)
private List<Bail> bailList;
}
#Entity
public class Bail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long bailId;
#Enumerated(EnumType.STRING)
private PaymentPeriodEnum paymentPeriod;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lodger_id")
private Lodger lodger;
}
There are a few options:
One (Non JPA, Hibernate Only)
Ensure the collection is correctly ordered and mark it is as extra lazy. You will have access to the whole collection but accessing of individual items will not trigger a full load.
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/performance.html
"Extra-lazy" collection fetching: individual elements of the
collection are accessed from the database as needed. Hibernate tries
not to fetch the whole collection into memory unless absolutely
needed. It is suitable for large collections.
The mapping will look like:
#OneToMany(mappedBy = "lodger")
#LazyCollection(LazyCollectionOption.EXTRA)
#OrderBy("theRelevantProperty ASC")
private List<Bail> bailList;
public void getCurrentBail(){
//will only load this item from the database
return bailList.get(bailList.size() - 1);
}
Two (Non JPA, Hibernate Only.)
Use the #Where annotation to filter the collection so that while still #OneToMany, only one element will be accessible.
https://docs.jboss.org/hibernate/stable/annotations/reference/en/html_single/#entity-hibspec-collection
The mapping will look like:
#OneToMany(mappedBy = "lodger")
#Where(clause="some native sql which will filter to include onyl 1item"))
private List<Bail> bailList;
public void getCurrentBail(){
//will be the only item accessible
return bailList.get(0);
}
Three (JPA Compliant)
Would involve creating views at the database level. Various options in this area. If we are only ever interested in the current bail then this view would be similar to option 2 above. Simply point the Bail entity to this view rather than the concrete table:
#Entity
#Table(name = "vw_active_bail")
public class Bail {
}
I have 2 entity classes with one-to-one dependencies on their primary keys:
The primary table:
#Entity
#Table(name = "tablePrimary")
#XmlRootElement
//...
public class TablePrimary implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "code")
private String code;
// set the dependency of table2 to this class
#OneToOne(cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Table2 table2inst;
// ...
} // end of class TablePrimary
The dependent table:
#Entity
#Table(name = "table2")
#XmlRootElement
//...
public class Table2 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#Column(name = "id")
private Integer id;
#Basic(optional = false)
#Column(name = "name")
private String name;
#MapsId
#OneToOne(mappedBy = "table2inst")
#JoinColumn(name = "id")
private TablePrimary tablePrimaryInst;
//...
} // end of class Table2
Whenever there is a row with say, id==55 in TablePrimary, there is
a row with the same id==55 in Table2 and vice-versa.
So in essence, these two tables are one table in logical level-- split into 2 physical tables for practicality.
When i'm inserting a row into the "logical" table,
i first am inserting to TablePrimary-- the primary table in the relationship,
getting the value of id==55 field of this new row i just inserted and inserting a row to
Table2 with that id value.
As part of this, i'm checking, just in case,
whether a row with id==55 is already in Table2.
Is there a better way of doing this?
Does JPA have a feature to make these two insertions to these two physical tables
by using the 1-1 dependency I configured on them-- without me having to do it "manually" in the code? Or a control feature on the id fields of the tables I set the dependency on?
If there is-- how is done? how does it handle the key value collision in the dependent table-- Table2?
A similar thing will come up on deletion. However, i'm not there yet, and might figure out out of this.
TIA.
You can take advantage of JPA cascading. You will have to define a cascade on the owning side (the one with the join column). If you have set the owning side of the relationship and persist the owning side, the inverse side will be persisted as well:
TablePrimary tp = new TablePrimary();
Table2 t2 = new Table2();
t2.setTablePrimaryInst(tp);
entityManager.persist(t2);
The 'mappedBy' element is supposed to be placed on the inverse side. You entities could look like this:
public class Table2 ...
#OneToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "tp_id")
private TablePrimary tablePrimary;
public class TablePrimary...
#OneToOne(mappedBy="tablePrimary")
private Table2 table2;
I have an entity class that contains a map of key-value pairs which live in a different table and there may be no such pairs for a given entity. The relevant code for the entity classes is below.
Now, when I insert such an entity with persist(), then add key-value pairs, and then save it with merge(), I get duplicate entry errors for the related table that stores the key-value pairs. I tried to hold back insertion until the keys were added, to have one call to persist() only. This led to duplicate entry errors containing an empty (zero) id in the foreign key column (ixSource).
I followed the process in the debugger, and found that eclipselink seems to be confused about the cascading. While it is updating the entity, it executes calls that update the related table. Nonetheless, it also adds those operations to a queue that is processed afterwards, which is when the duplicate entry errors occur. I have tried CascadeType.ALL and MERGE, with no difference.
I'm using static weaving, if it matters.
Here's the entities`code, shortened for brevity:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "sType")
#Table(name = "BaseEntity")
public abstract class BaseEntity extends AbstractModel
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ix")
private long _ix;
}
#Entity
#Table(name = "Source")
public class Source extends BaseEntity
{
#OneToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "SourceProperty", joinColumns = { #JoinColumn(name = "ixSource") })
#MapKey(name = "sKey")
private Map<String, SourceProperty> _mpKeys;
// ... there's more columns that probably don't matter ...
}
#Entity
#Table(name = "SourceProperty")
#IdClass(SourcePropertyKey.class)
public class SourceProperty
{
#Id
#Column(name = "sKey", nullable = false)
public String sKey;
#Id
#Column(name = "ixSource", nullable = false)
public long ixSource;
#Column(name = "sValue", nullable = true)
public String sValue;
}
public class SourcePropertyKey implements Serializable
{
private final static long serialVersionUID = 1L;
public String sKey;
public long ixSource;
#Override
public boolean equals(Object obj)
{
if (obj instanceof SourcePropertyKey) {
return this.sKey.equals(((SourcePropertyKey) obj).sKey)
&& this.ixSource == ((SourcePropertyKey) obj).ixSource;
} else {
return false;
}
}
}
I can't see how those errors would occur. Could you include the SQL and ful exception.
What version of EclipseLink are you using, did you try the latest release?
Why are you calling merge? Are you detaching the objects through serialization, if it is the same object, you do not need to call merge.
It could be an issue with the #MapKey, does it work if you remove this?