#Mapper(componentModel = "spring")
public interface ApiBeanMapper {
ApiBeanMapper mapper = Mappers.getMapper(ApiBeanMapper.class);
ApiEntity apiModelToEntity(Api user);
Api apiEntityToModel(ApiEntity user);
List<Api> apiEntityToModel(List<ApiEntity> user);
}
Trying to find a good junit5 test using Jacoco coverage report.Unable to get coverage on ApiBeanMapper mapper= Mappers.getMapper(ApiBeanMapper.class);
Api
public class Api {
private long id;
private String name;
private List<ApiResponseField> fields;
}
ApiEntity
public class ApiEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Column(name = "privilege_name")
private String privilegeName;
#OneToMany(mappedBy = "api", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private List<ApiResponseFieldEntity> apiResponseFields;
#Column(name = "field_access")
private boolean fieldAccess;
#Column(name = "field_mapping")
private boolean fieldMapping;
}
Related
I have two models that are having one to many relation (customers have many invoices)
so i create one - many relation on it, this is my customer class :
#Entity
#Table(name = "customer")
public class Customer {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "mobile_number")
private String mobileNumber;
#Column(name = "is_deleted")
private boolean isDeleted;
#OneToMany
private Set <Invoice> invoices;
}
and this is invoices class :
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JoinColumn(name = "customer_id")
private Customer customer;
}
and then i create GET API ( get customers ) but it's nor working and return this error :
nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.invoices.model.Customer["invoices"]), path=/customer/viewList}]
and this is my api :
public List<Customer> getAllCustomers() {
List<Customer> customers = cutomerRepository.findAll();
return customers;
}
and controller :
#GetMapping("/viewList")
public ResponseEntity<List<Customer>> getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers = customerService.getAllCustomers();
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers, HttpStatus.OK);
}
You have a Bidirectional relation and therefore an endless loop if json tries to deserialize the Object.
You can use #JsonIgnore to break the loop or use DTOs to return at the endpoint
#Entity
#Table(name = "invoice")
public class Invoice {
#Id
#GeneratedValue(strategy= GenerationType.IDENTITY)
private int id;
#Column(name = "serial_number")
private long serialNumber;
#Column(name = "status")
private String status;
#Column(name = "created_date")
private Timestamp createdDate;
#Column(name = "is_deleted")
private boolean isDeleted;
#ManyToOne
#JsonIgnore
#JoinColumn(name = "customer_id")
private Customer customer;
}
DTO would look something like this (I like to use records for this but since I don't know if you use Java 17 I still use class):
Customer:
#Data
public class CustomerDTO {
private final int id;
private final long serialNumber;
private final String firstName;
private final String lastName;
private final String email;
private final String mobileNumber;
private final boolean isDeleted;
private final Set <Invoice> invoices;
public static CustomerDTO fromModel(Customer customer) {
return new CustomerDTO(
customer.getId(),
customer.getSerialNumber(),
customer.getFirstName(),
customer.getLastName(),
customer.getEmail(),
customer.getMobileNumber(),
customer.isDeleted(),
customer.getInvoices()
.stream()
.map(InvoiceDTO::fromModel)
.collect(Collectors.toSet())
);
}
}
Invoice (here you don't show the customer again):
#Data
public class InvoiceDTO {
private final int id;
private final String status;
private final Timestamp createdDate;
private final boolean isDeleted;
public static InvoiceDTO fromModel(Invoice invoice) {
return new InvoiceDTO(
invoice.getId(),
invoice.getStatus(),
invoice.getCreatedDate(),
invoice.isDeleted()
);
}
}
Controller:
#GetMapping("/viewList")
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
List<CustomerDTO> customers = new ArrayList<>();
customers = customerService.getAllCustomers()
.stream()
.map(CustomerDTO::fromModel)
.toList() //Depending on Java Version .collect(Collectors.toList());
if (customers.isEmpty()) {
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
return new ResponseEntity<>(customers., HttpStatus.OK);
}
Do not open the entity class directly to the outside world
As DTO use for example:
public class InvoiceDTO {
private int id;
private long serialNumber;
private String status;
private Timestamp createdDate;
private boolean isDeleted;
private CustomerDTO customer;
}
See it applied in my GitHub repo FurnitureStoreApplication, example DTO classes in package dto:
I have the following code...
It's an application where the user has a wall, and on that wall there can be several games.
#Entity
#Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(mappedBy = "user",fetch = FetchType.EAGER)
private Mural mural;
public User() {
}
public User(Long id, String name, Mural mural) {
this.id = id;
this.name = name;
this.mural = mural;
//getters and setters...
}
#Entity
#Table(name = "tb_mural")
public class Mural implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany
#JoinTable(name = "tb_mural_games",
joinColumns = #JoinColumn(name = "mural_id"),
inverseJoinColumns = #JoinColumn(name = "games_id"))
private Set<Game> games = new HashSet<>();
#OneToOne
#JoinColumn(name = "user_id")
private User user;
public Mural() {
}
public Mural(Long id, String name, User user) {
this.id = id;
this.name = name;
this.user = user;
}
//getters and setters...
And the following request...
#GetMapping
public ResponseEntity<List<User>> findAll(){
List<User> list = repository.findAll();
return ResponseEntity.ok().body(list);
}
But I have the following error...
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role... continue...
I tried using FETCH.EAGER but it doesn't work, it only works if I use jsonignore, but I would like it to return the "mural" in the user request.
The user request data are these =
[
{
"id": 1,
"name": "Anderson Conforto"
},
{
"id": 2,
"name": "Airton Conforto"
}
]
but using jsonignore, it ignores the mural class data, and that's not what I'd like.
Put the JsonIgnore in the Mural class on
#OneToOne
#JoinColumn(name = "user_id")
private User user;
Also try to avoid directly returning entities to the end user. Either try to map the entity to DTO or use projection (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections).
I want to have a simple parent children relationship but somehow it does not work and I don't get what is missing.
Parent Mapper Interface (adding uses = {LayerMapper.class} does not change anything):
#Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
#DecoratedWith(MlpConfigMapperDecorator.class)
public interface MlpConfigMapper {
#Mapping(target = "epochNumber", source = "epochs")
#Mapping(target = "activationFunction", ignore = true)
MlpConfig toEntity(CustomMlpConfigRequest mlpConfigDto);
}
Parent decorator according to this answer (https://stackoverflow.com/a/60217018/10565504):
public abstract class MlpConfigMapperDecorator implements MlpConfigMapper {
#Autowired
#Qualifier("delegate")
private MlpConfigMapper delegate;
#Autowired
private ActivationFunctionService activationFunctionService;
#Override
public MlpConfig toEntity(CustomMlpConfigRequest mlpConfigDto) {
MlpConfig mlpConfig = delegate.toEntity(mlpConfigDto);
mlpConfig.setActivationFunction(activationFunctionService.findByType(mlpConfigDto.getActivationFunction()));
return mlpConfig;
}
}
The Parent DTO:
public class CustomMlpConfigRequest {
private String name;
private String description;
private int batchSize;
private int epochs;
private List<LayerDto> layers;
private String activationFunction;
}
The Child DTO:
public class LayerDto {
public String type;
public int orderNumber;
public int neuronsNumber;
}
Parent Entity:
public class MlpConfig {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
private String description;
private int batchSize;
private int epochNumber;
#JoinColumn(nullable = false, name = "activationFunction_id")
#ManyToOne(fetch = FetchType.LAZY)
private ActivationFunction activationFunction;
#JsonManagedReference
#Column(nullable = false)
#OneToMany(mappedBy = "mlpConfig", cascade = CascadeType.ALL)
private List<Layer> layers;
#ManyToOne(fetch = FetchType.LAZY)
private User user;
private Date lastUpdated;
}
Child Entity:
public class Layer {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private ELayer type;
private int neuronsNumber;
private int orderNumber;
#EqualsAndHashCode.Exclude
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY, optional=false)
#JoinColumn(name = "mlpConfig_id", nullable=false)
#JsonBackReference
private MlpConfig mlpConfig;
}
Generated Child Entity Mapper Method (setChildren or setMlpConfig() in my case is missing):
#Override
public LayerDto layerToDto(Layer layer) {
if ( layer == null ) {
return null;
}
LayerDto layerDto = new LayerDto();
if ( layer.getType() != null ) {
layerDto.setType( layer.getType().name() );
}
layerDto.setOrderNumber( layer.getOrderNumber() );
layerDto.setNeuronsNumber( layer.getNeuronsNumber() );
return layerDto;
}
How do I get the mapper to set the parent in the child?
do you have Layer toEntity(LayerDto layerDto);
Also, on setter of layers on entity class, you should say, layers.forEach (layer -> layer.setmlpConfig(this));
Did you do that setting job? If you don't, layers of mplConfig entity can always be null when you try to get it.
In the end I fixed it myself. I don't know if it is best practice and probably the mapper should do it without manual help but at least it works:
public abstract class MlpConfigMapperDecorator implements MlpConfigMapper {
#Autowired
#Qualifier("delegate")
private MlpConfigMapper delegate;
#Autowired
private ActivationFunctionService activationFunctionService;
#Override
public MlpConfig mlpConfigToEntity(CustomMlpConfigRequest mlpConfigDto) {
MlpConfig mlpConfig = delegate.mlpConfigToEntity(mlpConfigDto);
mlpConfig.setActivationFunction(activationFunctionService.findByType(mlpConfigDto.getActivationFunction()));
//this is the difference. I set the config for the layer manually
mlpConfig.getLayers().forEach(e -> e.setMlpConfig(mlpConfig));
return mlpConfig;
}
}
I'm doing a registration and I have the fields
Nome:
Data de Nascimento:
Inscrição Estadual:
Nome Responsável:
CPF Responsável:
Cep:
Bloco:
Número:
when i saving, I can not write data from the PessoasEnderecos class, the other data is recording normal. I'm getting all the data on the screen so much that I debugged the browser to see ..
It shows no error. Does anyone know what I'm missing ??
my class Pacientes
#Entity
#Table(name = "pacientes", schema = "sau")
public class Pacientes implements Serializable {
private static final long serialVersionUID = 5776384003601026304L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "idPaciente")
private Long idPaciente;
#JoinColumn(name="idPessoa")
#ManyToOne(cascade = CascadeType.ALL)
private Pessoas pessoa;
#Column(name = "nomeResponsavel")
private String nomeResponsavel;
#Column(name = "cpfResponsavel")
private String cpfResponsavel;
public Pacientes() {
}
//gets and sets
}
my class Pessoas
#Entity
#Table(name = "pessoas", schema="glb")
public class Pessoas implements Serializable {
private static final long serialVersionUID = -4042023941980758267L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Long idPessoa;
#Temporal(TemporalType.DATE)
private Date dataNascimento;
private String inscricaoEstadual;
private String inscricaoMunicipal;
private String nome;
public Pessoas() {
}
//gets and sets
}
#Entity
#Table(name = "pessoas_enderecos" ,schema="glb")
public class PessoasEnderecos implements Serializable {
private static final long serialVersionUID = -2560542418318988673L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idPessoaEndereco;
private String bloco;
private String cep;
private String numero;
#JoinColumn(name="idPessoa")
#ManyToOne(optional = false, cascade = CascadeType.ALL)
private Pessoas pessoa;
public PessoasEnderecos() {
}
//gets and sets
}
my methods
class Controller
#RequestMapping(method = RequestMethod.POST, value = "/pacientes")
public Pacientes cadastrarPacientes(#RequestBody Pacientes pac) {
return pacientesService.cadastrar(pac);
}
class service
public Pacientes cadastrar(Pacientes pacientes){
return pacRepository.save(pacientes);
}
class repository
public interface PacientesRepository extends JpaRepository<Pacientes, Integer> {
}
You should also add the linkage #OneToMany in Pacientes:
public class Pacientes implements Serializable {
...
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "pessoa")
#PrimaryKeyJoinColumn
private List<PessoasEnderecos> pessoasEnderecos = new ArrayList<>();
Update:
and your JSON should be something like this:
{
"nomeResponsavel": "abc",
"pessoasEnderecos": [
{
"bloco": "sdds",
"cep": "sdasdsad",
"numero": "sdasdsa"
}
]
}
I have faced with the problem that converter can't handle JSON object.
I have two objects in data base. Relationship OneToMany.
I have a AutoService with many services.
And wnen i am trying to send JSON object using postman to my server - I am getting an error:
WARN org.springframework.http.converter.json.MappingJackson2HttpMessageConverter - Failed to evaluate Jackson deserialization for type [[simple type, class com.webserverconfig.user.entity.AutoService]]: java.lang.IllegalArgumentException: Can not handle managed/back reference 'defaultReference': no back reference property found from type [collection type; class java.util.List, contains [simple type, class com.webserverconfig.user.entity.Service]]
Next two classes represents my model:
Class AutoService:
#Entity
#Table(name = "AutoRate")
public class AutoService {
public AutoService() {
}
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
private long id;
#Column(name = "serviceName", nullable = false)
private String serviceName;
#Column(name = "imageURL", nullable = false)
private String imageURL;
#Column(name = "mapCoordinate", nullable = false)
private String mapCoordinate;
#Column(name = "websiteURL", nullable = false)
private String websiteURL;
#Column(name = "phoneNumber", nullable = false)
private String phoneNumber;
#JsonManagedReference
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "autoServiceId")
private List<Service> services;
public long getId() {
return id;
}
public String getServiceName() {
return serviceName;
}
public String getImageURL() {
return imageURL;
}
public String getMapCoordinate() {
return mapCoordinate;
}
public String getWebsiteURL() {
return websiteURL;
}
public String getPhoneNumber() {
return phoneNumber;
}
public List<Service> getServices() {
return services;
}
}
Class service:
#Entity
#Table(name = "Service")
public class Service {
public Service() {
}
#Id
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
#Column(name = "serviceId", unique = true, nullable = false)
private long serviceId;
#Column(name = "serviceName", nullable = false)
private String serviceName;
#Column(name = "category", nullable = false)
private String category;
#Column(name = "price", nullable = false)
private int price;
#Column(name = "autoServiceId", nullable = false)
private long autoServiceId;
public long getId() {
return serviceId;
}
public String getCategory() {
return category;
}
public int getPrice() {
return price;
}
public String getServiceName() {
return serviceName;
}
public long getAutoServiceId() {
return autoServiceId;
}
}
Asking for help. Am i missing some annotation ?
Also Controller class:
#RestController
#RequestMapping("/directory")
public class ServiceController {
#Autowired
private AutoRateService dataBaseService;
#RequestMapping(value = "/get", method = RequestMethod.GET)
#ResponseBody
public AutoService getData(){
AutoService dataList = dataBaseService.getById(1);
return dataList;
}
#RequestMapping(value = "/saveService", method = RequestMethod.POST)
#ResponseBody public AutoService saveAutoService(#RequestBody AutoService autoService){
return dataBaseService.save(autoService);
}
}
You could add #JsonBackReference to the other site of the relation. Which by the way is missing or not correct implemented. Add:
#JsonBackReference
#ManyToOne
#JoinColumn(name = "autoServiceId", nullable = false)
private AutoService autoService;
instead of private long autoServiceId;.
Also the AutoService needs to be adjusted with:
#JsonManagedReference
#OneToMany(mappedBy = "autoService", fetch=FetchType.EAGER)
private List<Service> services = new ArrayList<>();
Solution #1 :
- add #JsonIgnore where you have attributes with #OneToMany
Example:
class User {
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JsonManagedReference
#JsonIgnore
private List<Comment> comments;
}
class Comment {
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
#JsonBackReference
private User user;
}
Solution #2:
- use on your class #JsonIgnoreProperties({"name-of-your-attribute"}), for example "comments"