Spring hibernate unnecessary version update queries - spring

I have a spring boot application connecting to mysql db.
I have nested entities in it, all with a version column
public class Entity1 {
.
.
.
#Version
#Column(
name = "version"
)
protected Long version = 0L;
.
.
.
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
List<Entity2> entity2List;
}
public class Entity2 {
.
.
.
#Version
#Column(
name = "version"
)
protected Long version = 0L;
.
.
.
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
List<Entity3> entity3List;
}
This is nested again for Entity3 and Entity4. Entity3 and Entity4 also have a version column.
I am doing this in a method
public void someMethod(...) {
Entity1 entity1 = findOneById(id);
transactionalMethod();
}
#Transactional(rollbackFor = Exception.class)
public void transactionalMethod() {
// Do nothing
return;
}
When the transaction from the transactionalMethod commits, I can see queries incrementing the version column for all Entity4 instances that were loaded as children of entity1. I have not run any update queries on entity1 and any of its children.
I am unable to figure out in which case would this happen?

Related

Hibernate fetches a deleted child entity from the parent entity

TripEntity
public class TripEntity extends BaseEntity {
.
.
#JsonManagedReference(value = "segment-trip")
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "TRIP_ID")
private List<SegmentEntity> segmentList = new ArrayList<>();
.
.
.
}
SegmentEntity:
public class SegmentEntity extends BaseEntity {
.
.
.
#JsonBackReference(value = "segment-trip")
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "TRIP_ID")
private TripEntity trip;
.
.
}
With in the same transaction, Deleted one segment that was part of the trip, but when I am fetching the trip using the trip id, getting the deleted segment with other segments as well.
Delete:
#Modifying
#Transactional
#Query(value = "delete from dis_segment where segment_id in :segmentDTOIds", nativeQuery = true)
void deleteSegmentsBySegmentIds(List<Long> segmentDTOIds);
But while fetching the trip :
TripDTO parentTrip = tripServicePort.getTripByIdAndOrgId(parentTripId);
parentTrip shows the deleted segments as well and after updating the trip using save() method, it is giving error : nested exception is javax.persistence.entitynotfoundexception
Please guide about how I can resolve this issue.

jpa - delete OneToMany violates foreign key constraint

I have two mapped entities, AssignmentOpsEntity and ConventionalOptionEntity. I want to remove an AssignmentOpsEntity with all conventionalOption. So i removed all conventionalOption from AssignmentOpsEntity, then i deleted the conventionalOption from database, finally i removed the object AssignmentOpsEntity. But i have the below error.
ERROR: update or delete on table "assignmentopsentity" violates
foreign key constraint "fkll31qdog9ye067ybhltjey6u7" on table
"conventionaloptionentity" Détail : Key
(assignmentopsid)=(8bf4a6b3-c09e-4da1-a88d-d49d9f7b63f6) is still
referenced from table "conventionaloptionentity".
#Entity
public class AssignmentOpsEntity {
#OneToMany(mappedBy = "assignmentOps", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ConventionalOptionEntity> conventionalOption;
public void removeConventionalOption(Set<ConventionalOptionEntity> conventionalOption) {
this.conventionalOption.removeAll(conventionalOption);
conventionalOption.forEach(item -> item.assignmentOps(null));
}
}
#Entity
public class ConventionalOptionEntity {
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
private AssignmentOpsEntity assignmentOps;
}
public void on(OpsDetachedFromAgreementEvent event) {
Optional<AssignmentOpsEntity> assignmentOpsEntityOptional = assignmentOpsRepository.findById(event.assignmentOpsId);
if (assignmentOpsEntityOptional.isPresent()) {
AssignmentOpsEntity assignmentOpsEntity = assignmentOpsEntityOptional.get();
assignmentOpsEntity.removeConventionalOption(assignmentOpsEntity.getConventionalOption());
conventionalOptionRepository.deleteAll(assignmentOpsEntity.getConventionalOption());
assignmentOpsRepository.delete(assignmentOpsEntity);
}
}
#OneToMany(mappedBy = "assignmentOps", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<ConventionalOptionEntity> conventionalOption;
CascadeType.ALL means here that conventionalOption entities are deleted when you delete a parent entity
So here the code you need
public void on(OpsDetachedFromAgreementEvent event) {
Optional<AssignmentOpsEntity> assignmentOpsEntityOptional = assignmentOpsRepository.findById(event.assignmentOpsId);
if (assignmentOpsEntityOptional.isPresent()) {
assignmentOpsRepository.delete(assignmentOpsEntity);
}
}

