JPA merge unable to update values in table - java

I am trying to use JPA to make a simple update to my table column in MYSQL.
Here is the snippet:
public Node get_folder(long fileID) {
try {
List resList = nodeFacade.list_parent(fileID, LinkType.FILE);
List resList1 = nodeFacade.list_parent(fileID, LinkType.VER_LINKS);
if (resList == null) {
throw new Exception("failed to get folder of a file." +
nodeFacade.getMsg());
}
if (resList.size() > 1) {
throw new Exception("one file cannot be attached to more than one folder(s).");
}
//fixed bugs: modified here to display different version
if(resList1.size()==1){
Fmedia fmedia = em.find(fileID,Fmedia.class);
fmedia.setFstatus(2);
em.merge(fmedia);
return (Node) resList1.get(0);
}
Now, fmedia is the table and I want to update an existing record of a value in fstatus column of the table. The error I got was:
Caused by: com.mysql.jdbc.exceptions.MySQLIntegrityConstraintViolationException: Column 'FEXT' cannot be null
FEXT is a column in the same table and it's a not nullable column but there's already a value tied to that record.The exception is thrown at the line em.merge(fmedia). I am confused what is the issue here.
I couldn't use save since my JPA is the older version.
Does anyone know what am I doing wrong?
EDIT: Entity code:
#Entity
#Table(name = "fmedia")
public class Fmedia implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "NODEID")
private Long nodeid;
#Basic(optional = false)
#Column(name = "FONAME")
private String foname;
#Basic(optional = false)
#Column(name = "FGNAME")
private String fgname;
#Basic(optional = false)
#Column(name = "FEXT")
private String fext;
#Basic(optional = false)
#Column(name = "FFULPATH")
private String ffulpath;
#Column(name = "FTYPE")
private int ftype;
#Basic(optional = false)
#Column(name = "FSIZE")
private long fsize;
#Column(name = "FREMARK")
private String fremark;
#Column(name = "FSTATUS")
private int fstatus;
#Column(name = "FDESC")
private String fdesc;
#Column(name = "MODIFYDATE")
#Temporal(TemporalType.DATE)
private Date ModifyDate;
#Size(max = 3000)
#Column(name = "FNTFREMARK")
private String fntfremark;
#Transient
private Fverinfo fver;

Merge is used for detach entities.
If you want to update an existing entity, use fetch + update.
1) fetch exiting Entity by nodeid
2) update necessary fields on that Entity (e.g. fmedia.setFstatus(2))
3) save the entity (if you're using Hibernate, the save is not necessary, Hibernate will still issue an Update statement)
Fmedia fmedia = repository.findOne(nodeid); //manged entity-update
fmedia.setFstatus(2); // hibernate will issue an update, cause the entity is managed
repository.save(fmedia) //redundant save anti-pattern, don't call save
Using entityManager:
entityManager.find(Fmedia.class, nodeId);
If you want to fetch your entity based on something else, rather than the primaryId, you will have to crate a query:
entityManager.createQuery("select m from Fmedia m where m.foname = :foname");

Merge does not do that you need to fetch the existing entity and update that entity instead of persisting a new one with same id, so like:
Fmedia fmedia = em.find(FMEdia.class, fileId);
fmedia.setFstatus(2);
em.persist(fmedia);
Merge is for attaching existing but detached entity to persistence context and your new FMedia(..) is not detached but just new.

Related

How to use derived columns in Spring Data JPA associations

I have an entity TeamActivity:
#Entity
#Table(name = "teams_to_activities")
public class TeamActivity {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "team_id", nullable = false)
private String teamId;
#Column(name = "activity_set_id", nullable = false)
private String activitySetId;
#Id
#Column(name = "scoped_team_activity_id", nullable = false)
private String scopedTeamActivityId;
}
And another entity ActivitySet:
#Entity
#Table(name = "activity_sets")
public class ActivitySet {
#Column(name = "scope_id", nullable = false)
private String scopeId;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "description", nullable = false)
private String description;
#Id
#Column(name = "scoped_activity_set_id", nullable = false)
private String scopedActivitySetId;
}
There's no index on any other column besides the PK in both tables.
There's no FK constraint creating a relationship between these tables whatsoever. I have no idea why as this is a legacy system.
Technically, if I fetch a TeamActivity record, I can pick the scope_id and activity_set_id from it and combine them to form a scoped_activity_set_id which would be a valid PK to fetch the corresponding ActivitySet.
I know that TeamActivity -> ActivitySet is a N -> 1 association
I would like to leverage Spring Data JPA features to create an association from TeamActivity to ActivitySet such that when I fetch a TeamActivity from TeamActivityRepository, the corresponding ActivitySet is also returned.
I have created an association like this before using a combination of #JoinColumn and #MapsId but there was actually a single FK to use which is different here where source table has 2 columns I can combine to get the target's key.
If you are fully in control of the database, I may propose you create a Materialized View with the contents you desire from both tables and handle it as any other table with JPA, i.e, create #Entity model and CrudRepository<MVTeamActivitySet, String>.
If you are not fully in control of the database, one easy way to achieve it is to simply create a method that internally executes two lookup queries and retrieves the expected model you want. You will still be using using JPA correctly.
Querying two tables and joining desired fields in the code layer is quite common with denormalized DBs, sometimes you want to avoid the overhead of a Materialized View.
#Override
public TeamActivitySetDto findById(String scopedTeamActivityId) throws DemoCustomException {
Optional<TeamActivity> teamActivityEntity = teamActivityDao.getById(scopedTeamActivityId);
if(teamActivityEntity.isEmpty()) {
throw new DemoCustomException("teamActivity record not found");
}
String scopedActivitySetId =
teamActivityEntity.get().getScopeId() + ":" + teamActivityEntity.get().getActivitySetId();
Optional<ActivitySet> activitySetEntity = activitySetDao.getById(scopedActivitySetId);
if(activitySetEntity.isEmpty()) {
throw new DemoCustomException("activitySet record not found");
}
return TeamActivitySetDto.builder()
.description(activitySetEntity.get().getDescription())
.name(activitySetEntity.get().getName())
.scopedActivitySetId(activitySetEntity.get().getScopedActivitySetId())
.activitySetId(teamActivityEntity.get().getActivitySetId())
.scopedTeamActivityId(teamActivityEntity.get().getScopedTeamActivityId())
.scopeId(teamActivityEntity.get().getScopeId())
.teamId(teamActivityEntity.get().getTeamId())
.build();
}

