I use Micronaut Data with JPA and have two entities. The first one is Recipe:
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#ManyToOne
private Category category;
#OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
private Set<Step> steps;
// + other fields, getters and setters
}
The second one is ParseError which refers to Recipe:
#Entity
#Table(name = "parse_error")
public class ParseError implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY)
private Recipe recipe;
#Id
#Enumerated(EnumType.ORDINAL)
#Column(name = "problem_area")
private ProblemArea problemArea;
private String message;
// + other fields, getters and setters
}
Now I would like to provide DTO in API with ParseError properties but not with whole Recipe entity because it contains ManyToOne and OneToMany relations which are not needed in this case. So I created projection DTO for that:
#Introspected
public class ParseErrorDto {
private Integer recipeId;
private String recipeName;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
And added listAll() method into ParseErrorRepository:
#Repository
public interface ParseErrorRepository extends CrudRepository<ParseError, Integer> {
List<ParseErrorDto> listAll();
}
But it seems that Micronaut Data is not able to project properties from nested entities or I missed something in the DTO or the repository method:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property recipeId is not
present in entity: ParseError
I also tried to create RecipeDto:
#Introspected
public class RecipeDto {
private Integer id;
private String name;
// + getters and setters
}
And updated ParseErrorDto accordingly:
#Introspected
public class ParseErrorDto {
private RecipeDto recipe;
private ParseError.ProblemArea problemArea;
private String message;
// + getters and setters
}
Again no success:
ParseErrorRepository.java:22: error: Unable to implement Repository
method: ParseErrorRepository.listAll(). Property [recipe] of type
[RecipeDto] is not compatible with equivalent property declared in
entity: ParseError
Is Micronaut Data able to handle this use case by DTO projection? If not then is there another way how can I solve it in Micronaut Data?
Now (in latest version 1.0.0.M1) it is not possible. So I created feature request issue for that: https://github.com/micronaut-projects/micronaut-data/issues/184
Current workaround is to map entity bean into DTO bean in Java stream or reactive stream for example and do the properties mapping manually or by Mapstruct.
Update: Here is an answer to question from comments with an example how to do the workaround using Mapstruct:
Add Mapstruct dependency into build.gradle:
implementation "org.mapstruct:mapstruct:$mapstructVersion"
annotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:$mapstructVersion"
Define mapper:
import org.mapstruct.Mapper;
#Mapper(
componentModel = "jsr330"
)
public interface ParseErrorMapper {
ParseErrorDto entityToDto(#NotNull ParseError parseError);
EntityReference recipeToDto(#NotNull Recipe recipe);
}
And here is a usage of that mapper in the controller:
#Controller("/parse-error")
public class ParseErrorController {
private final ParseErrorRepository repository;
private final ParseErrorMapper mapper;
public ParseErrorController(ParseErrorRepository repository, ParseErrorMapper mapper) {
this.repository = repository;
this.mapper = mapper;
}
#Get("all")
#Transactional
public Page<ParseErrorDto> getAll(final Pageable pageable) {
return repository.findAll(pageable).map(mapper::entityToDto);
}
}
Related
I've need to prepare a query in ServiceImpl ,as based on some logic the query can be differ by no. of columns. so I decided to prepare a custom JPA Repository but getting some error.
Before this to fulfill my requirement I tried this approach , please check .But I think JPA doesn't allow like this. So I tried Custom JPA Repository and getting error.
**
Entity class
#SuppressWarnings("serial")
#Entity
#Getter
#Setter
#Table(name = "REASON_CODE_REPORT",schema="automation")
#IdClass(ErrorCodeReportKeys.class)
public class ErrorCodeReportEntity
{
#Id
private String smsc;
#Id
private String userid;
#Id
private String smsc_userid;
#Id
private String operator;
#Id
private String circle;
#Id
private Date log_date;
#Id
private Integer log_hour;
#Id
private Integer log_min;
#Id
private Integer vf_reason_code;
#Id
private Integer smsc_reason_code;
private Integer count;
private Timestamp create_date;
}
**
ServiceImpl
#Override
public List<Object[]> errorCodeDefaultSummary(ErrorCodeReportDTO errorCodeReportDTO) {
String finalQuery="select smsc,userid from ErrorCodeReportEntity where log_date='2021-05-27'";
List<Object[]> result = errorCodeRepo.presentDaySummarySmscWise(finalQuery);
return result;
}
Custom JPA Interface
public interface ErrorCodeCustom {
List<Object[]> presentDaySummarySmscWise(String query);
}
Implementation of ErrorCodeCustomImpl
public class ErrorCodeCustomImpl implements ErrorCodeCustom{
#Autowired
private EntityManager entityManager;
#SuppressWarnings("unchecked")
#Override
public List<Object[]> presentDaySummarySmscWise(String query) {
final String finalQuery=query.toString();
List<Object[]> result= entityManager.createQuery(finalQuery).getResultList();
return result;
}
}
The Final Jpa Repository that implements our CustomRepository
#Repository
public interface ErrorCodeRepository extends JpaRepository<ErrorCodeReportEntity, ErrorCodeReportKeys>,ErrorCodeCustom
{
}
I don't know why i'm getting following errors
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List com.valuefirst.repository.ErrorCodeCustom.presentDaySummarySmscWise(java.lang.String)! No property presentDaySummarySmscWise found for type ErrorCodeReportEntity!
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property presentDaySummarySmscWise found for type ErrorCodeReportEntity!
Spring was not able to find your implementation ErrorCodeCustomImpl. You should annotate it #Component.
so the exception is there because ErrorCodeRepository extends ErrorCodeCustom and the query generated by your method name is trying to use a property named presentDaySummarySmscWise. Once your ErrorCodeCustomImpl will be injected by Spring the error will be gone.
i use querydsl, hibernate
i want select data by Dto in Dto list but not working
here is my code
#Data
#Entity
public class Team {
#Id
#GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
}
#Entity
#Setter
public class Member {
#Id
#GeneratedValue
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "team_id")
private Team team;
}
#Setter
public class TeamDto {
private Long id;
private String name;
private List<MemberDto> members = new ArrayList<>();
}
#Setter
public class MemberDto {
private Long id;
private String name;
}
test
#BeforeEach
void setup() {
queryFactory = new JPAQueryFactory(em);
Team team = new Team();
team.setName("teamA");
em.persist(team);
Member member = new Member("memberA");
member.setTeam(team);
em.persist(member);
Member member2 = new Member("memberB");
member2.setTeam(team);
em.persist(member2);
em.flush();
em.clear();
}
#Test
void t1() {
TeamDto teamDto = queryFactory
.select(Projections.fields(
TeamDto.class,
team.id,
team.name,
Projections.fields(
MemberDto.class,
member.id,
member.name
).as("members")
))
.from(team)
.fetchOne();
System.out.println("teamDto = " + teamDto);
}
error log is = java.lang.IllegalArgumentException: com.blog.querydsltest.domain.dto.MemberDto is not compatible with java.util.List
what is problem?? is impossible bring data by List dto??
i try to change Projections.fields to bean, construct, ... but not working
how can i do ?
Multi level aggregations are currently not supported by QueryDSL. There are also no concrete plans to support it as of now.
For a DTO solution that can fetch associations with it, I recommend you to have a look at Blaze-Persistence Entity Views. With Entity Views the code for your DTO would look something like the following:
#EntityView(Team.class)
public interface TeamDto {
#IdMapping public Long getId();
#Mapping("name") public String getName();
#Mapping("members") public List<MemberDTO> getMembers();
}
If members is not an association on your TeamEntity, you can map it through a #MappingCorrelated binding.
Disclaimer: I am a contributor for Hibernate, QueryDSL and Blaze-Persistence.
I have two entities (Project, OtherData) with one abstract entity. I'm using MySQL and Quarkus framework.
Problem: When I try to save Project entity field project_id remains null.
Table schemas:
On next picture there is shown, fk constraint in "project_other_data" table:
Abstract Entity:
#MappedSuperclass
public class AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
// getters and setters
}
Project Entity
#Entity
#Table(name = "projects")
public class Project extends AbstractEntity {
#NotNull
#Column(name = "name")
private String name;
#NotNull
#Column(name = "surname")
private String surname;
#Column(name = "date_create")
#JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateCreate;
#Column(name = "date_update")
#JsonbDateFormat(value = "yyyy-MM-dd")
private LocalDate dateUpdate;
#OneToOne(mappedBy = "project", cascade = CascadeType.ALL)
private OtherData otherData;
// getters and setters
}
OtherData Entity
#Entity
#Table(name = "project_other_data")
public class OtherData extends AbstractEntity {
#OneToOne
#JoinColumn(name = "project_id")
private Project project;
#Column(name = "days_in_year")
private Integer daysInYear;
#Column(name = "holidays_in_year")
private Integer holidaysInYear;
#Column(name = "weeks_in_year")
private Integer weeksInYear;
#Column(name = "free_saturdays")
private Integer freeSaturdays;
#Column(name = "downtime_coefficient")
private BigDecimal downtimeCoefficient;
#Column(name = "changes")
private Integer changes;
// getters and setters
}
Saving entities with code:
#Path("projects")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public class ProjectRest {
#Inject
ProjectService projectService;
#POST
public Response saveProject(Project project) {
return Response.ok(projectService.saveProject(project)).build();
}
}
#RequestScoped
#Transactional
public class ProjectService {
#Inject
EntityManager entityManager;
public Project saveProject(Project project) {
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
}
I was able to reproduce the problem by POSTing a new Project with an embedded OtherData. The body I used for the POST:
{
"name": "John",
"surname": "Doe",
"otherData": {}
}
Point is: the database entity is also used as DTO. Thus, the field project in otherData for the request body is set to null (since no Project is passed along this would be a recursive infinite definition).
During processing the entity from the rest controller to the service to the repository, the project of otherData is never set. A quick fix is to modify ProjectService::saveProject as follows:
public Project saveProject(Project project) {
project.getOtherData().setProject(project); // This line was added
if (project.getId() == null) {
entityManager.persist(project);
} else {
entityManager.merge(project);
}
return project;
}
This will fix the database issue (the project_id will be set), but leads to the next issue. The response body cannot be serialized due to an
org.jboss.resteasy.spi.UnhandledException: javax.ws.rs.ProcessingException: RESTEASY008205: JSON Binding serialization error javax.json.bind.JsonbException: Unable to serialize property 'otherData' from com.nikitap.org_prod.entities.Project
...
Caused by: javax.json.bind.JsonbException: Recursive reference has been found in class class com.nikitap.org_prod.entities.Project.
The object structure is cyclic (project references otherData, which return references project, ...) and Jackson is unable to resolve this cycle.
To fix this issue, I would suggest to separate DTOs and database entity and explicitly map between them. In essence:
Structure the Dto-object to represent the JSON-Request and -Response you expect to receive, in a non-cyclic order
Transfer JSON-related annotations from the database entity classes to the DTO classes
In the service- or repository-layer (your choice), map the DTO to the database entites, setting all fields (including the references from project to otherData and vice-versa)
In the same layer, map database-entites back to non-cyclic DTOs
Return the DTOs from the REST endpoint
Currently I'm using Spring Data JPA 2.2.1 with a Spring Boot Web 2.2.1 for a REST API service.
A getter call to /categories returns the following JSON, which in fact is the desired result:
[
{
"category1": "A",
"category2": "B",
"subcategories": []
},
{
"category1": "A",
"category2": "B",
"subcategories": [{
"field1": "A",
"field2": "B",
"field3": "C",
"field4": "D"
}]
},
.........
.........
]
Entities:
#Entity
#Table(name = "category")
#Getter #Setter
public class Category {
#JsonIgnore
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String category1;
private String category2;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Subcategory> subcategories;
}
#Entity
#Table(name = "subcategory")
#Getter #Setter
public class Subcategory {
#JsonIgnore
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#JsonIgnore
private int parent;
private String field1;
private String field2;
private String field3;
private String field4;
}
Controller:
#RestController
public class MyController {
private final DataService dataService;
#Autowired
public MyController(DataService dataService) {
this.dataService = dataService;
}
#GetMapping("/categories")
public List<Category> getAllCategories() {
return dataService.getAllCategories();
}
Repository
public interface MyRepository extends JpaRepository<Category, Long> {
List<MyRepository> findAll();
}
DataService
#Component
public class DataService {
private MyRepository myRepository;
#Autowired
public DataService(MyRepository myRepository) {
this.myRepository = myRepository;
}
public List<Category> getAllCategories() { return myRepository.findAll(); }
I now want to add a new call to my API, /limitedCategories, which does not return all of the fields. For example, "category2" of the parent entity, and "field4" of the nested entity shall be excluded.
Problem:
I don't manage to manually select the desired fields within my JPA Repository, and also wouldn't know how to deal with the nested object.
The simple idea to just use findAll and exclude those fields using #JsonIgnore is not possible, as my first request still needs those fields.
When using #Query annotation within my repository to fetch only the desired fields, I cannot find a way to fetch the nested fields for my JSON.
Thanks in advance.
I'm using Spring Data JPA 2.2.1 with a Spring Boot Web 2.2.1.
If you are using Jackson API, I recommend you to use #JsonView feature.
For example
class Person {
// it will be add always
Integer id;
// it will be add only for PutFirstName case
#JsonView(Views.PutFirstName.class)
String firstName;
// it will be add only for PutLastName case
#JsonView(Views.PutLastName.class)
String lastName;
}
class Views {
static class PutFirstName{}
static class PutLastNastName{}
}
// Controller
#JsonView(Views.PutFirstName.class)
#GetMapping
Person getFirstName(){
// result : {id,firstName}
}
#JsonView(Views.PutLastName.class)
#GetMapping
Person getLastName(){
// result : {id, lastName}
}
I am using spring framework and hibernate as an ORM tool.
My parent class is like:
#Entity
public class Category {
#Id
#GeneratedValue
#NotNull
private int cid;
private String cname;
#OneToMany(cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
#JoinColumn(name = "cid")
List<Ad> ads;
//getter and setter, constructor
}
And my child class is like:
#Entity
public class Ad {
private int adid;
private String adName;
//getter and setter, constructor
}
My category controller is :
#Controller
public class CategoryController {
#Autowired
SessionFactory sessionFactory;
Session session;
#Transactional
#RequestMapping(value = "categories",method = RequestMethod.GET)
#ResponseBody
public List<Category> getAllCategory()throws SQLException{
session=sessionFactory.getCurrentSession();
return session.createCriteria(Category.class).list();
}
}
When i hit url localhost:8080/categories .I get json data like:
{"cid":"1",cname":"category","ads":[{"adid":"1","adName":"ad"}]}
Here I am getting datas of both parent and the related child table.But how can I get only datas of parent table.Here in this
example, I need data like:
{"cid":"1",cname":"category"}
How can I do this
I saw a nice article which describes exactly your problem.
Json Exclude Property
By configuring Entity using #JsonIgnore and #JsonProperty annotations you can achieve this.
You can try as below
Infinite Recursion with Jackson JSON and Hibernate JPA issue
Basically have to apply exclusion where you need to break the link