How can I fix Spring boot POST API's 500 error? - java

I created POST API in Spring Boot, but 500 error occurs.
"timestamp": "2023-01-27T16:27:32.609+00:00",
"status": 500,
"error": "Internal Server Error",
"trace": "org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.CATEGORY(CATEGORY_ID) ( /* key:1 */ 1, U&'\\c1fc\\d551\\bab0', 1)"; SQL statement:\ninsert into category (category_id, category_name, site_user_id) values (default, ?, ?)
I want to put data in the 'category' table with 'categoryId', 'category_name', and 'site_user_id' as columns through POST API. It seems to be caused by putting 'siteUser' entity instead of 'site_user_id', but I don't know how to modify the code.
Below is the code I wrote.
Category.java
package com.kakaotrack.choco.linkupapi.category;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.kakaotrack.choco.linkupapi.linkcollection.LinkCollection;
import com.kakaotrack.choco.linkupapi.user.SiteUser;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
#Entity
#Data
#Table(name = "category")
#NoArgsConstructor
public class Category {
public Category(String category_name, SiteUser siteUser){
this.category_name = category_name;
this.siteUser = siteUser;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int categoryId;
private String category_name;
#OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE)
#JsonIgnoreProperties({"category"})
private List<LinkCollection> link_collection_list;
#ManyToOne
private SiteUser siteUser;
}
SiteUser.java
package com.kakaotrack.choco.linkupapi.user;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter
#Setter
#Entity
#Table(name = "users")
#NoArgsConstructor
public class SiteUser {
public SiteUser(String username, String email){
this.username=username;
this.email=email;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(unique = true)
private String username;
private String password;
#Column(unique = true)
private String email;
}
CategoryService.java
package com.kakaotrack.choco.linkupapi.category;
import com.kakaotrack.choco.linkupapi.linkcollection.LinkCollection;
import com.kakaotrack.choco.linkupapi.user.SiteUser;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
#RequiredArgsConstructor
#Service
public class CategoryService {
private final CategoryRepository categoryRepository;
public List<Category> getAll() {return categoryRepository.findAll();}
public List<Category> getBySiteUser(int id){
return categoryRepository.findBySiteUserId(id);
}
public Category createCategory(String categoryName, SiteUser siteUser){
Category category = new Category(categoryName, siteUser);
return categoryRepository.save(category);
}
public void deleteByCategoryId(int category_id){categoryRepository.deleteById(category_id);}
}
CategoryController.java
package com.kakaotrack.choco.linkupapi.category;
import com.kakaotrack.choco.linkupapi.linkcollection.LinkCollection;
import com.kakaotrack.choco.linkupapi.linkcollection.LinkCollectionRepository;
import com.kakaotrack.choco.linkupapi.user.SiteUser;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
#RequiredArgsConstructor
public class CategoryController {
private final CategoryService categoryService;
#GetMapping(value = "/categories")
public List<Category> getAll() {return categoryService.getAll();}
#GetMapping(value = "/categories/{id}")
public List<Category> getBySiteUser(#PathVariable int id) {return categoryService.getBySiteUser(id);}
#PostMapping(value = "/categories")
public Category createCategory(String categoryName, SiteUser siteUser){
Category category = categoryService.createCategory(categoryName, siteUser);
return category;
}
#DeleteMapping(value = "/categories/{category_id}")
public void deleteCategory(#PathVariable int category_id){ categoryService.deleteByCategoryId(category_id);}
}
DELETE and GET APIs work well.

Try to update SiteUser fields as shown below:
#ManyToOne(optional = true, fetch = FetchType.LAZY)
#JoinColumn(name = "site_user_id", referencedColumnName = "id")
private SiteUser siteUser;

I think the issue is with the category_name. It is not following the standard naming convention. Underscore is used to separate property names in JPA custom methods.
#Column(name = "category_name")
private String categoryName;
NB: Also you have to implement the changes mentioned by Murat. Use optional = false if it is Not Null in DB

Related

Jpa repository saveAndFlush returning timestamps as null

I have a entity
#AllArgsConstructor
#NoArgsConstructor
#Setter
#Getter
#Entity
#Table(name = "t_org")
public class Organization {
/**
* id of the organization.
*/
#Id
#GeneratedValue(generator = "UUID")
#Column(name = "c_id", columnDefinition = "BINARY(16) DEFAULT (UUID_TO_BIN(UUID(), TRUE))")
private UUID id;
/**
* legal name of the organization.
*/
#Column(name = "c_legal_name", nullable = false, unique = true)
private String legalName;
/**
* alias name of the organization.
*/
#Column(name = "c_alias", nullable = false, unique = true)
private String alias;
/**
* timestamp when the organization was created.
*/
#Column(name = "c_date", insertable = false, updatable = false,
columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP",
nullable = false)
private Instant ts;
}
now when I am saving this entity from service and when I want to access timestamp by calling saveAndFlush(orgObject).getTs();
Timestamp is always null but I can see it is getting saved in database but jpa not returning it.
I tried annotating it with #CreationTimestamp but that will set jvm's timestamp and I want db's timestamp.
I tried like this and this is fetching the timestamp. I created an entity class called post. and made API controller and service class for that. Here is the whole code.
Post entity class
package com.project.demo.entity;
import java.time.Instant;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "post")
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String thePost;
private Instant creationTime;
}
service class
package com.project.demo.service;
import java.time.Instant;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.project.demo.entity.Post;
import com.project.demo.repository.PostRepository;
import jakarta.transaction.Transactional;
#Service
public class PostService {
#Autowired
private PostRepository postRepository;
#Transactional
public List<Post> getPosts(){
return postRepository.findAll();
}
#Transactional
public void savePost(Post post) {
post.setCreationTime(Instant.now());
postRepository.save(post);
}
}
controller class for rest API
package com.project.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.project.demo.entity.Post;
import com.project.demo.service.PostService;
#RestController
#RequestMapping
public class Controller {
#Autowired
private PostService postService;
#GetMapping("/posts")
public List<Post> getPost() {
return postService.getPosts();
}
#PostMapping("/posts")
public String savePost(#RequestBody Post post) {
post.setId(0);
postService.savePost(post);
return "Post saved";
}
}
application.property
spring.datasource.name=test
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:postgresql://localhost:5432/stackoverflow
spring.datasource.username=postgres
spring.datasource.password=123
spring.jpa.show-sql=true
The screenshots of fetched data in postman
The screenshot
Hope this helps.

Spring Ignore field just in getAll method

I have a list of persons in DB everyone having a CV field which is a MultiPart File in Spring. I'm trying to get all persons from db, but to ignore the CV field because I don't want to be downloaded when I use getAllPersons function. I saw a possibility with JsonIgnore but I want to ignore that field just in getAllPersons, not in other functions like getPersonById etc. How can I do it?
For that purpose you can use HQL.
i.e
interface Repo extends C... {
#Query(select h.name, h.phone, ... from Person h)
List<Person> getAllPerson();
}
Suppose that you use Sping and Spring-data you can use a projection in order to avoid maintaining custom queries. Consider the following example:
Entity class Book
#Data
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(generator = "book_sequence", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "book_sequence", sequenceName = "book_sequence", allocationSize = 1)
private Long id;
#NaturalId
private String name;
private String author;
private String publisher;
private String plot;
#ManyToMany(mappedBy = "books")
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<BookFilter> filters = new HashSet<>();
}
Projection interface:
public interface BookNameAuthorOnly {
String getName();
String getAuthor();
}
Repository method:
#Repository
public interface BookRepository extends JpaRepository<Book, Long> {
List<BookNameAuthorOnly> findBy();
}
When the latter is invoked, the dynamic query generated by Spring, will select only the fields that you have specified in the related interface object. For more on this you can check the following documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Don't know if this fits your use case scenario but this is also another way of achieving what you need to achieve.
You can also use DTO as shown in below example:
Person entity:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter
#Setter
#NoArgsConstructor
#Entity
public class Person {
#Id
private long id;
private String name;
private String address;
#Lob
private Object cvFields;
}
PersonDTO:
package com.example.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter
#Setter
#NoArgsConstructor
public class PersonDTO {
private long id;
private String name;
private String address;
public PersonDTO(Person person) {
this.id = person.getId();
this.name = person.getName();
this.address = person.getAddress();
}
}
PersonRepository:
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query("SELECT new com.example.dto.PersonDTO(p) FROM Person p")
List<PersonDTO> getAll();
}