Spring Data update entity with yesterday's date

I have some problems with update (and also insert) data into my database. I have an entity with some integer properties, some String properties, but also is there one property with LocalDate type, and it has to be unique.
I put a lot of entities like that into the database, but user needs to edit it and update some properties. When I tried to test it and change some String property and save updated entity to db I saw this error log in the console:
Duplicate entry '2019-07-27' for key 'work_day_workday_date_uindex'
As you can see, Hibernate tries to put object with yesterday's date. But... why? I checked it in traditional ( :D ) way -> by entering System.out.println instruction before saving object into database.
Log shows me a correct date in printing:
WorkDay{id=296, date=2019-07-28, workingTime=8,....
So I think that the problem is connected with differences in time between database and application.
I found some tips here, in StackOverflow. Somebody said that removing serverTimezone=UTC from application.properties in SpringBoot could help. And it fixed the problem - yesterday I updated the entity successfully. But today I come back to coding and the problem appeared again.
I hope that maybe some of you had this problem in past and know some solution - it will be very helpful for me :)
Here is WorkDay Entity:
#Entity
#Table(name = "work_day")
public class WorkDay implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id_workday")
private Long id;
#NotNull
#Column(name = "workday_date", nullable = false, unique = true)
private LocalDate date;
#NotNull
#Column(name = "working_time", nullable = false)
private Integer workingTime;
#Column(name = "booked_artist")
private String bookedArtist;
#ManyToOne
#JoinColumn(name="workday_importance_id")
private WorkDayImportance workDayImportance;
#ManyToMany
#JoinTable(name = "workday_employee",
joinColumns = {#JoinColumn(name = "workday_id",
referencedColumnName = "id_workday")},
inverseJoinColumns = {#JoinColumn(name="employee_id",
referencedColumnName = "id_employee")})
private List<Employee> employers;
#OneToMany(mappedBy = "workDay", cascade = CascadeType.REMOVE)
private List<Comment> comments;
Here is some code where I perform this operation:
public void setBookedArtist(Long workDayId, String artist){
workDayRepository
.findById(workDayId)
.ifPresent(workDay -> workDayDetailsService.saveBookedArtist(workDay, artist));
}
void saveBookedArtist(WorkDay workDay, String artist){
if(artist != null && !artist.equals("")) {
workDay.setBookedArtist(artist);
workDayRepository.save(workDay);
}
}
The entity repository is Spring Data interface which extends JpaRepository.
Best regards!
Setting the Id of workDay before saving the record should work and as we don't want to update the date set updatable = false as to below
public void setBookedArtist(Long workDayId, String artist){
workDayRepository
.findById(workDayId)
.ifPresent(workDay -> workDayDetailsService.saveBookedArtist(workDay, artist));
}
void saveBookedArtist(WorkDay workDay, String artist){
if(artist != null && !artist.equals("")) {
workDay.setId(workDay.getId());
workDay.setBookedArtist(artist);
workDayRepository.save(workDay);
}
}
#NotNull
#Column(name = "workday_date", nullable = false, unique = true, updatable = false)
private LocalDate date;

