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

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.

Related

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

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

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

In Spring Boot, how do you set a (extended) property using extended setters and getters?

I am using Spring Boot, and have the following Entity definitions (abridged):
package com.vw.asa.entities;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.PreUpdate;
import javax.validation.constraints.NotNull;
public abstract class CmsModel extends Model {
#Basic(optional = false)
#NotNull
#Column(name = "is_active")
private short isActive;
public short getIsActive() {
return isActive;
}
public void setIsActive(short isActive) {
this.isActive = isActive;
}
public void setIsActive(String isActive) {
if (isActive.equals("true")) {
this.isActive = IS_TRUE;
} else if (isActive.equals("1")) {
this.isActive = IS_TRUE;
} else {
this.isActive = IS_FALSE;
}
}
}
Then I have several models which extend this 'base' model, following this flavor:
package com.vw.asa.entities.cms;
import com.vw.asa.entities.CmsModel;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.Date;
import java.util.List;
/**
* #author Barry Chapman
*/
#Entity
#Table(name = "cms_extra_questions", schema = "asa")
public class CmsExtraQuestions extends CmsModel {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Integer id;
...
}
When I initialize an instance of CmsExtraQuestions as a result of a hibernate query, if I call setActive(true) on the object, it has no effect on the members of that object. When I copy the setters and getters from the CmsModel base class into the CmsExtraQuestions class, it works fine.
$entity = new CmsExtraQuestions();
$entity->setActive(true);
Why does this not set the member properties of the instantiated object when calling the extended setter? If this is normal - is there a way to add these properties and member functions to the base model so that they can be inherited also?
Rookie mistake, I forgot to add #MappedSuperClass to the inherited model, CmsModel. That allows JPA to map the properties from that class as though they were defined in the model that was inheriting that base class.
#MappedSuperClass
public abstract class CmsModel extends Model {
#Basic(optional = false)
#NotNull
#Column(name = "is_active")

JpaRepository with complex key class is failing validation with error "Validation failed for query for method" when using JPA syntax

I am trying to use the "out of the box" goodies of JPA and get data from DB to an object. It is working perfect when the JpaRepository is using a simple class as key, but for test cases when the key class is a bit complex, the jpa validation fails even for the simplest queries.
using a native_query=true will solve the issue, however I am not sure what I am doing wrong and what am I missing.
MyTestPK.java:
package com.mytest.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
import java.io.Serializable;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
public class MyTestPK implements Serializable {
private String type;
private String name;
}
MyTest.java:
package com.mytest.model;
import lombok.Data;
import javax.persistence.*;
#Entity
#Table(name = "my_test")
#Data
#IdClass(MyTestPK.class)
public class MyTest {
#Id
#Column(name = "my_test_type", nullable = false)
private String type;
#Id
#Column(name = "my_test_name", nullable = false)
private String name;
#Column(name = "my_test_misc")
private String misc;
}
MyTestRepository.java:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
#Repository
public interface MyTestRepository extends JpaRepository<MyTest, MyTestPK> {
#Query(value="SELECT m FROM MyTest m")
List<MyTest> getAllObjs();
}
Getting the following error:
....
Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List
com.mytest.dao.MyTestRepository.getAllObjs()!
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:586) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:90) ~[spring-beans-5.0.9.RELEASE.jar:5.0.9.RELEASE]
You can use #EmbeddedId and no need to define columns in the entity classes again.
MyTestPK.java:
package com.mytest.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Embeddable;
import java.io.Serializable;
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
public class MyTestPK implements Serializable {
#Column(name = "my_test_type", nullable = false)
private String type;
#Column(name = "my_test_name", nullable = false)
private String name;
}
MyTest.java:
package com.mytest.model;
import lombok.Data;
import javax.persistence.*;
#Entity
#Table(name = "my_test")
#Data
public class MyTest {
#EmbeddedId
private MyTestPK pk;
#Column(name = "my_test_misc")
private String misc;
}

Spring JPA + CRUD - custom query doesn't allow _ characters?

I'm having troubles in creating a custom query within spring, because my Entity contains an "_" character in it's parameter's name: "game_date".
My table has a column named "game_date" as well.
I have created following method:
List<Games> findByGame_dateAndOpponent(#Param("game_date") Date game_date, #Param("opponent") String opponent);
but when I start my app, it's crashing with exception of kind: "org.springframework.data.mapping.PropertyReferenceException: No property gamedate found for type Games!". After changing a parameter name to the "gameDate" both in Entity and Query method, it stopped complaining, and is actually returning expected entries. But at the same time, it doesn't return values from the column "game_date", in the search queries, which is a simple regular column of a Date type. I have no idea what's going on with all this thing.
DB I'm using is MySql.
Here comes the code itself:
Entity:
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
#Entity
#Table(name = "games")
public class Games {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_game")
private int id;
#Column(name = "game_date", columnDefinition = "DATE")
#Temporal(TemporalType.DATE)
private Date gameDate;
public Date getGame_date() {
return gameDate;
}
public void setGame_date(Date _game_date) {
this.gameDate = _game_date;
}
}
And a repository:
import java.sql.Date;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource
public interface GamesRepository extends CrudRepository< Games , Integer > {
List< Games > findById( #Param( "id" ) int id );
List< Games > findAll( );
List<Games> findByGameDateAndOpponent(#Param("game_date") Date game_date, #Param("opponent") String opponent);
}
The underscore is a reserved keyword in Spring Data JPA. It should be enough to remove it from your property and from its getters and setters and Hibernate will do the rest:
#Entity
#Table(name = "games")
public class Games {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id_game")
private int id;
//Getter and setters for id
#Column(name = "game_date")
private Date gameDate;
public Date getGameDate() {
return gameDate;
}
public void setGameDate(Date gameDate) {
this.gameDate = gameDate;
}
}
Also, in general, try to use java naming convention for variable and field names, which is mixed case with lowercase first.
See also:
Spring Data JPA repository methods don't recognize property names with underscores

Categories

Resources