spring input validation with put request - spring

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

Related

JPARepository wont update entity on save

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.

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).

Saving entity with composite key get ConversionNotSupportedException

I use spring boot 2 and some of my entities have composite key
When I try to save an entity, I get this error
Failed to convert request element:
org.springframework.beans.ConversionNotSupportedException: Failed to
convert property value of type 'java.lang.Integer' to required type
'com.lcm.model.SamplingsPK' for property 'sampling'; nested exception
is java.lang.IllegalStateException: Cannot convert value of type
'java.lang.Integer' to required type 'com.lcm.model.SamplingsPK' for
property 'sampling': no matching editors or conversion strategy found
I get my entity with that method
public Samples findById(Integer id, int year, String sampleLetter) {
Optional<Samples> optSamples = samplesRepository.findById(new SamplesPK(new SamplingsPK(year, id), sampleLetter));
if (optSamples.isPresent()) {
return optSamples.get();
}
return null;
}
Samples samples = samplesService.findById(idSeq, year, samplesLetter);
Compressions compressionTest = null;
if (samples.getTestSamples().getAbsorptionTest() != null) {
compressionTest = samples.getTestSamples().getCompressionTest();
} else {
compressionTest = new Compressions();
}
samplesService.save(samples);
My entity
#Entity
#IdClass(SamplesPK.class)
public class Samples extends BaseEntity{
#Id
private String sampleLetter;
#Embedded
private TestSamples testSamples;
#Id
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")})
private Samplings sampling;
}
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
}
public class SamplingsPK implements Serializable {
private int year;
private Integer id;
public SamplingsPK(int year, Integer id) {
this.id = id;
this.year = year;
}
}
public class SamplesPK implements Serializable {
private SamplingsPK sampling;
private String sampleLetter;
public SamplesPK(SamplingsPK sampling, String sampleLetter) {
this.sampling = sampling;
this.sampleLetter = sampleLetter;
}
}
edit
no problem to save sample, when I pass from sampling
The problem is that since the IDs are set manually and there's no #Version property on these entities then Spring Data has no good way of knowing if the entity is a brand new one or an existing one. In this case it decides it is an existing entity and attempts a merge instead of a persist. This is obviously a wrong conclusion.
You can read more about how Spring Data decides if an entity is new or not here.
The best solution I've found is to always let entity classes with manually set IDs implement Persistable interface. This solves the problem. I make this a rule for myself for any such case. Most of the time I do not have to implement Persistable because my entity either has an auto-generated key or my entity uses a "#Version" annotation. But this is special case.
So, as per the recommendation in the Spring official documentation, for example the Samplings class would become:
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings implements Persistable<SamplingsPK> {
#Transient
private boolean isNew = true;
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
#Override
public SamplingsPK getId() {
return new SamplingsPK(year, id);
}
}
This issue is tracked at https://jira.spring.io/browse/DATAJPA-1391 and has to do with the use of #Id #ManyToOne inside of Samples. As a workaround, you can try creating a constructor for Samplings that takes in its two primary keys, or maybe one that takes a java.lang.Integer? That's what worked for a single level of composite primary keys, but it might not work if you have multiple levels.
You also have year in SamplingsPK typed as an int rather than an Integer. This may cause problems with PK recognition, since special consideration is needed to handle autobox-able primitive classes and I doubt it was considered.
I noticed this too. It does not happen on my IDE on Windows but it happens on the Azure build server
I was on org.springframework.data:spring-data-jpa:jar:2.4.5:compile.
I upgraded the BOM to <spring-data-bom.version>2020.0.15</spring-data-bom.version> so I have org.springframework.data:spring-data-jpa:jar:2.4.15:compile
Once I did that it started working correctly.

Lazy attribute is null inside transaction after creation