New child entity is getting created while Updating Parent in OneToMany JPA

Hi I have a Parent Child tables as below. There are below problems.
I am using Spring Data Repository (org.springframework.data.repository)
Question 1
While I am Persisting the Parent child entries are getting inserted as well, but while I am trying to update the the Parent (Where new changes are present both in parent & child), new child entries are getting inserted in child table with the updated data instead of updating the old child data.
Question 2
I am making a patch call here , the data is coming from UI as json, I have some audit trail fields like createdBy, createdTimestamp, updatedBy, updatedTimestamp. These fields are getting populated in backend service in Create & Update operations respectively. Now in update operation, my dto don't have any values for createdBy & createdTimestamp, so in DB it is getting set as null.I am confused here, I am using a patch call then it should retain the old value right ?
Please suggest If I have missed any
Parent:
#Id
#SequenceGenerator(name = "DIVERSITY_TEMPLATE_ID", sequenceName = "DIVERSITY_TEMPLATE_ID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DIVERSITY_TEMPLATE_ID")
#Column(name = "DIVERSITY_TEMPLATE_ID")
private Integer diversityTemplateId;
#Column(name = "LABEL")
private String label;
#Column(name = "RELATIONSHIP_TYPE")
private String relationshipType;
#Column(name = "CREATED_BY")
private String createdBy;
#Column(name = "CREATED_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date createdTimestamp;
#Column(name = "UPDATED_BY")
private String updatedBy;
#Column(name = "UPDATED_TIMESTAMP")
#Temporal(TemporalType.TIMESTAMP)
private Date updatedTimestamp;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "diversityTemplate", fetch = FetchType.LAZY)
private List<DiversityTemplateAttribute> attributes = new ArrayList<>();
/**
* #return the attributes
*/
public List<DiversityTemplateAttribute> getAttributes() {
return attributes;
}
/**
* #param attributes the attributes to set
*/
public void setAttributes(List<DiversityTemplateAttribute> attributes) {
for (DiversityTemplateAttribute diversityTemplateAttribute : attributes) {
diversityTemplateAttribute.setDiversityTemplate(this);
}
this.attributes = attributes;
}
Child:
#Id
#SequenceGenerator(name = "DIVERSITY_TEMPLATE_ATTR_ID", sequenceName = "DIVERSITY_TEMPLATE_ATTR_ID", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "DIVERSITY_TEMPLATE_ATTR_ID")
#Column(name = "DIVERSITY_TEMPLATE_ATTR_ID")
private Integer diversityTemplateAttributeId;
#Column(name = "AA")
private String aa;
#Column(name = "BB")
private String bb;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DIVERSITY_TEMPLATE_ID", referencedColumnName = "DIVERSITY_TEMPLATE_ID")
private DiversityTemplate diversityTemplate;
Sample Update JSON
{
"diversityTemplateId": 681,
"label": "SAMPLE_LABEL_463_UPDATED",
"relationshipType": "Married",
"attributes": [{
"diversityTemplateId": 681,
"diversityTemplateAttributeId": 3006,
"aa": "AA",
"bb": "BB Updated",
}, {
"diversityTemplateId": 681,
"diversityTemplateAttributeId": 3006,
"aa": "aa Updated",
"bb": "bb"
}
]
}
Service Layer:
DiversityTemplate updatedEntity = diversityTemplateRepository.save(diversityTemplate);
Question 3
In case of Mapping back the Entity object (when I get it from GET/CREATE operation) to DTO object I am not able to set the FK id in child object , So as a workaround I am iterating through the child list of objects & setting the pk of parent in the child DTO manually, is there any better way of doing this. I have added anothe transient column in the child ENTITY class with same pk Column name as in Parent, but then also it's value is coming as zero, is there any better way ? Please find below my work around.
DiversityTemplate updatedEntity = diversityTemplateRepository.save(diversityTemplate);
List<DiversityTemplateAttributeDTO> attrbiteList = new ArrayList<>();
for (DiversityTemplateAttribute attribute : updatedEntity.getAttributes()) {
DiversityTemplateAttributeDTO attributeDTO = resourceMapper
.convertToDiversityTemplateAttributeDTO(attribute);
attributeDTO.setDiversityTemplateId(updatedEntity.getDiversityTemplateId());
attrbiteList.add(attributeDTO);
}
DiversityTemplateDTO updatedDiversityTemplateDTO = resourceMapper.convertToDiversityTemplateDTO(updatedEntity);
diversityTemplateDTO.setAttributes(attrbiteList);
Please suggest
I had all the same issues as you.
Answer 1: What I ended up doing was setting the orphanRemoval flag equal to true on the #OneToMany. if you do this just be aware that you can no longer set that list of children equal to a new list or else it will through and error. You have to remove and append depending on what you want to delete and add.
Answer 2: You are missing the #EntityListeners(AuditingEntityListener.class) at the top of you parent class but just in case that doesn't work, here is a link to what I used to get it working https://dzone.com/articles/spring-data-jpa-auditing-automatically-the-good-stuff
Answer 3: The reason the id is not setting is because in the java code you have to set the reference to the parent in the child.
i.e. child.setDiversityTemplate(parent);
Once you do that it will properly map it in the table and you'll be able to retrieve it no problem.