Is there something wrong with my method-naming in JPA?

I have a simple question about JpaRepository.
First, this is my Entity class.
package com.surveypedia.domain.pointhistory;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
#NoArgsConstructor
#Getter
#Entity
#Table(name = "pointhistory")
public class PointHistory {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer ph_code;
#Column(nullable = false)
private String email;
#Column(nullable = false, name = "s_code")
private Integer s_code;
#Column(nullable = false)
private Integer pointchange;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private PointHistoryType ph_type;
public PointHistory(String email, Integer s_code, Integer pointchange, PointHistoryType ph_type) {
this.email = email;
this.s_code = s_code;
this.pointchange = pointchange;
this.ph_type = ph_type;
}
}
And below is my repository interface to do CRUD operations.
package com.surveypedia.domain.pointhistory;
import com.surveypedia.tools.SQL;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface PointHistoryRepository extends JpaRepository<PointHistory, Integer> {
List<PointHistory> findByEmail(String email);
PointHistory findByS_codeAndEmailAndPh_type(Integer s_code, String email, PointHistoryType ph_type);
}
After starting my spring-boot project, I get this error :
java.lang.IllegalArgumentException: Failed to create query for method public abstract com.surveypedia.domain.pointhistory.PointHistory com.surveypedia.domain.pointhistory.PointHistoryRepository.findByS_codeAndEmailAndPh_type(java.lang.Integer,java.lang.String,com.surveypedia.domain.pointhistory.PointHistoryType)! No property s found for type PointHistory!
I tried findByEmailAndS_codeAndPh_type with proper arguments, but I got the same error log. What's the problem with my method there?
The problem is that underscore (_) is restricted to class hierarchies in spring-data-jpa mathod names. It's based on the simple convention of using camelCase in Java, which you're breaking.
Rename the field ph_code to phCode and s_code to sCode both in the entity and in the method name.

