JPARepository wont update entity on save - spring-boot

Very simple situation and JPA is killing my brain cells
#Entity
#Table(name = "food_entry")
#ublic class FoodEntry implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "product_name", nullable = false, unique = false, insertable = true, updatable = false, length = 256)
private String name;
#CreatedDate
#Column(name = "instant", updatable = false, unique = false, nullable = false, insertable = true)
private Instant instant = Instant.now();
#Min(value = 0, message = "calories must be positive")
#Column(name = "calories", updatable = false, insertable = true, nullable = false, unique = false)
private long calories;
}
#Transactional
public FoodEntry update(final FoodEntry newEntry, long id) {
final User loggedUser = SecurityUtils.getCurrentLoggedUser();
if (loggedUser == null || !loggedUser.getAuthorities().contains(Authority.ADMIN))
throw new AccessDeniedException("You dont have authorization to perform this action");
FoodEntry current = this.repository.findById(id).orElseThrow(() -> new NotFoundException("Not found FoodEntry with specified id: " + id));
current.setCalories(newEntry.getCalories());
current.setInstant(newEntry.getInstant());
current.setName(newEntry.getName());
try {
this.repository.save(current);
this.repository.flush();
return null;
}
catch (Exception e) {
throw e;
}
}
#Repository
public interface FoodRepository extends JpaRepository<FoodEntry, Long> {}
The code runs, the food entry is queried from database, but when i call save NOTHING HAPPENS,
the JPA simple returns the very same object i passed as parameter and no query runs on database... later get to that entity will return the outdated value
why? this is so simple what am i missing?
The very same code for CREATE works fine... but when i'm trying to update, the save method does nothing

The updatable = false attribute should be changed, JPA will not update the entities with attribute updatable as false so set it to true.
For more reference:
https://www.ibm.com/support/pages/jpa-entities-primary-key-automatically-generated-read-only-annotations

The problem is, that all of those attributes you want to update (calories, instant, name) have set their updatable=false.
Attributes with updatable=false can only be set until the first time you have called .save(..). After that, all those attributes won't be updated anymore, even if the transaction hasn't been flushed.

I found the answer, is something super stupid I going to post here in case someone is stuck with same problem:
https://github.com/spring-projects/spring-data-jpa/issues/1735
JPA wont update entities in case all fields are set to update false, it does not throw an error or exception or any kind of traceable log, it simple ignores the call
as the project had an early requirement of not editing i forgot to alter the entities after it changed

This problem came due to updatable=false, because whenever column is specified as updatable=false then it can not be updated through JPA.

Related

Spring JPA: How to remove OneToOne related entities when master entity is removed?