Embeddable PK Object not populating after persist and flush

I have an embedded PK object that doesn't populate the id field after persisting and flushing to the database. The ID is an auto-increment field in the database.
Now normally, I would just try a refresh, but it throws the following error:
"Entity no longer exists in the database: entity.Customers[ customersPK=entity.CustomersPK[ id=0, classesId=36 ] ]."
public class Customers implements Serializable {
#EmbeddedId
protected CustomersPK customersPK;
...
}
#Embeddable
public class CustomersPK implements Serializable {
#Basic(optional = false)
#Column(name = "id")
private int id;
#Basic(optional = false)
#NotNull
#Column(name = "classes_id")
private int classesId;
.....
}
And here's the code that makes the call
Classes cl = em.find(Classes.class, classId);
CustomersPK custPK = new CustomersPK();
custPK.setClassesId(cl.getId());
Customers cust = new Customers(custPK);
em.persist(cust);
em.flush();
// The problem is right here where id always equals 0
int id = cust.getCustomerspk().getId();
Thanks for the help.
Why would the id not be 0, you have never set it?
If it is a generated id, you need to annotate it using #GeneratedValue, otherwise you need to set the value.

Could EclipseLink be causing a ORA-00957: duplicate column name error because of a #DiscriminatorColumn in a polymorphic entity?