Java spring boot lazy fetch and json ignore not working

I have a bord class and a bordrow class, meaning a bord has multiple bordrows in a one to many relation. Whenever I do a GET request on /bords I just want every bord model without the datetimes and without the rows, this is why I added lazy fetch and #JsonIgnore. However all attributes are sent on the request. What did I do wrong?
Bord.java:
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
#Table(name = "bords")
#Entity
public class Bord
{
#Id
#GeneratedValue
private int id;
#NotNull
private String name;
private String icon;
private String background;
#DateTimeFormat
#JsonIgnore
private Date created_at;
#DateTimeFormat
#JsonIgnore
private Date updated_at;
#DateTimeFormat
#JsonIgnore
private Date deleted_at;
#OneToMany(mappedBy = "bord", fetch = FetchType.LAZY)
private List<BordRow> bordRows;
public Bord() {
bordRows = new ArrayList<>();
}
}
BordRow.java:
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
#Entity
#Table(name = "bord_rows")
public class BordRow
{
#Id
#GeneratedValue
private int id;
#NotNull
private String title;
#ManyToOne
#JoinColumn(name="bord_id", nullable=false)
#JsonIgnore
private Bord bord;
}
BordController.java:
import com.jordibenck.scrumbords.scrumbords.entity.Bord;
import com.jordibenck.scrumbords.scrumbords.entity.User;
import com.jordibenck.scrumbords.scrumbords.repository.BordRepository;
import com.jordibenck.scrumbords.scrumbords.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(path = "/bords")
public class BordController
{
#Autowired
private BordRepository repository;
#GetMapping
public Iterable<Bord> findAll() {
return repository.findAll();
}
}
You should use the annotations in your getters. and not your private fields
Hope it helps
You should delete #DateTimeFormat.
#DateTimeFormat has conflict with #JsonIgnore.