First of all, thank you for interest in this question.
Here is the issue I am having with my relations.
I have a master entity which is #OneToOne referenced to 2 different tables. But the master entity has no references from those two tables.
#SQLDelete(sql = "UPDATE contract_reservation SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class ContractReservation extends AbsLongEntity {
#Column(nullable = false)
private String reservationNumber;
#Column(name = "date", nullable = false)
private LocalDate date;
#ManyToOne
private Company ownCompany;
Above is my master entity which is basis for two other entities. The code above is not complete, just a gist of what I have.
Below are the two other entities which has ContractReservation as their basis and #NotNull contract_reservation_id.
OriginalContract
#SQLDelete(sql = "UPDATE original_contract SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class OriginalContract extends AbsLongEntity {
#Column(nullable = false, unique = true)
private String contractNumber;
#OneToOne
#JoinColumn(nullable = false, unique = true)
private ContractReservation reservation;
private Boolean deleted;
FleetDashboard
#SQLDelete(sql = "UPDATE fleet_dashboard SET deleted = true WHERE id=?")
#Where(clause = "deleted=false")
public class FleetDashboard extends AbsLongEntity {
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne(cascade = {
CascadeType.REMOVE
})
#JoinColumn(nullable = false)
private ContractReservation contractReservation;
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToOne(cascade = {
CascadeType.REMOVE
})
private OriginalContract originalContract;
So far I have tried CascadeType.REMOVE, ALL. #OnDelete as seen in FleetDashboard entity but none of them worked so far. I also tried to write query but it is taking too long to respond.
What I want here is, when master entity(ContractReservation) is deleted, all related instances should also be deleted from related entities (OriginalContract, FleetDashboard).
For example, if I delete ContractReservation with id = 1, OriginalContract that has contract_reservation_id = 1 should also be deleted and so on. Main problem here seems #OneToOne relation and the fact that I have not referenced related entities in my master entity(ContractReservation).
Is there any way that can solve this issue without referencing related tables into master entity?
Thank you for your answers in advance.

JPA - deleteBy query not working, orphanRemoval=true not working

I am unable to understand why JPA delete is not working. Here is my parent entity:
public class RoleEntity {
//...other attributes like name etc.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false, insertable = false, updatable = false)
private Set<RoleExtEntity> extensions;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "file_id", referencedColumnName = "id", nullable = false)
private FileEntity fileEntity;
}
RoleRepository:
#Repository
public interface RoleRepository extends JpaRepository<RoleEntity, Long> {
#Transactional
int deleteByFileEntityAndName(FileEntity fileEntity, String roleName);
}
I am trying to delete the RoleEntity using FileEntity and RoleName attributes. The delete query returns val = 1 however when I do a findBy again, it gives me the record instead of null (as I think the record should be deleted, both parent and child should be deleted).
FileEntity fileEntity = fileRepository.findByFolderId(id);
RoleEntity roleToBeDeleted = roleRepository.findByFileEntityAndName(fileEntity, roleName);
int val = roleRepository.deleteByFileEntityAndName(fileEntity, roleName);
RoleEntity doesroleExistEntity = roleRepository.findByFileEntityAndName(fileEntity, roleName);
I have tried out various solutions mentioned on this platform like by using:
orphanRemoval = true
#Transactional annotation
flush()
CascadeType.ALL
However, they don't seem to work. Can someone please let me know what I am doing incorrectly here? Thanks!
Update: The issue was that I was calling a wrong method from a persistence service in my code. That method was a readOnlyTransaction() which didn't allow me to do the delete so I had to use another method withTransaction() that solved my issue.
Other database query is logged when you call I think service method?
Yot call the jpa delete method.
JPA Method roleRepository.findByFileEntityAndName(fileEntity, roleName);
return something maybe try show check.

Spring Data JPA update a Row without getting the row ById