I have a small example with some get/post mappings and JpaRepository calls in Spring Boot.
Firstly I have two entity Classes:
#Entity
#Table(name = "stock")
public class Stock extends BaseEntity
{
#Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
#Column(name = "stock_id")
public Long stockId;
#Column(name = "value")
public String value;
}
I have a many-to-one association from StockItem to Stock.
I insert a Stock and have a controller as below:
#Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
#RequestMapping("/")
#Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
#RequestMapping("/fromSave")
#Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
and getItemsById in the repository is defined as follows:
#Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(#Param("id") Long id);
From my understanding, when I call the post method:
it creates a new item
sets the id of the associated attribute
saves and ends the transaction
Heres where things get strange...
I call get after the post and make the above repository call, which has a join fetch and when I call stockitem.getStock().getValue() I get a null pointer when I expect a LazyInitializationException.
If I call the get() from the mapping, outside the class, it successfully loads the associated object.
I have even removed the #Transaction annotation from the get, as well as
the join-fetch from my query and again, if I call from outside of the class it works and from the post, it crashes with a NullPointerException.
I have put the get inside of a TransactionTemplate.execute() and I still get a NullPointerException when calling from inside the class.
So the main questions are:
Why am I getting a NullPointerException instead of LazyInitializationException?
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
The problem here is that you are misusing JPA. As you are seemingly aware judging from the comments on the other answer you have mapped the stock_id column twice. Once as a many-to-one relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
and once as a simple column
#Column(name = "stock_id")
public Long stockId;
When you set the simple column and flush the changes as in your post() method the following happens:
the value gets set in the simple column. The reference is still null.
the value gets stored in the database. The reference is still null.
The repository call will find the id of the StockItemin the Persistence Context and return that instance, i.e. the exact same used in the post method, with the reference still null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
No magic involved here. fetch specifications are only used for object traversal. JPQL queries don't honor these.
The unasked question remains: how to fix the situation?
The obvious fix is to lose the simple column and just use entity references as intended by JPA.
You don't want to do that in order to avoid DB access somewhere. But as long as you only access the id of the referenced Stock it shouldn't get initialized. So it seems that this should be possible with just Lazy Fetching.
Alternatively, I'd suggest removing the many-to-one relationship and creating a repository for Stock and manually loading it when required.
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
#Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
#Column(name = "value")
public String value;
}
with insertable = false and updatable = false it won't insert in your DB and neither it will allow updation, so you are getting NullPointerException. You should atleast allow insertion in order to run the query based on the foreign key stock_id
UPDATE
Change your Entity class with property-based access:
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
#Basic
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Ajax gets only the first object from a list got from the controller

I just started working with Java for web and I have this basic Quizz project where I integrated ajax.
In spring I have a controller which returns a list of answer objects based on a question id.
#RequestMapping(value = "/view-answers/{id}", method = RequestMethod.GET, produces = "application/json")
#SuppressWarnings("unchecked")
public #ResponseBody List<Answer> viewAnswers(#PathVariable int id, Model model){
// TODO: Get all quizzes
List<Answer> answers = answerService.findByQuestionId(id);
return answers;
}
Using this ajax fuction I retrieve the data from the controller with only one problem: It gets only the first object in full and the rest of the objects are just ID`s of the objects.
// DO GET
function ajaxGet(){
var questionID = $(".questionID").val();
$.ajax({
type : "GET",
url : "/view-answers/"+questionID,
dataType: 'json',
headers: {
Accept: 'application/json'
},
success: function(answers){
$('#answersList .answersUl').empty();
var ansList = "";
console.log(answers);
$.each(answers, function(i, answer){
var answer = i + "." + answer.answer + "<br />";
$('#answersList .answersUl ').append(answer);
});
console.log("Success: ", answers);
},
error : function(e) {
$("#getResultDiv").html("<strong>Error! Something went wrong.</strong>");
console.log("ERROR: ", e);
}
});
}
It can be a problem with my controller function? The findByQuestionId function is this:
#Override
public List<Answer> findByQuestionId(int question_id) {
Session session = sessionFactory.openSession();
session.beginTransaction();
Question question = session.find(Question.class, question_id);
List<Answer> answers = question.getAnswers();
session.getTransaction().commit();
//Close the session
session.close();
return answers;
}
This is what I'm getting with ajax right now:
The important part of my entities:
Quizz:
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "quizz_id")
public class Quizz {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int quizz_id;
private String title;
private String description;
private int nr_participanti;
private int timp_disp;
private int nr_intrebari;
#OneToMany(mappedBy = "quizz", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private List<Question> questions;
Question:
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Question {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String titlu;
#ManyToOne
private Quizz quizz;
#OneToMany(mappedBy = "question", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private List<Answer> answers;
Answer:
#Entity
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "answer_id")
public class Answer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int answer_id;
private String answer;
private boolean corect;
#ManyToOne
private Question question;
I assume that it's a problem of lazy loading the related Answer objects in your service bean. Since you seem to use Hibernate the list of answers returned by question.getAnswers() may not eagerly fetch all answers related to the question. Hibernate loads one-to-many relations lazily by default. You close the Hibernate session at the end of the service method findByQuestionId and therefore it is not possible to load the remaining answers as soon as the list is iterated on when generating the response of the controller.
You could try the following in your service:
List<Answer> answers = question.getAnswers();
List<Answer> loadedAnswers = answers.stream().collect(Collectors.toList());
This will iterate the list of answers returned for the question relation immediately and add all answers to another transient list. Then it is safe to close the Hibernate session afterwards.
For anyone that has the same problem or somehow finds this question. The problem was indeed in my Entities.
For each entity I had to add two annotations:
#JsonManagedReference is the forward part of reference – the one that gets serialized normally.
#JsonBackReference is the back part of reference – it will be omitted from serialization.
Therefore, I annotate ManyToOne objects with #JsonManagedReference and OneToMany lists objects with #JsonBackReference. This was the only part that was missing in order for this to work.

Resources