I wish to retrieve the information contained in the database thanks to my DTO class.
The problem is that my query doesn't work without me understanding why...
Entity from database
#Entity
#Table(name = "historiquedeploiement")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class HistoriqueDeploiement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "idnamespace", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("idnamespace")
private Namespace namespace;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "idservice", nullable = false)
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("idservice")
private Service service;
#NotEmpty
#Size(max = 100)
private String tagvalue;
}
DTO :
#Data
#NoArgsConstructor
#AllArgsConstructor
public class HistoriqueDeploiementReadingDTO {
private Integer id;
#NotEmpty
private String namespacename;
#NotEmpty
private String servicename;
#NotEmpty
private String tagvalue;
}
My Query :
#Repository
public interface HistoriqueDeploiementRepository extends JpaRepository<HistoriqueDeploiement, Integer> {
List<HistoriqueDeploiement> findAll();
// Problem Here
Error creating bean with name 'historiqueDeploiementRepository': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalArgumentException: Validation failed for query for method public abstract java.util.List com.example.jpa.repository.HistoriqueDeploiementRepository.findAllDeploiement()!
#Query("SELECT new com.example.jpa.dto.HistoriqueDeploiementReadingDTO(historiquedeploiement.id, namespace.namespacename, service.servicename, historiquedeploiement.tagvalue) FROM historiquedeploiement, namespace, service WHERE namespace.id = historiquedeploiement.idnamespace and service.id = historiquedeploiement.idservice")
List<HistoriqueDeploiementReadingDTO> findAllDeploiement();
}
My goal is to have this query working :)
If you think you have a better idea than solving this problem let me know !
Thanks
Your HistoriqueDeploiement entity is missing the #Entity:
#Entity
public class HistoriqueDeploiement {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
... the rest of the class
}
Without that tag Spring-boot ORM does not know that the class represents an entity and can't perform queries on it.
Here you can find the explanation on the docs about the #Entity tag:
The Customer class is annotated with #Entity, indicating that it is a JPA entity. (Because no #Table annotation exists, it is assumed that this entity is mapped to a table named Customer.)
The solution that is working on my side is this one !
package com.example.jpa.services.historiquedeploiement;
import java.util.List;
import java.util.stream.Collectors;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import com.example.jpa.repository.HistoriqueDeploiementRepository;
import com.example.jpa.dto.HistoriqueDeploiementReadingDTO;
import com.example.jpa.model.HistoriqueDeploiement;
#Service
#Configuration
public class MapService {
#Autowired
private HistoriqueDeploiementRepository historiqueDeploiementRepository;
#Autowired
private ModelMapper modelMapper;
#Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
return modelMapper;
}
public List<HistoriqueDeploiementReadingDTO> getAllHistorique() {
return ((List<HistoriqueDeploiement>) historiqueDeploiementRepository
.findAll())
.stream()
.map(this::convertToHistoriqueDeploiementReadingDTO)
.collect(Collectors.toList());
}
private HistoriqueDeploiementReadingDTO convertToHistoriqueDeploiementReadingDTO(HistoriqueDeploiement historiqueDeploiement) {
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.LOOSE);
HistoriqueDeploiementReadingDTO historiqueDeploiementReadingDTO = modelMapper
.map(historiqueDeploiement, HistoriqueDeploiementReadingDTO.class);
return historiqueDeploiementReadingDTO;
}
}
Related
I have a case, when from request I get a BookDTO with author id, and I have to create and save new book to database with nested Author entity. Now I get author object from database before creating book, but I am not sure that this is a right way. What if I with have multiple nested entity inside one. I have to get them all from DB before save it, or there is a more clean and fast way to do it?
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import javax.persistence.*;
#Entity
#Getter
#Setter
class Book
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "author_id")
private Author author;
public Book(BookDTO bookDTO, Author author)
{
this.setName(bookDTO.getName());
this.setAuthor(author);
}
}
#Getter
#Setter
class BookDTO
{
private String name;
private Long author_id;
}
#Entity
class Author
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
#Repository
interface BookRepo extends JpaRepository<Book, Long>
{
}
#Repository
interface AuthorRepo extends JpaRepository<Author, Long>
{
}
#Controller
class BookController
{
#Autowired
AuthorRepo authorRepo;
#Autowired
BookRepo bookRepo;
#MutationMapping
public Book createUser(BookDTO bookDTO)
{
// Getting author from DB before create book
Author author = authorRepo.getReferenceById(bookDTO.getAuthor_id());
Book book = new Book(bookDTO, author);
return bookRepo.save(book);
}
}
How can I save author ID for book without getting Author from database?
I have the following entities
#JsonInclude(JsonInclude.Include.NON_NULL)
#Embeddable
public class EmbeddableEntity
{
#Cascade(CascadeType.SAVE_UPDATE)
#OneToOne
#JoinColumn(name = "fk_entity_A")
private EntityA entityA;
}
#Entity
#Table(name = "tb_entity_a")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", length = 50)
#JsonInclude(JsonInclude.Include.NON_NULL)
public abstract class EntityA
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
protected Long id;
#JsonIgnore
#OneToOne(mappedBy = "embeddableEntity.entityA", orphanRemoval = true)
protected EntityB entityB;
#NotNull
#Column(name = "boolean_field", columnDefinition = "TINYINT DEFAULT 0", nullable = false)
private Boolean booleanField = false;
}
#Entity
#Table(name = "tb_entity_b")
#DiscriminatorColumn(name = "type", length = 50)
#DiscriminatorValue("entityb")
public class EntityB
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Embedded
private EmbeddableEntity embeddableEntity;
}
I want JPAMetaModelEntityProcessor to generate a MetaModel class like this:
package br.com.loopec.loopkey.server.corp.persistence.entity;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(EmbeddableEntity.class)
public abstract class EmbeddableEntity_ {
public static volatile SingularAttribute<EmbeddableEntity, EntityB> entityB;
public static volatile SingularAttribute<EmbeddableEntity, Boolean> booleanField;
public static final String ENTITY_B = "entityB";
public static final String BOOLEAN_FIELD = "booleanField";
}
But all it generates for me is a class like this, omitting the entityB field I want generated:
package br.com.loopec.loopkey.server.corp.persistence.entity;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;
#Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
#StaticMetamodel(EmbeddableEntity.class)
public abstract class EmbeddableEntity_ {
public static volatile SingularAttribute<EmbeddableEntity, Boolean> booleanField;
public static final String BOOLEAN_FIELD = "booleanField";
}
I don't know it's useful information, but I've been facing this problem building the project from Docker.
My version of hibernate is: 5.4.15.Final
Can you see if I did something wrong with my code? I don't know if it's a hibernate bug, but I found a thread on stack overflow with another developer having a similar problem. JPA Hibernate 5: OneToOne in nested Embeddable causes metamodel issue
I am working on a project using Springboot to create API to call all provinces in the list. So first i create an entity class
package example.parameter.entity;
import lombok.Data;
import javax.persistence.*;
import javax.validation.constraints.NotBlank;
import java.util.List;
#Data
#Entity
#Table(name = "provinces",indexes = {
#Index(name = "PROVINCES_INDX_0", columnList = "name")
})
public class Province extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "provinces_generator")
#SequenceGenerator(name = "provinces_generator", sequenceName = "provinces_seq", allocationSize = 1)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column(name = "is_deleted")
private Boolean isDeleted;
#OneToMany(mappedBy = "province", cascade=CascadeType.ALL, orphanRemoval = true)
private List<Regency> regencies;
#NotBlank
#Column(name = "name")
private String name;
}
and then I created this responseDTO
package example.parameter.api.response;
import lombok.Data;
#Data
public class ProvinceResponseDTO {
private String id;
private String name;
}
after that I create the repository
package example.parameter.repository;
import example.parameter.api.response.ProvinceResponseDTO;
import example.parameter.entity.Province;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.util.List;
public interface ProvinceRepository extends JpaRepository<Province,Long> {
public List<ProvinceResponseDTO> findAllByIsDeleted(Boolean isDeleted);
}
when I am trying to hit the API I am getting this error on data layer.
org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [example.parameter.entity.Province] to type [example.api.response.ProvinceResponseDTO]
I don't know thy this is happening, any solution to fix this issue?
Part:1
JpaRepository Query creation from method name should have the default return type of the entity object. In your case, your repository is for the Province entity. So, it should be like
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public List<Province> findAllByIsDeleted(Boolean isDeleted);
}
Reference:-
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaRepository.html
Part: 2
If you need to return a custom object from the query creation from a method name, then you can use dynamic projections like below:-
public interface ProvinceRepository extends JpaRepository<Province, Long> {
public <T> List<T> findAllByIsDeleted(Boolean isDeleted, Class<T> dynamicClassType);
}
while calling this method you define like
provinceRepository.findAllByIsDeleted(true, ProvinceResponseDTO.class)
Reference:- https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections.dtos
I have 2 models in my application which have the one-to-many and many-to-one relationships.
The model classes are:
Invoice Model
#Entity
#Data
public class Invoice implements java.io.Serializable {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
private String business;
private String client;
private String invoiceNo;
#Enumerated(EnumType.STRING)
private InvoiceStatus status;
private String additionalInfo;
#OneToMany(mappedBy = "invoice")
private Set<InvoiceItem> items = new HashSet<>();
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
}
InvoiceItem Model
#Entity
#Data
public class InvoiceItem {
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
#ManyToOne
#JoinColumn(name="invoice_id", nullable = false)
private Invoice invoice;
private String description;
#Enumerated(EnumType.STRING)
private InvoiceItemType type;
#CreationTimestamp
private LocalDateTime createdAt;
#UpdateTimestamp
private LocalDateTime updatedAt;
private LocalDateTime deletedAt;
}
I have an api endpoint which is used to create an invoiceItem for an invoice
#RestController
#RequestMapping(path="/api/v1/invoices/{invoice}/items")
public class InvoiceItemController {
#Autowired
private ModelMapper modelMapper;
#Autowired
private InvoiceItemService invoiceItemService;
#Autowired
private InvoiceService invoiceService;
#PostMapping
public ResponseEntity<InvoiceItem> addInvoiceItem(#PathVariable("invoice") String invoiceId, #RequestBody InvoiceItemCreationDto invoiceItemCreationDto) throws NotFoundException {
Optional<Invoice> invoiceOptional = invoiceService.findInvoiceById(invoiceId);
if (!invoiceOptional.isPresent()) {
throw new NotFoundException("Invoice not found");
}
InvoiceItem invoiceItem = modelMapper.map(invoiceItemCreationDto, InvoiceItem.class);
invoiceItem.setInvoice(invoiceOptional.get());
InvoiceItem savedInvoiceItem = invoiceItemService.addInvoiceItem(invoiceItem);
return new ResponseEntity<>(savedInvoiceItem, HttpStatus.CREATED);
}
}
Invoice Service
package com.spencerfeng.invoiceservice.services;
import com.spencerfeng.invoiceservice.models.Invoice;
import com.spencerfeng.invoiceservice.repositories.InvoiceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
public class InvoiceServiceImpl implements InvoiceService {
#Autowired
InvoiceRepository invoiceRepository;
#Override
public Invoice addInvoice(Invoice invoice) {
return invoiceRepository.save(invoice);
}
#Override
public Optional<Invoice> findInvoiceById(String id) {
return invoiceRepository.findById(id);
}
}
Invoice Repository
package com.spencerfeng.invoiceservice.repositories;
import com.spencerfeng.invoiceservice.models.Invoice;
import org.springframework.data.repository.CrudRepository;
public interface InvoiceRepository extends CrudRepository<Invoice, String> {
}
InvoiceItem Service
package com.spencerfeng.invoiceservice.services;
import com.spencerfeng.invoiceservice.models.InvoiceItem;
import com.spencerfeng.invoiceservice.repositories.InvoiceItemRepository;
import org.springframework.beans.factory.annotation.Autowired;
public class InvoiceItemServiceImpl implements InvoiceItemService {
#Autowired
private InvoiceItemRepository invoiceItemRepository;
#Override
public InvoiceItem addInvoiceItem(InvoiceItem invoiceItem) {
return invoiceItemRepository.save(invoiceItem);
}
}
But when I call this api endpoint to create an invoiceItem for an invoice, the items property in the invoiceOptional has the 'Unable to evaluate the expression Method threw 'org.hibernate.exception.GenericJDBCException' exception, while other properties are fine.
You can try to exclude the circular tostring\hashcode reference on one POJO:
#ToString(exclude = "invoice")
#EqualsAndHashCode(exclude = "invoice")
#Entity
#Getter #Setter
public class InvoiceItem {
or to do it by using #EqualsAndHashCode.Exclude & #ToString.Exclude on the field
#Entity
#Data
public class InvoiceItem {
#ManyToOne
#EqualsAndHashCode.Exclude #ToString.Exclude
#JoinColumn(name="invoice_id", nullable = false)
private Invoice invoice;
I've got an entity class MyEntity that can be associated with other MyEntitys. I want to define the relationship between the MyEntitys. So I've ended up with a class like
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import javax.persistence.*;
import java.util.HashMap;
import java.util.Map;
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Accessors(chain = true)
#Entity
#Table
public class MyEntity {
#Id
#GeneratedValue
private Long id;
#NonNull
private String name;
private String email;
private String phone;
#NonNull
#OneToOne
private MyEntityType myEntityType;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
private Map<String, Address> addresses = new HashMap<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable()
private Map<Relationship, MyEntity> relationships = new HashMap<>();
public MyEntity addAddress(String key, Address address) {
addresses.put(key, address);
return this;
}
public MyEntity addRelationship(Relationship relationship, MyEntity myEntity) {
relationships.put(relationship, myEntity);
return this;
}
}
Where the relationship class looks like
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.experimental.Accessors;
import javax.persistence.*;
#Data
#NoArgsConstructor
#RequiredArgsConstructor
#Entity
#Table
#Accessors(chain = true)
public class Relationship {
#Id
#GeneratedValue
private Long id;
#NonNull
private String name;
#NonNull
private String antonym;
}
The field in question is the relationships field in MyEntity.
Things seem to work until I delete a MyEntity. If the deleted MyEntity has no relationships everything is ok. If the deleted MyEntity has relationships then the MyEntitys it is related to are also deleted.
If I modify the #ManyToMany annotation to
#ManyToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.MERGE})
Then the related MyEntitys are no longer deleted. Does this look like the correct way to do this?
In order to correctly define the Map<Entity, Entity> mapping you need to combine #ManyToMany (or #OneToMany), #JoinTable and the #MapJoinColumn annotations:
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(...)
#MapKeyJoinColumn(name = "relationship_id")
private Map<Relationship, MyEntity> relationships = new HashMap<>();
See here for more examples.