I want to update the table using spring-jpa
This is my Entity Class
public class RewardEntity {
#Id
#Column(name = "reward_id", columnDefinition = "bigserial")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rewardId;
#Column(name = "reward_title", nullable = false)
private String rewardTitle;
#Column(name = "reward_text")
private String rewardText;
#Column(name = "reward_type", nullable = false)
private String rewardType;
#Column(name = "reward_for_code", nullable = false)
private String rewardFor;
#Column(name = "reward_from_date", nullable = false)
private OffsetDateTime rewardFromDate;
#Column(name = "reward_to_date", nullable = false)
private OffsetDateTime rewardToDate;
#Column(name = "is_display_on", nullable = false)
private Boolean isDisplayOn;
#Column(name = "created_id", length = 50, nullable = false)
private String createdId;
#Column(name = "updated_id", length = 50)
private String updatedId;
#Column(name = "created_date", columnDefinition = "timestamptz", nullable = false)
private OffsetDateTime createdDate;
#Column(name = "last_modified_date", columnDefinition = "timestamptz")
private OffsetDateTime lastModifiedDate;
}
I have a PutMapping Spring boot API that gets below Json Input
{
"rewardId": 53,
"rewardTitle": "Reward is Allocated",
"rewardText": "Reward allocated for your recent purchase with our shop located at ABC-Mall",
"rewardType": "Informational",
"rewardFor": "Customer",
"rewardFromDate": "2019-04-12T00:00:00+05:30",
"rewardToDate": "2019-04-15T00:00:00+05:30",
"isDisplayOn": false
}
My Controller takes Principal object for both creation and updating the rewards table
#PutMapping
public ResponseEntity<RewardsResponse> updateRewards(Principal updatedPrincipal,
#RequestBody RewardUpdateRequest RewardUpdateRequest) {
But I won't send my createdId or updatedId from my Angular-UI.. So when i try to insert the updated-entity in to the table, using the below service-layer code
public RewardEntity updateReward(Principal principal, rewardEntity rewardEntity) {
String updatedId = null != principal ? principal.getName() : "defaultUpdatedId";
rewardEntity.setUpdatedCdsId(updatedId);
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
return rewardRepository.save(rewardEntity);
}
I get the below error
could not execute statement; SQL [n/a]; constraint [created_id]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
My assumption is that RewardEntity gets updated in the same row by mapping the ID that we pass and update only the fields that i set and do not touch rest of the fields ...
Should i first get my RewardEntity object from the DB based on the ID and then update on top of it ?? This makes the code connect DB twice for every update.
Request your inputs please
I would first get reference object using updatedId
RewardEntity rewardEntity = rewardRepository.getOne(updatedId )
update this object as per your requirement
rewardEntity.setLastModifiedDate(OffsetDateTime.now());
and finally use save to update this.
return rewardRepository.save(rewardEntity);
getOne() returns a reference to the entity and internally invokes EntityManager.getReference() method. It will always return a proxy without hitting the database (lazily fetched).

spring input validation with put request

good day everyone,
i have this spring rest api that i'm building, and currently having a problem with the put method on my of controllers.
i have a question entity that has a relation with a test entity:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column(name="question_id", nullable = false, updatable = false)
private Long id;
#Column(name="question_text", nullable = false)
#NotNull
private String question;
#Column(name="question_weight", nullable = false)
#Min(1)
private Integer weight = 1;
#Column(name="question_type", nullable = false)
private String type = "radio";
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "last_modified", nullable = false)
#LastModifiedDate
private Date lastModified;
#ManyToOne(fetch = FetchType.LAZY, optional = false, targetEntity = com.QCMGenerator.QCMGenerator.Model.Test.class)
#JoinColumn(name = "test_id", referencedColumnName = "test_id", nullable = false, updatable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private Test test;
i posted earlier asking about this problem and i've been told to use the DTOs, so i did and here is my question DTO:
private Long id;
private String question;
private String type;
private Integer weight;
private Date lastModified;
private TestDTO test;
and this the put method i have in my controller:
#PutMapping("/{questionID}")
public QuestionDTO updateQuestion(
#PathVariable(value = "testID") Long testID,
#PathVariable(value = "questionID") Long questionID,
#Valid #RequestBody QuestionDTO newQuestion
){
if(!testRepo.existsById(testID)){
throw new ResourceNotFoundException("No test with the ID '"+testID+"' was found...");
}
QuestionDTO savedDTO = null;
try {
Question questionEntity = questionRepo.findById(questionID).get();
QuestionDTO questionDTO = convertToDTO(questionEntity);
if (newQuestion.getTest() != null) {
questionDTO.setTest(newQuestion.getTest());
}
if (newQuestion.getQuestion() != null) {
questionDTO.setQuestion(newQuestion.getQuestion());
}
if (newQuestion.getType() != null) {
questionDTO.setType(newQuestion.getType());
}
if (newQuestion.getWeight() != null) {
questionDTO.setWeight(newQuestion.getWeight());
}
Question newQuestionEntity = convertToEntity(questionDTO);
Question saved = questionRepo.save(newQuestionEntity);
savedDTO = convertToDTO(saved);
}catch (Exception e){
System.out.println(e.getMessage());
}
return savedDTO;
}
and i keep getting this error on my IDE console:
2018-11-18 21:33:12.249 WARN 12876 --- [nio-8080-exec-2] o.h.a.i.UnresolvedEntityInsertActions : HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities.
Unsaved transient entity: ([com.QCMGenerator.QCMGenerator.Model.Test#])
Dependent entities: ([[com.QCMGenerator.QCMGenerator.Model.Question#10]])
Non-nullable association(s): ([com.QCMGenerator.QCMGenerator.Model.Question.test])
org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.QCMGenerator.QCMGenerator.Model.Question.test -> com.QCMGenerator.QCMGenerator.Model.Test; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.QCMGenerator.QCMGenerator.Model.Question.test -> com.QCMGenerator.QCMGenerator.Model.Test
i was hoping someone here would clarify this problem for me as i have been stuck all day long just on that single method or the other methods work fine, i have tried with and without a DTO and before adding it i was having a problem with the method accepting null values on certain fields.
i really appreciate any help given and thank you all for your help.
have a good day everyone. :D
This error occurs when you are trying to save an entity association with null id.
Means is this case convertToEntity method in
Question newQuestionEntity = convertToEntity(questionDTO);
returns questionEntity with new Test object.
you should check all relations inside an entity for being null when their id is null.
You need add ID in your entity in order to save an entity with that ID as reference.
Easy to solve it:
Question newQuestionEntity = convertToEntity(questionDTO);
newQuestionEntity.setId(testID);
A best solution would be:
questionDTO.setID(testID);
public Question convertToEntity(QuestionDTO qDto) {
Question question = new Question(qDto.getID()) ;
.......
return question;
}

null id generated for composite PK

I have the following tables and the following relationship table too: , which has a composite PK as follow:
UserRole.java
#RooJavaBean
#RooJpaEntity(identifierType = UserRolePK.class, versionField = "", table = "UserRole", schema = "dbo")
#RooDbManaged(automaticallyDelete = true)
#RooToString(excludeFields = { "idApplication", "idRole", "idUserName" })
public class UserRole {
}
UserRole_Roo_DbManaged.aj
#ManyToOne
#JoinColumn(name = "IdApplication", referencedColumnName = "IdApplication", nullable = false, insertable = false, updatable = false)
private Application UserRole.idApplication;
#ManyToOne
#JoinColumn(name = "IdRole", referencedColumnName = "IdRole", nullable = false, insertable = false, updatable = false)
private Role UserRole.idRole;
#ManyToOne
#JoinColumn(name = "IdUserName", referencedColumnName = "IdUserName", nullable = false, insertable = false, updatable = false)
private Users UserRole.idUserName;
But also exist a PK table:
#RooIdentifier(dbManaged = true)
public final class UserRolePK {}
And its identifier class (UserRolePK_Roo_Identifier.aj)
privileged aspect UserRolePK_Roo_Identifier {
declare #type: UserRolePK: #Embeddable;
#Column(name = "IdRole", nullable = false)
private Long UserRolePK.idRole;
#Column(name = "IdUserName", nullable = false, length = 16)
private String UserRolePK.idUserName;
#Column(name = "IdApplication", nullable = false)
private Long UserRolePK.idApplication;
The way how I'm setting the service objec to save is:
UserRole userRole= new UserRole();
userRole.setIdApplication(app);
userRole.setIdRole(invited);
userRole.setIdUserName(user);
appService.saveURole(userRole);
app has been set and saved before (same transaction), as well as invited and user objects.
Since user (from Users table with composite PK: IdUserName which is a String ), is defined as follow, otherwise doesnt work.
#RooJavaBean
#RooJpaEntity(versionField = "", table = "Users", schema = "dbo")
#RooDbManaged(automaticallyDelete = true)
#RooToString(excludeFields = { "quotations", "taxes", "userRoles", "idCompany", "idPreferredLanguage" })
public class Users {
#Id
//#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "IdUserName", length = 16, insertable = true, updatable = true)
private String idUserName;
}
So, the error that I'm getting is:
org.springframework.orm.jpa.JpaSystemException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.domain.UserRole; nested exception is javax.persistence.PersistenceException: org.hibernate.id.IdentifierGenerationException: null id generated for:class com.domain.UserRole
Try this:
public class UserRole {
#PrePersist
private void prePersiste() {
if (getId() == null) {
UserRolePK pk = new UserRolePK();
pk.setIdApplication(getIdApplication());
pk.setIdRole(getIdRole);
pk.setIdUserName(getIdUserName());
setId(pk);
}
}
}
Roo is generating the fields on UserRole entity and its id embedded class, but is not the same thing (UserRole.idRole is not the same than UserRole.id.idRole). In your example, you fill the UserRole fields, but not the id fields. This code makes it for you before entity is persisted.
Good luck!
In my case if the follow example tries to be persisted in DB, then similar Exception mentioned above is thrown:
EntityExample e = new EntityExample();
repositoryExample.save(e);
//throw ex
This is caused due to missing id field values which needs to be set something like that:
EntityExample e = new EntityExample();
e.setId(new EmbeddedIdExample(1, 2, 3));
repositoryExample.save(e);

Resources