Querying #ManyToMany with unmapped #JoinTable with hibernate in a spring-mvc app

I have two entities. (Find code below)
I am trying to write a query that would count customDetails=:myCriteria of EntitiesA that are associated to EntityB of specific id.
I have written the necessary query using session.CreateSQLQuery that reads the associated_entitites table, however, I am unable to use it as the customDetails column is encrypted by hibernate's #ColumnTransformer and returns a BLOB. And I cannot replicate it in HQL as associated_entities is not mapped.
a
#Entity
public class entityA{
#Id
private int id;
#Column
#ColumnTransformer
private CustomDetails customDetails;
#ManyToMany(fetch = FetchType.EAGER,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "entitiesA")
private List<entityB> entitiesB;
//getters and setters
}
b
#Entity
public class entityB{
#Id
private int id;
#JoinTable(name = "associated_entities",
joinColumns = { #JoinColumn(name = "entityA_id") },
inverseJoinColumns = { #JoinColumn(name = "entityB_id") })
private List<EntityA> entitiesA;
//getters and setters
}
The solution I have found, but is not ideal as the logic is not done by hibernate. Had to write the logic in the DAOImpl.
Example code:
public Long getQuery(String criteria, String, fromdate, String todate){
Query theQuery = currentSession.createQuery(
"from EntityA a "+
"where a.CustomDetails >= :from "+
"and a.CustomDetails <= :to");
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate From = LocalDate.parse(fromDate, formatter);
LocalDate To = LocalDate.parse(toDate, formatter);
theQuery.setParameter("from", From);
theQuery.setParameter("to", To);
Long count = (long)0;
List<EntityA> entities= theQuery.list();
for(EntityA EA:entities) {
for(EntityB EB: EA.getEntityB()) {
if(EB.someValue().equals(criteria)) count++;
}
}
return count;
Another solution I have found and is much preferred as the logic is performed by hibernate, which I have found to be a lot more faster, is to use two separate queries and utilise where :foo in elements()
Code example below (not matching question example, but idea and use of elements() should be clear)
Query<Object1> q1 = currentSession.createQuery("from Object1 o where o.objectNumber= :objectNumber");
q1.setParameter("objectNumber", objectNumber);
Object1 obj1 = q1.getSingleResult();
Query<Long> q2 = currentSession.createQuery("select count(id) from Object2 o where :object1param in elements(o.associatedObjects));
q2.setParameter("object1param ", obj1);

Spring Data JPA Specification Predicate for a #OneToMany Collection not working

For background:
I have built a module that captures a list of a historical events that occur against an asset over its life and using JPA specifications using spring-data-jpa with hibernate to run the dynamic query using the JPA SpecificationExecutor interface. I have the following historical event JPA object with a many to one asset this historical event is directly against and other associated assets this historical event is also associated with defined in a many-to-many relationship. I am trying to write a JPA Specification predicate that pulls all historical events for a given asset that the asset is either directly against or associated too by using the includeAssociations flag in the predicate. When I try to execute the predicate I am not getting the correct results when I have the includeAssociations flag set to true. I would expect it would by default return at a minimum all the historical events they are directly as if the includeAssociations was false plus any ones they are indirectly associated with. I need help figuring out why this predicate is not returning back what I would expect. Any help is much appreciated!
Here is my Historical Event JPA object:
#Entity
#Table(name = "LC_HIST_EVENT_TAB")
public class HistoricalEvent extends BaseEntity implements Comparable<HistoricalEvent>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(targetEntity = Asset.class, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "ASSET_ID")
private Asset asset;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, targetEntity = Asset.class)
#JoinTable(name = "LC_HIST_EVENT_ASSETS", joinColumns =
{
#JoinColumn(name = "HIST_EVENT_ID", referencedColumnName = "id")
}, inverseJoinColumns =
{
#JoinColumn(name = "ASSET_ID", referencedColumnName = "id")
}, uniqueConstraints =
{
#UniqueConstraint(columnNames =
{
"HIST_EVENT_ID", "ASSET_ID"
})
})
#BatchSize(size=10)
#OrderBy("partCatalogItem.partID, serialNumber ASC")
private Set<Asset> associatedAssets;
#Column(name = "START_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar startDate;
#Column(name = "END_DATE", nullable = true)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar endDate;
}
JPA Metamodel for Historical Event:
#StaticMetamodel(HistoricalEvent.class)
public class HistoricalEvent_ extends BaseEntity_
{
public static volatile SingularAttribute<HistoricalEvent, Asset> asset;
public static volatile SetAttribute<HistoricalEvent, Asset> associatedAssets;
public static volatile SingularAttribute<HistoricalEvent, Calendar> startDate;
public static volatile SingularAttribute<HistoricalEvent, Calendar> endDate;
public static volatile SingularAttribute<HistoricalEvent, String> type;
public static volatile SingularAttribute<HistoricalEvent, String> description;
public static volatile SingularAttribute<HistoricalEvent, HistoricalEvent> triggeringEvent;
public static volatile SetAttribute<HistoricalEvent, HistoricalEvent> associatedEvents;
public static volatile MapAttribute<HistoricalEvent, String, HistoricalEventMap> data;
}
Here is my Asset JPA Object:
#Entity
#Table(name = "LC_ASSET_TAB")
public class Asset extends BaseEntity implements Comparable<Asset>, Serializable
{
private static final long serialVersionUID = 1L;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = PartCatalog.class)
#JoinColumn(name = "PART_CATALOG_ID", nullable = false)
private PartCatalog partCatalogItem;
#Column(name = "SERIAL_NO", nullable = false)
private String serialNumber;
#Column(name = "DATE_INTO_SERVICE", nullable = false)
#Temporal(value = TemporalType.TIMESTAMP)
private Calendar dateIntoService;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "asset", targetEntity = AssetMap.class)
#MapKey(name = "fieldName")
#BatchSize(size=25)
private Map<String, AssetMap> data;
}
Asset Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Here is my Part Catalog JPA object:
#Entity
#Table(name = "LC_PART_CATALOG_TAB")
public class PartCatalog extends BaseEntity implements Comparable<PartCatalog>, Serializable
{
private static final long serialVersionUID = 1L;
#Column(name = "PART_ID", length=100, nullable = false)
private String partID;
#Column(name = "NSN", length=100, nullable = true)
private String nsn;
#Column(name = "DESCRIPTION", length=250, nullable = false)
private String description;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "partCatalogItem", targetEntity = PartCatalogMap.class)
#MapKey(name = "fieldName")
private Map<String, PartCatalogMap> data;
}
Part Catalog Metamodel:
#StaticMetamodel(PartCatalog.class)
public class PartCatalog_ extends BaseEntity_
{
public static volatile SingularAttribute<PartCatalog, String> partID;
public static volatile SingularAttribute<PartCatalog, String> nsn;
public static volatile SingularAttribute<PartCatalog, String> description;
public static volatile MapAttribute<PartCatalog, String, PartCatalogMap> data;
}
Specification Predicate for returning historical events by a given Part Number and Serial Number:
PROBLEM: If includeAssociations is false, it returns fine however soon as it is true, it returns the wrong list of associations and never returns any results from the events the asset is directly tied too like if the includeAssociations was false. This is where I need help how to best write the criteria builder query to properly pull the data.
These are the two JPQL queries I am trying to combine into the Predicate using the Criteria API:
Normal:
#Query("SELECT he FROM HistoricalEvent he WHERE he.asset.partCatalogItem.partID =:partID AND he.asset.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate")
Association:
#Query("SELECT he FROM HistoricalEvent he INNER JOIN he.associatedAssets associated WHERE associated.partCatalogItem.partID =:partID AND associated.serialNumber =:serialNumber " +
"AND he.startDate >:startDate AND he.endDate <:endDate");
/**
* Creates a specification used to find historical events by a given asset part number and serial
* parameter.
*
* #param partID - part identifier
* #Param serialNumber
* #return Historical Event Specification
*/
public static Specification<HistoricalEvent> hasPartAndSerial(final String partID, final String serialNumber, final Boolean includeAssociations)
{
return new Specification<HistoricalEvent>() {
#Override
public Predicate toPredicate(Root<HistoricalEvent> historicalEventRoot,
CriteriaQuery<?> query, CriteriaBuilder cb) {
if (partID == null || partID == "")
{
return null;
}
if(serialNumber == null || serialNumber =="")
{
return null;
}
Path<Asset> assetOnEvent = historicalEventRoot.get(HistoricalEvent_.asset);
Path<PartCatalog> partCatalogItem = assetOnEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatch = partCatalogItem.get(PartCatalog_.partID);
Expression<String> serialToMatch = assetOnEvent.get(Asset_.serialNumber);
if(includeAssociations)
{
SetJoin<HistoricalEvent, Asset> assetsAssociatedToEvent = historicalEventRoot.join(HistoricalEvent_.associatedAssets);
Path<PartCatalog> partCatalogItemFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.partCatalogItem);
Expression<String> partIdToMatchFromAssociatedAsset = partCatalogItemFromAssociatedAsset.get(PartCatalog_.partID);
Expression<String> serialToMatchFromAssociatedAsset = assetsAssociatedToEvent.get(Asset_.serialNumber);
return cb.or(cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase())),
cb.and(cb.equal(cb.lower(partIdToMatchFromAssociatedAsset), partID.toLowerCase()), cb.equal(cb.lower(serialToMatchFromAssociatedAsset), serialNumber.toLowerCase())));
}
else
{
return cb.and(cb.equal(cb.lower(partIdToMatch), partID.toLowerCase()), cb.equal(cb.lower(serialToMatch), serialNumber.toLowerCase()));
}
}
};
}
Finally I am calling this to find the historical events:
#Override
public Page<HistoricalEvent> getByCriteria(String type, String partID,
String serialNumber, Calendar startDate, Calendar endDate,
Boolean includeAssociations, Integer pageIndex, Integer recordsPerPage)
{
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Searching historical event repository for type of " + type + " , part id of " + partID +
" , serial number of " + serialNumber + " , start date of " + startDate + " , end date of " + endDate + ", include associations flag of " + includeAssociations
+ " , pageIndex " + pageIndex + " and records per page of " + recordsPerPage);
Page<HistoricalEvent> requestedPage = historicalEventRepository.findAll(Specifications
.where(HistoricalEventSpecifications.hasType(type))
.and(HistoricalEventSpecifications.greaterThanOrEqualToStartDate(startDate))
.and(HistoricalEventSpecifications.lessThanOrEqualToEndDate(endDate))
.and(HistoricalEventSpecifications.hasPartAndSerial(partID, serialNumber, includeAssociations)),
DatabaseServicePagingUtil.getHistoricalEventPagingSpecification(pageIndex, recordsPerPage));
LOGGER.info("HistoricalEventDatabaseServiceImpl - getByCriteria() - Found " + requestedPage.getTotalElements() + " that will comprise " + requestedPage.getTotalPages() + " pages of content.");
return requestedPage;
} UPDATE: i have been able to get the specification if the historical event was either directly or indirectly associated working however using the following Predicate 1 = cb.equals(cb.lower(partIDToMatch, partID.toLowercase()); Predicate2 = cb.equals(cb.lower(serialToMatch), serialNumber.toLowercase(); Predicate3 = cb.or(Predicate1, Predicate2 ); Predicate4 = cb.equals(cb.lower(partIDToMatchFromAssociatedAsset), partIDToMatch.toLowercase()); Predicate5 = cb.equals(cb.lower(serialNumberFromAssociatedAsset), serialNumberToMatch.toLowercase()); Predicate6 = cb.and(Predicate4, Predicate5); Predicate7 = cb.or(Predicate3,Predicate6); When i return Predicate I only get results matching Predicate6 not either one as i would expect. I want it to pull events where either predicate condition returns a record. Each predicate returns the right data but when i use the cb.or it doesnt combine results as i would expect. What am I missing?
You have to start printing the query and parameters value that are bean generated, just enable this properties.
After that you have to analyze your query and make some tests with different combinations to check your jpa specification are falling.
There is no magic way to do that and it's hard and painful :(
Good look

org.hibernate.TransientObjectException: The given object has a null identifier

I got the below Exception when update my Modelclass
18:27:15,203 ERROR [com.sinergia.ea.daoimpl.TypeOfArtifactDaoImpl] ERROR Exception in updateTypeOfArtifact() : o
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.getUpdateId(DefaultSaveOrUpdateEventListener.
at org.hibernate.event.def.DefaultUpdateEventListener.getUpdateId(DefaultUpdateEventListener.java:46) [:3
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventList
at org.hibernate.event.def.DefaultUpdateEventListener.performSaveOrUpdate(DefaultUpdateEventListener.java
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListen
at org.hibernate.impl.SessionImpl.fireUpdate(SessionImpl.java:564) [:3.2.6.ga]
at org.hibernate.impl.SessionImpl.update(SessionImpl.java:552) [:3.2.6.ga]
at org.hibernate.impl.SessionImpl.update(SessionImpl.java:544) [:3.2.6.ga]
at com.sinergia.ea.daoimpl.TypeOfArtifactDaoImpl.updateTypeOfArtifact(TypeOfArtifactDaoImpl.java:67) [:]
Model Class :
#Entity
#Table(name="TYPE_OF_ARTIFACT")
public class TypeOfArtifactModel implements java.io.Serializable , Identifiable{
/**
*
*/
private static final long serialVersionUID = 2662289176706818360L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TYPE_OF_ARTIFACT_SEQ")
#SequenceGenerator(name = "TYPE_OF_ARTIFACT_SEQ", sequenceName = "TYPE_OF_ARTIFACT_SEQ")
#Column(name="ID",unique=true, nullable=false)
private Integer id;
#Column(name="DESCRIPTION", nullable=true, length=400)
private String description;
#Column(name="NAME", nullable=false, length=50)
private String name;
#OneToMany(fetch = FetchType.LAZY, targetEntity = AdditionalInfoModel.class, mappedBy = "typeOfArtifactID")
private Set<AdditionalInfoModel> additionalInfos = new HashSet<AdditionalInfoModel>(0);
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "TYPE_ARTIFACT_OPERATE_RELATION", joinColumns = { #JoinColumn(name = "TYPE_OF_ARTIFACT_ID") }, inverseJoinColumns = { #JoinColumn(name = "OPERATE_ARTIFACT_ID") })
private Set<TypeOfArtifactModel> checkedItems = new HashSet<TypeOfArtifactModel>(0);
#Column(name="FLAG",length=1)
boolean editable;
public TypeOfArtifactModel() {
}
DaoImppl implementation :
#Override
#Transactional(readOnly = true)
public Boolean updateTypeOfArtifact(#NotNull final TypeOfArtifactModel tipoModel,final Set<AdditionalInfoModel> additionalInfos,final Set<TypeOfArtifactModel> checkedItems) {
try {
System.out.println("Dao Impl Name :"+tipoModel.getName());
System.out.println("Dao Impl Description :"+tipoModel.getDescription());
System.out.println("Dao Impl CheckedItems :"+tipoModel.getCheckedItems());
if(additionalInfos !=null && !(additionalInfos.isEmpty())){
for(AdditionalInfoModel item : additionalInfos){
getSession().update(item);
}
tipoModel.setAdditionalInfos(additionalInfos);
}
getSession().update(tipoModel);
return Boolean.TRUE;
} catch (Exception e) {
log.error(" ERROR Exception in updateTypeOfArtifact() ", e);
return Boolean.FALSE;
}
}
I got the above exception only when i use the update() method if i use the saveOrUpdate() there is no exception but in saveOrUpdate() method new record has created, its not update the record, Could you please tell me whats the wrong in that
The method in which you're trying to update your entity is annotated as #Transactional(readOnly = true). Is that deliberate? That seems wrong.
The problem is that you've passed an object to Hibernate that doesn't have a row in the database with a matching id.
In DefaultSaveOrUpdateEventListener.getUpdateId Hibernate attempts to the read the identifier from the object you're updating but finds that it's null.
Are you sure that the object you're trying to update was previously saved? Is the #Id null at the point that it's loaded? What is the value of the ID column for this entity in the database? Has anything

Resources