I'm running into a very annoying issue whereby an entity loses its state after the object is merged into the EntityManager.
In the application there's a "Dossier" with ExpenditureStatements which has a number of expenditures.
These expenditures can be (partially) claimed from multiple debtors.
An ExpenditureStatementClaim is created for the ExpenditureStatement.
An ExpenditureClaim is created for each Expenditure on the ExpenditureStatement.
Both the ExpenditureClaims and the ExpenditureStatementClaim are persisted without any issues.
The expenditures however lose their state after the merge on the entitymanager is called:
em.merge(dossier).
However, the data in each expenditure reverts back to its last state in the database.
I've already tried cascading only top down, i've made changes to equals/hashcode but this didn't change anything.
Does anyone have a clue as to what might be causing this issue?
Dossier:
#Entity
#Table(name = "DOSSIER")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DOSSIER_TYPE", discriminatorType = DiscriminatorType.STRING, length = 48)
public abstract class Dossier {
#OneToMany(mappedBy = ExpenditureStatement.PROP_DOSSIER, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private List<ExpenditureStatement> expenditureStatements = new ArrayList<ExpenditureStatement>();
}
ExpenditureStatement:
#Entity
#Table(name = "EXPENDITURE_STATEMENT")
public class ExpenditureStatement {
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.MERGE})
#JoinColumn(name = "DOSSIER_ID", nullable = false)
private Dossier dossier;
#OneToMany(mappedBy = Expenditure.PROP_EXPENDITURE_STATEMENT, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<Expenditure> expenditures = new ArrayList<Expenditure>();
#OneToMany(mappedBy = ExpenditureStatementClaim.PROP_EXPENDITURE_STATEMENT, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureStatementClaim> expenditureStatementClaims = new ArrayList<ExpenditureStatementClaim>();
}
Expenditure:
#Entity
#Table(name = "EXPENDITURE")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "EXPENDITURE_ORIGIN_CD", discriminatorType = DiscriminatorType.STRING, length = 48)
public abstract class Expenditure extends EntityObject<Long> {
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_STATEMENT_ID", nullable = false)
private ExpenditureStatement expenditureStatement;
#OneToMany(mappedBy = ExpenditureClaim.PROP_EXPENDITURE, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureClaim> claims = new HashSet<>();
#NotNull
#Column(name = "EXPENDITURE_STATUS_CD", nullable = false)
#Enumerated(EnumType.STRING)
private ExpenditureStatus status;
public abstract BigDecimal getAmount();
}
ExpenditureStatementClaim:
#Entity
#Table(name = "DEEL_STAAT")
public class ExpenditureStatementClaim {
#NotNull
#ManyToOne(cascade = {CascadeType.MERGE, CascadeType.PERSIST})
#JoinColumn(name = "EXPENDITURE_STATEMENT_ID", nullable = false)
private ExpenditureStatement expenditureStatement;
#OneToMany(mappedBy = ExpenditureClaim.PROP_EXPENDITURE_STATEMENT_CLAIM, cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private Collection<ExpenditureClaim> expenditureClaims = new ArrayList<>();
#ManyToOne(cascade=CascadeType.ALL)
#JoinColumn(name = "INVOICE_ID")
private Invoice invoice;
}
ExpenditureClaim:
#Entity
#Table(name = "EXPENDITURE_CLAIM")
public class ExpenditureClaim extends EntityObject<Long> {
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_ID", nullable = false)
private Expenditure expenditure;
#Column(name = "AMOUNT", precision = NumberConstants.CURRENCY_PRECISION, scale = NumberConstants.CURRENCY_OPERATION_SCALE)
private BigDecimal amount;
#ManyToOne(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "EXPENDITURE_ST_CLAIM_ID", nullable = false)
private ExpenditureStatementClaim expenditureStatementClaim;
}
The issue was not caused by a problem with the mapping, but due to a bug in the (quite old) version of EclipseLink the project was using, whereby a newly created object would not cascade changes made to an already existing object.
https://bugs.eclipse.org/bugs/show_bug.cgi?id=340802
Just a good reminder that whenever possible you should try to update the version of the dependencies in your project...
Related
I am using spring-boot-starter-data-jpa 1.5.1.RELEASE which internally uses hibernate-core 5.0.11.Final
My entity looks like this:
AreaDto
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH)
#JoinColumn(name = "AREA_ROWID")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "FESTIVAL", joinColumns = {
#JoinColumn(name = "AREA_ROWID", referencedColumnName = "AREA_ROWID")}, inverseJoinColumns = {
#JoinColumn(name = "FESTIVAL_ROWID", referencedColumnName = "FESTIVAL_ROWID")})
private Collection<ActionDto> festivalActions;
}
FestivalDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
ActionDto
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I'm trying to make sense of the below ideas:
What is the strategy used by hibernate to decide on the festival_rowid (or festival_row ids) used to get all the associated action? How will hibernate generated SQL query vary if i change festivalActions fetch strategies between LAZY and EAGER? I know about proxying, collection proxying and all, my question is specific to how those sql is generated and how it may have an impact on deciding the value of bind parameter.
Is my mapping accurate or should I be using a multimap for this relationship since an area could have multiple festival and each festival could have multiple actions
Background:
I am getting below error which goes away if I change the fetch type from LAZY to EAGER. Hoping to understand the behaviour for gaining some confidence in the fix. I have read SO and error
org.hibernate.HibernateException: More than one row with the given identifier was found: data.dto.ActionDto#280856b5
This mapping does not make much sense. You can't map festivalActions this way because there is no way to persist the state properly through such a mapping. Also festival in AreaDto should be mapped by the area in FestivalDto. Try the following instead:
#Entity
#Table(name = "AREA")
#EntityListeners(AuditingEntityListener.class)
public class AreaDto {
#Id
#Column(name = "AREA_ROWID")
private String areaRowId;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<FestivalDto> festival;
#OneToMany(cascade = CascadeType.DETACH, mappedBy = "area")
private Collection<ActionDto> actions;
public Collection<ActionDto> getFestivalActions() {
return festival.stream().flatMap(f -> f.actions.stream()).collect(Collectors.toList());
}
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "FESTIVAL")
public class FestivalDto {
#Id
#Column(name = "FESTIVAL_ROWID")
#GeneratedValue(generator = "FESTIVAL_ROWID_SEQ")
private Long festivalRowId;
#ManyToOne(cascade = CascadeType.DETACH, fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "festival")
private Collection<ActionDto> actions = Lists.newArrayList();
}
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "ACTION")
public class ActionDto implements Serializable {
...
#Id
#Column(name = "ACTION_ID")
#GeneratedValue(generator = "ACTION_ID_SEQ")
private Long actionId;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "FESTIVAL_ROWID")
private FestivalDto festival;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne(cascade = DETACH, fetch = FetchType.LAZY)
#JoinColumn(name = "AREA_ROWID")
private AreaDto area;
}
I have this entity
#Builder
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
#Data
#Entity
#Table(name = "visits")
public class Visit {
#EqualsAndHashCode.Include
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
#Column(nullable = false, updatable = false)
private long id;
#Column
private LocalDate date;
#Column
private LocalTime startTime;
#Column
private LocalTime endTime;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "client_id")
private Client client;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "employee_id")
private Employee employee;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "services_booked",
joinColumns = {#JoinColumn(name = "visit_id")},
inverseJoinColumns = {#JoinColumn(name = "service_id")}
)
private Set<Service> servicesBooked = new HashSet<>();
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "visit")
private Set<TimeSlot> timeSlots = new HashSet<>();
}
My controller performs delete action on service bean into transaction:
#Transactional
#DeleteMapping("/{id}")
public ResponseEntity<DeleteVisitResponse> deleteVisit(#PathVariable("id") long visitId,
#RequestAttribute(USER_AUTH) UserAuthDto userAuthDto) {
// some logic
Optional<Visit> visit = visitService.findVisitById(visitId);
// check isPresent via Optional.map
visitService.deleteVisit(visit.get());
// constructing the response
}
And service bean just deletes it:
#Override
public void deleteVisit(#NonNull Visit visit) {
visitRepository.delete(visit);
}
But actually it does not delete it. It performs sequential selects to resolve chained entities. Here are log records of that deletion: https://gist.github.com/bvn13/906582ad39720e033c24ddd6f59f906c
That's all. Why it cannot perform deleting operation?
How to copy entity with multiple #OneToMany?
I have entities:
Profile.java
#Getter
#Setter
#ToString
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#EqualsAndHashCode(exclude = {"id"})
#Entity
#Table(name = "profile")
public class Profile implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
private List<ProfileDoc> documents = new ArrayList<>();
#OneToMany(mappedBy = "profile", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Contact> contacts = new ArrayList<>();
#OneToOne(mappedBy = "profileAddress", cascade = CascadeType.ALL, orphanRemoval = true)
private AddressBirth birthPlace;
}
ProfileDoc
#Getter
#Setter
#JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
#ToString(exclude = "profile")
#EqualsAndHashCode(exclude = {"id", "profile"})
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
#Entity
#Table(name = "profile_doc")
public class ProfileDoc implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "doc_code")
private String documentCode;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="profile_id")
private Profile profile;
#OneToMany(mappedBy = "document", cascade = CascadeType.ALL, orphanRemoval = true)
private List<DocFile> files = new ArrayList<>();
}
How to copy Profile(all field) without id?
as I understand it, you need to copy documents without profile_id in ProfileDoc class
UPD:
I want copy entity from another entity(copy) for save to DB
BeanUtils.copyProperties(profile, newProfile);
if I copy with Id, then the original entity will update, not a new one
I have a LibraryModel class, a LibraryImage class and a LibraryAttribute class. A LibraryModel can have an arbitrary number of LibraryImages and LibraryAttributes.
The error that I get:
org.hibernate.MappingException: Foreign key (FKmbn4xh7xdxv371ao5verqueu3:library_item_attribute [LIBRARY_ITEM_ATTRIBUTE_ID])) must have same number of columns as the referenced primary key (library_item_attribute [LIBRARY_ITEM_ID,LIBRARY_ITEM_ATTRIBUTE_ID])
Here are my annotated Objects:
Library Model:
#Entity
#Table(name = "library_item", uniqueConstraints = {
})
#Inheritance(strategy = InheritanceType.JOINED)
public class LibraryItemModel implements LibraryItem{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ID", unique = true, nullable = false)
private Integer libraryItemId;
#Column(name = "ITEM_TITLE", unique = false, nullable = false)
private String itemTitle;
#Column(name = "IS_PARENT", nullable = false)
private Boolean isParent;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ID", nullable = false)
private LibraryModel libraryModel;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "ITEM_LISTING",
joinColumns = {#JoinColumn(name = "PARENT_LIB_ITEM_ID", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="CHILD_LIB_ITEM_ID", nullable = false)})
private Set<LibraryItemModel> itemChildren = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "itemChildren")
private Set<LibraryItemModel> itemParents = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_IMAGE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable=false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_IMAGE_ID", nullable = false)})
private Set<LibraryImage> itemImages = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "rootLibraryItemModel")
private Set<LibraryModel> libraries = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_ATTRIBUTE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable =false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_ITEM_ATTRIBUTE_ID", nullable = false)})
private Set<LibraryItemAttribute> libraryItemAttributes = new HashSet<>();
}
LibraryImage:
#Entity
#Table(name = "library_image", uniqueConstraints = {
})
public class LibraryImage {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_IMAGE_ID", unique = true, nullable = false)
private Integer libraryImageId;
#Column(name = "IMAGE_LOCATION")
private String imageLocation;
#Column(name = "IMAGE_TITLE")
private String imageTitle;
#Enumerated(EnumType.STRING)
private LibraryImageType imageType;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
Library Attribute:
#Entity
#Table(name = "library_item_attribute", uniqueConstraints = {
})
public class LibraryItemAttribute {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ATTRIBUTE_ID", unique = true, nullable = false)
private Integer libraryItemAttributeId;
#Column(name = "ATTRIBUTE_NAME")
private String attributeName;
#Column(name = "ATTRIBUTE_VALUE")
private String attributeValue;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
This is really frustrating as the LibraryImage class is mapped without problems and doesn't throw any errors, but the LibraryAttribute class which is annotated in an identical way to the LibraryImage class is throwing this error.
Can someone please have a look and let me know what my problem is?
Found the problem.
In the LibraryItemModel class I defined the Join table with the LibraryItemAttribute to be called "LIBRARY_ITEM_ATTRIBUTE", which is the name of the table of the Library item attributes.
The join table is a different table and should have a different table name.
For the Library Image table above, the image table is called library_image, while the join table is called LIBRARY_ITEM_IMAGE
i have an entity Entity1 that have one to many relation with Entity2 as follows:
1- Entity1:
#Entity
#Table(name = "Entity1", catalog = "mydb")
public class Entity1 implements java.io.Serializable {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "entity1", cascade = javax.persistence.CascadeType.ALL)
#OrderBy("id")
private Set<Entity2> collection = new HashSet<Entity2>(
0);
}
2- Entity2: (equals and hashcode method overridden)
#Entity
#Table(name = "entity2", catalog = "advertisedb")
public class Entity2 implements java.io.Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "pkid", unique = true, nullable = false)
#Basic(fetch = FetchType.EAGER)
private long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "fk_entity1", nullable = false)
private Entity1 entity1;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "apid", nullable = false)
private Entity3 entity3;
}
3- Here's how i am removing the entity from the collection:
entity1Obj.getEntity2().remove(entity2);
log.debug("size after remove: "+ entity1Obj.getEntity2().size()); // size decreases correctly, so the entity is removed from the collection
entity1Dao.updateEntity1(entity1);
4- DAO method:
public void updateEntity1(Entity1 entity1) {
getCurrentSession().update(getCurrentSession().merge(entity1));
}
Problem: what i get in the console, is a select query for the entity2 that should be removed, and no delete query, and nothing is getting deleted.
please advise how to fix this issue.
i replaced cascade = CascadeType.ALL with orphanRemoval = true and it works fine now.