I'm getting this exception in an attempt to persist an entity object in an Oracle database, and I only started getting this error after switching my JPA project to EclipseLink 2.0 from Hibernate, and I'm using "entity inheritance" if this could have anything to do with it (which I highly suspect).
*
Caused by: javax.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.0.1.v20100213-r6600): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: ORA-00957: duplicate column name
Error Code: 957
Call: INSERT INTO SUREC (ID, PERSON_ID, SURECID, VERSIYONNO, FAZ, FORM_TARIH, DURUMKODU_ID, surecId) VALUES (...
*
The exception message suggests that SURECID is generated twice in the SQL which seems to be causing the duplicate column error, however surecId is defined once as a property and annotated as a discriminator column in the entity class: (see below)
The base entity class resembles:
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
#DiscriminatorColumn(name = "surecId")
public class Surec implements java.io.Serializable {
private static final long serialVersionUID = -6008473677883005878L;
#Column(name = "ID")
private Long id;
#Basic(optional = false)
#Column(name = "FAZ")
private int faz;
#Basic(optional = false)
#Column(name = "FORM_TARIH")
#Temporal(TemporalType.DATE)
private Date formTarih;
#Column(name = "PERSON_ID")
private Integer personId;
// #Column(name = "SURECID", updatable = false, length=17)
#Column(updatable = false, length=17)
private String surecId;
#Column(name = "VERSIYONNO")
private Long versiyonno;
#JoinColumn(name = "DURUMKODU_ID", referencedColumnName = "ID")
#ManyToOne
private DurumKod durumKodu;
public Surec() {
}
public Surec(String surecId) {
this.surecId = surecId;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
.
.
.
public String getSurecId() {
return surecId;
}
public void setSurecId(String surecId) {
this.surecId = surecId;
}
.
.
.
I commented the "#Column(name=..." annotation jus to see if it could be causing the duplicate column error, but it didn't work out.
And below is the polymorphic entity extending Surec.java above:
#Entity
#DiscriminatorValue("atf")
public class MailOrder extends Surec {
private static final long serialVersionUID = 8333637555543614502L;
#Column(name = "AMOUNT")
private Double amount;
#Basic(optional = false)
#Column(name = "CURRENCY", length = 17)
private String currency;
#Column(name = "BANK")
private String Bank;
#Column(name = "ACCOUNT_ID", length = 31)
private String accountId;
#Column(name = "INVOICE_ID")
private Integer invoiceId;
public MailOrder() {
}
public MailOrder(String surecId) {
super(surecId);
}
public String getCurrency() {
return currency;
}
.
.
.
The error occurs when I try to persist this very sub-entity.
It doesn't override any property of its superclass, although I'm not sure if it's the constructor...
Any advise to resolve the problem (and acknowledgement of any possible EclipseLink or Oracle (or my!) bug will be appreciated.
This is a common issue if you have a relationship mapping that uses this field, and has to do with case sensitivity. Try indicating the descriminator colum as
#DiscriminatorColumn(name = "SURECID")
EclipseLink is case sensitive by default, which is why surecId is seen as a different field from SURECID. You can make EclipseLink case insensitive by using the eclipselink.jpa.uppercase-column-names property, which when set to true, forces EclipseLink to use the upper case on field name comparisons.
I guess you need to mark the property as insertable = false, updateable = false, since it's already inserted as a discriminator:
#Column(insertable = false, updatable = false, length=17)
private String surecId;

Categories

Resources