ModelMapper returns NULL when trying to map Entity to DTO object

Here is the class of the object I am trying to map:
package com.agent.module.entities;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;
#Entity
#Getter #Setter #NoArgsConstructor
#Accessors
public class Accommodation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String location;
#ManyToOne(optional=false, fetch=FetchType.EAGER)
private AccommodationType type;
private String description;
private String name;
#OneToMany(orphanRemoval=true, fetch=FetchType.EAGER)
#Cascade(CascadeType.ALL)
private Set<Document> images;
private Integer capacity;
#ManyToMany(fetch=FetchType.EAGER)
private Set<AdditionalService> additionalServices;
#OneToMany(orphanRemoval=true, fetch=FetchType.EAGER)
#Cascade(CascadeType.ALL)
private Set<PricePlan> pricePlan;
#ManyToOne(optional=false, fetch=FetchType.LAZY)
private Agent agent;
#OneToMany(orphanRemoval=true, mappedBy="accommodation", fetch=FetchType.EAGER)
#Cascade(CascadeType.ALL)
private Set<Restriction> restrictions;
#ManyToOne(fetch=FetchType.EAGER)
private Category category;
#Override
public String toString() {
return "Name: "+name+"\n"+"Agent PIB: "+agent.toString()+"\n";
}
}
And here is my DTO object:
package com.agent.module.dto;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter #Setter #NoArgsConstructor
#XmlRootElement
public class AccommodationView {
private Long id;
private String location;
private String typeName;
private String description;
private String name;
private List<String> imagesPath;
private Integer capacity;
private List<String> additionalServicesName;
private List<PricePlanView> pricePlan;
private String agentUsername;
private List<RestrictionView> restrictions;
private String categoryName;
#Override
public String toString() {
return "ID: "+id+"\n"+"Type: "+typeName+"\n"+"Description: "+description+"\n"+"Category: "+categoryName+"\n"+"Name: "+name+"\n";
}
}
When I open my Postman and try to get all the Accommodation objects from MySQL database, I actually want to get DTO objects, and in order to do that I am using ModelMapper. But for some reason every time I try to map Accommodation to AccommodationView, I get Null in return. Here is the class where I am trying to perform the mapping:
#RestController
#RequestMapping(value = "/accommodation")
public class AccommodationController {
#Autowired
AccommodationRepo accommodationRepo;
#Autowired
ModelMapper mapper;
#RequestMapping(value="/all",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
ResponseEntity<List<AccommodationView>> getAll(){
List<Accommodation> accommodations = accommodationRepo.findAll();
List<AccommodationView> accommodationViewList= new ArrayList<AccommodationView>();
for(Accommodation accommodation : accommodations) {
System.out.println(accommodation);
System.out.println(convertToDto(accommodation));
accommodationViewList.add(convertToDto(accommodation));
}
return new ResponseEntity<List<AccommodationView>>(accommodationViewList, HttpStatus.OK);
}
private AccommodationView convertToDto(Accommodation accommodation) {
return mapper.map(accommodation, AccommodationView.class);
}
private Accommodation convertToEntity(AccommodationView accommodationView) {
return mapper.map(accommodationView, Accommodation.class);
}
}
Here is the output I get when I hit the method:
Name: Test
Agent PIB: 2308995710368
ID: null
Type: null
Description: null
Category: null
Name: null
First part of the output is from Accommodation object, and second part of the output is from AccommodationView object. If anyone has any idea whats going on I would really appreciate the help.
you have to generate public setters functions for the target class, in your case (Accommodation Entity). elsewise the Modelmapper cannot access the private fields of your class to set their values.

Categories

Resources