Would pessimistic lock has cascade effect? - jpa

Here is my data structure.
#Entity
public class JobEntity {
#Id
private Long id;
private String name;
#OneToMany(fetch = FetchType.EAGER,cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE}, mappedBy = "parentJob")
private List<JobEntity> subJobs;
#ManyToOne
#JoinColumn(name = "parent_job")
private JobEntity parentJob;
}
So the job entity has a tree data structure, If I want to add pessimistic lock like below, using JPA:
Map<String, Object> properties = new HashMap<>();
map.put("javax.persistence.lock.scope", PessimisticLockScope.EXTENDED);
entityManager.find(JobEntity.class, 1L,LockModeType.PESSIMISTIC_WRITE, properties);
Would the pessimistic lock work on all referenced sub rows in the tree data structure?
Or just the row with id "1L"? Or just the rows directly referenced from "1L"?

It should work but note that if you are using Hibernate, unfortunately it's not yet supported
The javax.persistence.lock.scope is not yet supported as specified by the JPA standard.
From Hibernate Docs
Related Ticket

Related

How can I force Hibernate to use joins to fetch data for instances

I have a Spring Boot application using Hibernate as JPA provider. My application has two entities connected with a #OneToMany / #ManyToOne relation. The relation is annotated with #Fetch(FetchMode.JOIN) on both directions, and fetch = FetchType.EAGER.
My entities are called Car and Driver:
#Entity
#Table(name = "car")
#Data
public class Car implements Serializable, Cloneable {
#Id
#GenericGenerator(name = "car_seq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", parameters = {
#Parameter(name = "sequence_name", value = "car_seq") })
#GeneratedValue(generator = "car_seq")
private Integer id;
#OneToMany(mappedBy = "car", fetch = FetchType.EAGER)
#Fetch(FetchMode.JOIN)
private List<Driver> drivers = new ArrayList<>();
#Column(name = "license_no", nullable = false)
private String licenseNo;
}
#Entity
#Table(name = "driver")
#Data
public class Driver implements Serializable, Cloneable {
#Id
#GenericGenerator(name = "driver_seq", strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator", parameters = {
#Parameter(name = "sequence_name", value = "driver_seq") })
#GeneratedValue(generator = "driver_seq")
private Integer id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "car_id", nullable = true)
#Fetch(FetchMode.JOIN)
private Car car;
#Column(name = "name", nullable = false)
private String name;
}
When selecting a care (e.g. by calling Car.findById()), Hibernate joins the the two tables in a single SQL, and returns a Car object with a list of Drivers.
But if I select a single driver, Hibernate will join the Driver and Car table to give me the Driver object with the Car property populated, but it will run a second query to fetch all the driver objects on for the list on the car object.
For performance reasons I would like all the involved objects to be fetched in a single query, as is the case when I fetch a car. But I cannot find a way to make Hibernate do this. There is a property, hibernate.max_fetch_depth, which is supposed to do this, but I have found that it only affects the behavior of fetching a car, not when I fetch a driver.
I know I can use an EntityGraph to control the fetching, and by using an EntityGraph I have successfully retrieved a driver object with its car and all the car's drivers in one query. But to do that, I have to explicitly use a graph when retrieving the object, and I cannot do that in all the various cases where a Car object is needed. There are lots of other entities that have a relation to Car, and I don't want to write an EntityGraph for each and every one of those.
So is there a way to tell Hibernate how you want the fetching to be done by default on an entity? I would have thought that the annotations would be enough, but it seems that there either has to be something more, or that this simply cannot be done.
Arndt
FetchType.EAGER is one of the most common reasons for performance problems. You should use
#OneToMany(mappedBy = "car")
private List<Driver> drivers = new ArrayList<>();
And fetch drivers If needed
SELECT c FROM Car c JOIN FETCH c.drivers

ERROR: update or delete on table "tablename" violates foreign key constraint

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;

ON UPDATE CASCADE not triggered in Hibernate JPA

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)

Feed a list with the last value

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 {
}

merging entity with onetomany mapping and #version field causes delete of the previous mapping

Hi! All,
I have a mapping issue with two entities. mapped through a #OneToMany unidirectional relation. I have an entity Artifact which can have multiple Revision. Here's how I have mapped them
#Entity
#Table(name = "artifact")
public class Artifact implements Serializable {
private static final long serialVersionUID = 248298400283358441L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Version
private Integer version;
...
#OneToMany(cascade = { CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE })
#JoinTable(name = "artifact_revisions", joinColumns = #JoinColumn(name = "artifact_id"), inverseJoinColumns = #JoinColumn(name = "revision_id"))
private Set<Revision> revisions;
And the revisions entity
#Entity
#Table(name = "revision")
public class Revision implements Serializable {
private static final long serialVersionUID = -1823230375873326645L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
...
#Column(name = "date_created", nullable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date creationDate;
The revision table saves the filed name that was updated; old value and new value etc.
The problem I face is that when I update the artifact; the last mapping gets deleted and then it inserts a new one, so if effect I only have the last but one revision available not the entire revision history.
Hibernate:
update
artifact
set
description=?,
estimate=?,
name=?,
rank=?,
status=?,
sysId=?,
version=?
where
id=?
and version=?
Hibernate:
delete
from
artifact_revisions
where
artifact_id=?
and revision_id=?
Hibernate:
insert
into
artifact_revisions
(artifact_id, revision_id)
values
(?, ?)
If I remove #version annotation from the artifact it works fine.
Is it because I am mapping the relation in a wrong manner? Should this relation be mapped as an element collection instead?
There is another Entity Task which is to be mapped with the Revision entity. So what will be the best approach here?
Maybe this is not a straight answer to your question but I think you should look into hibernate envers. I think it's doing pretty similar thing. (envers stands for entity versioning). You just annotate entity with #Audited put some listeners into config and the rest magic is done for you.