how to add subquery into specification in Java - java

I wish to have subquery, which provides me filtering actors by name.
I have a rest controller's method, which returns list of actors as JSON from movie base on movieId. I try to add filters as specification, but I have no idea how to write proper query. Base on "Spring Data JPA Specification for a ManyToMany Unidirectional Relationship" I found solution for subquery, which returns me all actors to proper movie base on movieId. Now I try to write this query.
Actor entity
#Data
#NoArgsConstructor
#Entity
#Table(name = "actors")
public class Actor implements Serializable {
private static final long serialVersionUID = 6460140826650392604L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "actor_id")
private Long actorId;
#Column(name = "first_name")
private String firstName;
#ManyToMany(mappedBy = "actors")
#ToString.Exclude
private List<Movie> movie = new ArrayList<>();
#JsonIgnore
public List<Movie> getMovie() {
return this.movie;
}
}
Movie entity
#Data
#Entity
#NoArgsConstructor
#Table(name = "movies")
public class Movie implements Serializable {
private static final long serialVersionUID = 3683778473783051508L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "movie_id")
private Long movieId;
private String title;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(name = "movies_actors"
, joinColumns = { #JoinColumn(name = "movie_id") }
, inverseJoinColumns = { #JoinColumn(name = "actor_id") })
private List<Actor> actors = new ArrayList<>();
#JsonIgnore
public List<Actor> getActors() {
return this.actors;
}
}
//Rest Controller
#CrossOrigin(origins = "http://localhost:3000")
#RestController
#RequestScope
#RequestMapping("/rest")
public class ActorRestController {
private ActorService actorService;
private MovieService movieService;
#Autowired
public ActorRestController(ActorService actorService, MovieService movieService) {
this.actorService = actorService;
this.movieService = movieService;
}
.
.
.
#GetMapping("movies/{movieId}/actors")
public ResponseEntity<Page<Actor>> getAllActorsFromMovieByIdMovie(#PathVariable(name = "movieId") Long movieId, Pageable pageable) {
Optional<Movie> movieFromDataBase = movieService.findMovieById(movieId);
if (movieFromDataBase.isPresent()) {
return new ResponseEntity<>(actorService.findAllActors(ActorSpec.query(movieId), pageable), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
.
.
}
// Specification for actor
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ActorSpec {
public static Specification<Actor> query(final Long movieId) {
return (root, query, cb) -> {
query.distinct(true);
Subquery<Movie> movieSubQuery = query.subquery(Movie.class);
Root<Movie> movie = movieSubQuery.from(Movie.class);
Expression<List<Actor>> actors = movie.get("actors");
movieSubQuery.select(movie);
movieSubQuery.where(cb.equal(movie.get("movieId"), movieId), cb.isMember(root, actors));
return cb.exists(movieSubQuery);
};
}
}
I would like, my code will return filtered actors by name ex.:
http://localhost:8080/rest/movies/48/actors?name=Collin
will return me
{ "actorId": 159,
"firstName": "Collin",
"lastName": "Konopelski",
"age": 21
},
but in case I do not sent any request param (http://localhost:8080/rest/movies/48/actors), let program return me all actors. I don't want to create new endpoint only for #Requestparam cause, this one is used by UI created in React.
Thanks!

Ok I found,
My solution:
RestController
#GetMapping("movies/{movieId}/actors")
public ResponseEntity<Page<Actor>> getAllActorsFromMovieByIdMovie(#PathVariable(name = "movieId") Long movieId,
#RequestParam(name = "name", required = false) String name,
Pageable pageable) {
Optional<Movie> movieFromDataBase = movieService.findMovieById(movieId);
if (movieFromDataBase.isPresent()) {
return new ResponseEntity<>(actorService.findAllActors(ActorSpec.query(movieId ,name), pageable), HttpStatus.OK);
} else {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
}
Specification
#NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ActorSpec {
public static Specification<Actor> query(final Long movieId, String name) {
return (root, query, cb) -> {
Predicate predicateMovieID = getPredicateByMovieId(movieId, root, query, cb);
if (Strings.isNotBlank(name)) {
Predicate a = cb.and(predicateMovieID, cb.equal(root.get("firstName"), name));
Predicate b = cb.and(predicateMovieID, cb.equal(root.get("lastName"), name));
return cb.or(a,b);
}
return cb.and(predicateMovieID);
};
}
private static Predicate getPredicateByMovieId(Long movieId, Root<Actor> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
query.distinct(true);
Subquery<Movie> movieSubQuery = query.subquery(Movie.class);
Root<Movie> movie = movieSubQuery.from(Movie.class);
Expression<List<Actor>> actors = movie.get("actors");
movieSubQuery.select(movie);
movieSubQuery.where(cb.equal(movie.get("movieId"), movieId), cb.isMember(root, actors));
return cb.exists(movieSubQuery);
}
}

Related

Spring JPA specification with enum mapping

In my project, I used a complicated enum for my entity, and I want to do some search function with specification using JPA.
(Entity)
CoDocument.java
#Getter #Setter
#Entity
#Table(name ="co_document")
public class CoDocument {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", nullable = false)
private Long id;
#Column(insertable = false, updatable = false , name = "co_application_type")
private CoApplicationType coApplicationType; //DB column is CHAR(255)
}
(Specification)
RepositorySpecification.java
public class RepositorySpecification implements Specification<CoDocument> {
#Serial
private static final long serialVersionUID = -7694054498602732930L;
private final List<SearchCriteria> list;
public CoapRepositorySpecification(List<SearchCriteria> list) {
this.list = list != null ? list : new ArrayList<>();
}
#Override
public Predicate toPredicate(Root<CoDocument> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> andPredicates = new ArrayList<>();
for (SearchCriteria criteria : list) {
if (criteria.getValue() == null || !StringUtils.hasLength(criteria.getValue().toString())) {
continue;
}
Path<String> rootKey = root.get(criteria.getKey();
andPredicates.add(builder.like(
rootKey,
"%" + criteria.getValue().toString().toLowerCase() + "%"));
}
return builder.and(andPredicates.toArray(new Predicate[0]));
}
(Enum)
CoApplicationType.java
#Getter #Setter
public enum CoApplicationType {
CO_TYPE_A("CO(Maldives)","CO_MALDIVES"),
CO_TYPE_B("CO(ASE)","CO_ASE");
private String applicationType;
private String formName;
private CoApplicationType(String appicationType, String formName){
this.applicationType = appicationType;
this.formName = formName;
}
(Enum converter)
CoApplicationTypeConverter.java
#Converter(autoApply = true)
public class CoApplicationTypeConverter implements AttributeConverter<CoApplicationType, String> {
#Override
public String convertToDatabaseColumn(CoApplicationType coApplicationType) {
if (coApplicationType == null) {
return null;
}
return coApplicationType.getAppicationType();
}
#Override
public CoApplicationType convertToEntityAttribute(String applicationType) {
if (applicationType == null) {
return null;
}
return Stream.of(CoApplicationType.values())
.filter(c -> c.getAppicationType().equals(applicationType))
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
}
But here comes the exception:
Parameter value [%ASE%] did not match expected type [com.scm.co.constant.CoApplicationType (n/a)]
There's something wrong with the type in the entity, and I have no clue searching the document online.
What I want to do in SQL statement is like:
SELECT * FROM co_document WHERE coApplicationType like "%ASE%";
and it does work in MySQL Workbench.
But I'm not sure how to convert it into JPA with specification, and with complicated enum structure.
Any reply would be appreciated!
You have to add
#Enumerated(EnumType.STRING) // dataType of your enum
#Column(insertable = false, updatable = false , name = "co_application_type")
private CoApplicationType coApplicationType; //DB column is CHAR(255);
for enums you have to add #Enumerated annotation before your column.

How can I receive the data of my List sent through a method POST in Java?

I am making a REST api in Java with Spring boot JPA Hibernate and with H2 database, I am trying to do a POST to "api/facturas" which is the RequestMapping of FacturaController, in which I want to add data to the FacturaDetalles List, but It returns an empty array, as if the data had never been sent, what am I doing wrong here?
I am trying to make a POST to "/api/facturas" like this:
{
"cliente": {
"idCliente": 1
},
"facturaDetalles": [
{
"producto": {
"idProducto": 3
}
}
]
}
This is what it should return:
{
"cliente": {
"idCliente": 1,
"nombreCliente": "Juan",
"apellidoCliente": "Rufiol",
"dni": 35884121,
"direccionCliente": "Av. Siempre Viva 300",
"telefonoCliente": 353654128,
"emailCliente": "juancito#gmail.com",
"cuit": 5412456985510,
"ciudad": "Capital Federal",
"provincia": "Buenos Aires",
"pais": "Argentina",
"codigoPostal": 1426,
"fechaNacimiento": "10-10-1980"
},
"facturaDetalles": [
{
"idFacturaDetalles": 1,
"producto": {
"idProducto": 3,
"nombreProducto": "Pollo frito",
"precioProducto": 40,
"descripcion": "Una delicia king chicken",
"stock": 3
}
}
]
}
This is what i get:
{
"cliente": {
"idCliente": 1,
"nombreCliente": "Juan",
"apellidoCliente": "Rufiol",
"dni": 35884121,
"direccionCliente": "Av. Siempre Viva 300",
"telefonoCliente": 353654128,
"emailCliente": "juancito#gmail.com",
"cuit": 5412456985510,
"ciudad": "Capital Federal",
"provincia": "Buenos Aires",
"pais": "Argentina",
"codigoPostal": 1426,
"fechaNacimiento": "10-10-1980"
},
"facturaDetalles": []
}
[data.sql]
INSERT INTO clientes (nombre,apellido,dni,direccion,telefono,email,cuit,ciudad,provincia,pais,codigo_postal, fecha_nacimiento ) VALUES ('Juan','Rufiol','35884121','Av. Siempre Viva 300','353654128','juancito#gmail.com','5412456985510','Capital Federal','Buenos Aires', 'Argentina',1426,'10-10-1980' );
INSERT INTO clientes (nombre,apellido,dni,direccion,telefono,email,cuit,ciudad,provincia,pais,codigo_postal, fecha_nacimiento ) VALUES ('Rolo','Garcia','41882121','Mariano Moreno 44','353614128','rolaso#rol.com','51134569854113','Capital Federal','Buenos Aires', 'Argentina',1426,'01-03-1989' );
INSERT INTO facturas (nro_factura,tipo_factura,precio_total_factura,id_cliente) VALUES (4444,'A',2000,1);
INSERT INTO facturas (nro_factura,tipo_factura,precio_total_factura,id_cliente ) VALUES (4444,'A',2000,1);
INSERT INTO facturas_detalles (cantidad,subtotal,id_facturas ,id_producto) VALUES (1,1,1,3);
INSERT INTO facturas_detalles (cantidad,subtotal,id_facturas ,id_producto) VALUES (1,1,1,2);
[Factura]
#Entity
#Table(name = "facturas")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Factura {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long idFactura;
#Column(name = "nro_factura")
private Integer nroFactura;
#Column(name = "tipo_factura")
private String tipoFactura;
#Column(name = "fecha", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private String fechaFactura;
#Column(name = "precio_total_factura")
private Integer precioTotalFactura;
#OneToOne
#JoinColumn(name = "id_cliente")
private Cliente cliente;
#JsonManagedReference
#OneToMany(mappedBy = "factura", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<FacturaDetalles> facturaDetalles = new ArrayList<>();
#OneToOne
#JoinColumn(name = "id_empresa")
private Empresa empresa;
}
[FacturaDTO]
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDTO {
private ClienteDTO cliente;
private List<FacturaDetallesDTO> facturaDetalles;
}
[FacturaServiceImp]
#Service
public class FacturaServiceImp implements FacturaService {
#Autowired
private FacturaRepository facturaRepository;
public List<Factura> getAllFacturas() {
return facturaRepository.findAll();
}
public Factura getOneFactura(Long id) {
return facturaRepository.findById(id).orElseThrow(() -> new RuntimeException("Factura no encontrado"));
}
public Factura createFactura(Factura factura) {
return facturaRepository.save(factura);
}
public void deleteFactura(Long id) {
facturaRepository.deleteById(id);
}
}
[FacturaService]
public interface FacturaService {
List<Factura> getAllFacturas();
Factura getOneFactura(Long id);
Factura createFactura(Factura factura);
void deleteFactura(Long id);
}
[FacturaController]
#RestController
#RequestMapping("/api/facturas")
public class FacturaController {
#Autowired
ModelMapper modelMapper;
#Autowired
FacturaService facturaService;
#GetMapping()
public List<FacturaDTO> getAllFacturas() {
return facturaService.getAllFacturas().stream().map(factura -> modelMapper.map(factura, FacturaDTO.class)).collect(Collectors.toList());
}
#GetMapping("/{id}")
public ResponseEntity<FacturaDTO> getOneFactura(#PathVariable Long id) {
Factura factura = facturaService.getOneFactura(id);
FacturaDTO facturaDTO = modelMapper.map(factura, FacturaDTO.class);
return ResponseEntity.ok().body(facturaDTO);
}
#PostMapping()
public ResponseEntity<FacturaDTO> createFactura(#RequestBody FacturaDTO facturaDTO) {
Factura factura = modelMapper.map(facturaDTO, Factura.class);
Factura facturaCreacion = facturaService.createFactura(factura);
FacturaDTO conversion = modelMapper.map(facturaCreacion, FacturaDTO.class);
return new ResponseEntity<FacturaDTO>(conversion, HttpStatus.CREATED);
}
#DeleteMapping("/{id}")
public ResponseEntity<FacturaDTO> deleteFactura(#PathVariable Long id) {
Factura factura = facturaService.getOneFactura(id);
facturaService.deleteFactura(id);
FacturaDTO facturaDTO = modelMapper.map(factura, FacturaDTO.class);
return ResponseEntity.ok().body(facturaDTO);
}
}
[FacturaDetalles]
#Entity(name = "facturas_detalles")
#Table(name = "facturas_detalles")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDetalles {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long idFacturaDetalles;
#Column(name = "cantidad")
private Integer cantidadProductos;
#Column(name = "subtotal")
private Integer totalParcial;
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_facturas")
private Factura factura;
#OneToOne
#JoinColumn(name = "id_producto")
private Producto producto;
}
[FacturaDetallesDTO]
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDetallesDTO {
private Long idFacturaDetalles;
private ProductoDTO producto;
}
[FacturaDetallesServiceImp]
#Service
public class FacturaDetallesServiceImp implements FacturaDetallesService {
#Autowired
private FacturaDetallesRepository facturaDetallesRepository;
public List<FacturaDetalles> getAllFacturaDetalles() {
return facturaDetallesRepository.findAll();
}
public FacturaDetalles getOneFacturaDetalles(Long id) {
return facturaDetallesRepository.findById(id).orElseThrow(() -> new RuntimeException("Factura no encontrado"));
}
public FacturaDetalles createFacturaDetalles(FacturaDetalles facturaDetalles) {
return facturaDetallesRepository.save(facturaDetalles);
}
public void deleteFacturaDetalles(Long id) {
facturaDetallesRepository.deleteById(id);
}
}
[FacturaDetallesService]
public interface FacturaDetallesService {
List<FacturaDetalles> getAllFacturaDetalles();
FacturaDetalles getOneFacturaDetalles(Long id);
void deleteFacturaDetalles(Long id);
FacturaDetalles createFacturaDetalles(FacturaDetalles facturaDetalles);
}
[FacturaDetallesController]
#RestController
#RequestMapping("/api/detalles")
public class FacturaDetallesController {
#Autowired
ModelMapper modelMapper;
#Autowired
FacturaDetallesService facturaDetallesService;
#GetMapping()
public List<FacturaDetallesDTO> getAllFacturaDetalles() {
return facturaDetallesService.getAllFacturaDetalles().stream().map(facturaDetalles -> modelMapper.map(facturaDetalles, FacturaDetallesDTO.class)).collect(Collectors.toList());
}
#GetMapping("/{id}")
public ResponseEntity<FacturaDetallesDTO> getOneFacturaDetalles(#PathVariable Long id) {
FacturaDetalles facturaDetalles = facturaDetallesService.getOneFacturaDetalles(id);
FacturaDetallesDTO detallesDTO = modelMapper.map(facturaDetalles, FacturaDetallesDTO.class);
return ResponseEntity.ok().body(detallesDTO);
}
#PostMapping()
public ResponseEntity<FacturaDetallesDTO> createFacturaDetalles(#RequestBody FacturaDetallesDTO facturaDetallesDTO) {
FacturaDetalles facturaDetalles = modelMapper.map(facturaDetallesDTO, FacturaDetalles.class);
FacturaDetalles facturaDetallesCreacion = facturaDetallesService.createFacturaDetalles(facturaDetalles);
FacturaDetallesDTO conversion = modelMapper.map(facturaDetallesCreacion, FacturaDetallesDTO.class);
return new ResponseEntity<FacturaDetallesDTO>(conversion, HttpStatus.CREATED);
}
#DeleteMapping("/{id}")
public ResponseEntity<FacturaDetallesDTO> deleteFacturaDetalles(#PathVariable Long id){
FacturaDetalles facturaDetalles = facturaDetallesService.getOneFacturaDetalles(id);
facturaDetallesService.deleteFacturaDetalles(id);
FacturaDetallesDTO detallesDTO = modelMapper.map(facturaDetalles, FacturaDetallesDTO.class);
return ResponseEntity.ok().body(detallesDTO);
}
}
try like this (lines omitted due to readability, e.g. checks)
#Autowired
private FacturaRepository facturaRepository;
#Autowired
private FacturaDetallesRepository facturaDetallesRepository;
public Factura createFactura(...) {
// get factura from db (by id from dto)
Factura factura = facturaRepository.findByIdFactura(facura.getIdFactura())
// get facturaDetalles from db (by id from dto)
FacturaDetalles facturaDetalles = facturaDetallesRepository.findByIdFacturaDetalles(...)
// link entites, for each detalles object!
facturaDetalles.setFactura(factura)
// add to list, but check if already in list first
factura.getFacturaDetalles().add(facturaDetalles)
facturaRepository.save(factura);
}

How to fill multiple tables using a single #postmapping in SpringBoot

I develop an application that allows to manage the candidates, the application contains two tables (candidate and techno) joined with a #ManyToMany join table, I'm looking for how to fill both tables with the same #PostMapping, as my code indicates. I'm using an Angular application witch send a candidat with all the informations and a table of techno (that the candidat have to select, he can not add a new techno). I would like to join the new candidat with some techno. This is what the controller will receive:
{prenom: "Pname", nom: "Name", pseudo: "Pnamename", ecole: "school", mail: "email#email.com", …}
ecole: "school"
mail: "email#email.com"
nom: "Name"
numTel: "0123456789"
prenom: "Pname"
pseudo: "Pnamename"
roleCible: "poste"
secteurActivites: "sector"
techno: Array(3)
0: "android"
1: "drupal"
2: "html"
length: 3
__proto__: Array(0)
typeContrat: "CDI"
villeRecherchee: "Paris"
__proto__: Object
1- Candidat.java
#Entity
#Table(name = "Candidats")
public class Candidat {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nom;
private String prenom;
private String ecole;
private String numTel;
private String mail;
private String pseudo;
private String roleCible;
private String typeContrat;
private String villeRecherchee;
#Temporal(TemporalType.DATE)
private Date dateCurrent = new Date();
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "candidat_techno", joinColumns = { #JoinColumn(name = "candidat_id") },
inverseJoinColumns = {
#JoinColumn(name = "techno_id") })
private Set<Techno> techno = new HashSet<>();
public Candidat() {
}
#SuppressWarnings("unchecked")
public Candidat(String nom, String prenom, String ecole, String numTel, String mail, String pseudo,
String roleCible, String typeContrat, String villeRecherchee, List<Techno> techno, Date dateCurrent,) {
super();
this.nom = nom;
this.prenom = prenom;
this.ecole = ecole;
this.numTel = numTel;
this.mail = mail;
this.pseudo = pseudo;
this.roleCible = roleCible;
this.typeContrat = typeContrat;
this.villeRecherchee = villeRecherchee;
this.techno = (Set<Techno>) techno;
this.dateCurrent = new Date();
//getters ans setters
2- CandidatController
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/avatar")
public class CandidatController {
#Autowired
CandidatDao candidatdao;
#Autowired
TechnoDao technoDao;
#PostMapping(value = "/add-candidat")
public Candidat addCandidate(#RequestBody Candidat Candidat) {
Candidat candidatAdded = candidatdao.save(Candidat);
return candidatAdded;
technodao.save(Candidat.getTechno());
}
}
3- CandidatDAO
#Repository
public interface CandidatDao extends JpaRepository<Candidat, String> {
}
4-Techno.java
#Entity
#Table(name = "techno")
public class Techno {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nomTechno;
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "techno")
private Set<Candidat> candidat = new HashSet<Candidat>();
public Techno() {
}
#SuppressWarnings("unchecked")
public Techno(String nomTechno, Candidat candidat) {
super();
this.nomTechno = nomTechno;
this.candidat = (Set<Candidat>) candidat;
}
public String getNomTechno() {
return nomTechno;
}
public void setNomTechno(String nomTechno) {
this.nomTechno = nomTechno;
}
#Override
public String toString() {
return "Techno [nomTechno=" + nomTechno + ", candidat=" + candidat + "]";
}
//getters ans setters
5- TechnoController
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/avatar")
public class TechnoController {
#Autowired
TechnoDao technodao;
#PostMapping(value = "/add-techno")
public Techno addCandidate(#RequestBody Techno Techno) {
Techno technoAdded = technodao.save(Techno);
return technoAdded;
}
}
6- TechnoDao
#Repository
public interface TechnoDao extends JpaRepository<Techno, String> {
Techno save(Set<Techno> techno);
}
for now I can fill both tables, but with two different post mapping.
how to fill both tables (techno and candidate) at the same time with a single #post mapping ?? like this:
{
id: 1,
nom: "smith",
prenom: "john",
ecole: "usa",
numTel: "11111",
mail: "j#smith",
pseudo: "JS",
roleCible: "usa",
typeContrat: "usa",
villeRecherchee: "paris",
dateCurrent: "2019-10-02",
techno: [
{
id: 1,
nomTechno: "springBoot"
},
{
id: 2,
nomTechno: "java"
}
]
}
In your CandidateController, Add this:
#Autowired
TechnoDao technoDao;
Inside post mapping use this:
technoDao.save(candidat.getTechno());
This has to help you.

Why filter for Spring Data JPA Specification doesn't work?

I try select data from the table by a filter with Spring Data JPA Specification I think what my implementation is correct, But it doesn't work. Help me please understand my mistake and fix my example.
I have very strange SQL query in log :
select phone0_.id as id1_0_, phone0_.note as note2_0_, phone0_.number as number3_0_, phone0_.operator_login as operator4_0_, phone0_.operator_pass as operator5_0_, phone0_.operator_name as operator6_0_, phone0_.operator_url as operator7_0_, phone0_.reg_date as reg_date8_0_, phone0_.status as status9_0_ from phone phone0_ where 0=1 limit ?
In the end: where 0=1 it's crash my mind. Where did that come from?
Here I fill CriteriaBuilder if filter field not null. I expect to get correctly built Specification object and send it to findAll(Specifications.where(specification), Pageable p) method. But something incorrect.
My repo and specification impl:
public interface PhoneRepository extends CrudRepository<Phone, Integer>, JpaRepository<Phone, Integer>, JpaSpecificationExecutor<Phone> {
class PhoneSpecification implements Specification<Phone> {
private final #NonNull PhoneService.PhoneFilter filter;
public PhoneSpecification(#NonNull PhoneService.PhoneFilter filter) {
this.filter = filter;
}
#Override
public Predicate toPredicate(Root<Phone> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.disjunction();
if (nonNull(filter.getId())) {
cb.disjunction().getExpressions().add(cb.equal(root.get("id"), filter.getId()));
}
if (nonNull(filter.getNote())) {
cb.disjunction().getExpressions().add(cb.like(root.get("note"), filter.getNote()));
}
if (nonNull(filter.getNumber())) {
cb.disjunction().getExpressions().add(cb.like(root.get("number"), filter.getNumber()));
}
if (nonNull(filter.getStatus())) {
cb.disjunction().getExpressions().add(cb.like(root.get("status"), filter.getStatus()));
}
if (nonNull(filter.getOpName())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorName"), filter.getOpName()));
}
if (nonNull(filter.getOpLogin())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccLogin"), filter.getOpLogin()));
}
if (nonNull(filter.getOpPassword())) {
cb.disjunction().getExpressions().add(cb.like(root.get("operatorAccPassword"), filter.getOpPassword()));
}
if (nonNull(filter.getRegFrom()) && nonNull(filter.getRegTo())) {
cb.disjunction().getExpressions().add(cb.between(root.get("regDate"), filter.getRegFrom(), filter.getRegTo()));
}
return predicate;
}
}
}
This is service level:
#Service
public class PhoneService {
#Autowired
private PhoneRepository phoneRepository;
public Phone get(int id) {
Phone phone = phoneRepository.findOne(id);
return nonNull(phone) ? phone : new Phone();
}
public Page<Phone> list(#NonNull PhoneFilter filter) {
PhoneSpecification specification = new PhoneSpecification(filter);
return phoneRepository.findAll(Specifications.where(specification), filter.getPageable());
}
#Data
public static class PhoneFilter {
private Pageable pageable;
private Integer id;
private Timestamp regFrom;
private Timestamp regTo;
private String number;
private String opLogin;
private String opPassword;
private String opName;
private String status;
private String note;
}
}
And entity
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "phone")
#ToString(exclude = {"accounts"})
#EqualsAndHashCode(exclude = {"accounts"})
public class Phone {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#OneToMany(mappedBy = "phone", cascade = CascadeType.DETACH)
private Collection<SocialAccount> accounts;
#Column(name = "reg_date")
private Timestamp regDate;
#Column(name = "number")
private String number;
#Column(name = "operator_url")
private String operatorUrl;
#Column(name = "operator_login")
private String operatorAccLogin;
#Column(name = "operator_pass")
private String operatorAccPassword;
#Column(name = "operator_name")
private String operatorName;
#Column(name = "status")
private String status;
#Column(name = "note")
private String note;
}
I find the mistake.
Method CriteriaBuilder.disjunction() this is factory and each time when I call him I got new Predicate object.
This implementation CriteriaBuilderImpl:
public Predicate disjunction() {
return new CompoundPredicate(this, BooleanOperator.OR);
}
Be careful with it.

Mapping hibernate entity with Jackson annotation

I'm working with Spring, hibernate and MySql but I have some problem with seralization of query result.
First in my entity I added #JsonManagedReference on Set structure (#OneToMany side) and #JsonBackReference on single object reference (#ManyToOne side) and it works but I wasn't be able to retrieve all needed information (for example #ManyToOne reference).
So i swapping #JsonBackReference on set structure and #JsonManagedReference on single object but I retrieve
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.model.tablesField.TableUI["data"]->java.util.ArrayList[0]->com.domain.Car["carType"]->com.domain.CarType_$$_jvst744_f["handler"])
I tried also with #JsonIgnore on Set structure but it doesn't work for the same issues.
This is my spring configuration
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
// properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
properties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
properties.put("hibernate.enable_lazy_load_no_trans",true);
return properties;
and this is part of one of my several entities:
/**
* Car generated by hbm2java
*/
#Entity
#Table(name = "car", catalog = "ATS")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonManagedReference
private CarType carType;
#JsonManagedReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonBackReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
public Car() {
}
public Car(CarType carType, Fleet fleet, int initialKm, String carChassis) {
this.carType = carType;
this.fleet = fleet;
this.initialKm = initialKm;
this.carChassis = carChassis;
}
public Car(CarType carType, Fleet fleet, String id, int initialKm, String carChassis, String note,
Set<Acquisition> acquisitions) {
this.carType = carType;
this.fleet = fleet;
this.id = id;
this.initialKm = initialKm;
this.carChassis = carChassis;
this.note = note;
this.acquisitions = acquisitions;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id_car", unique = true, nullable = false)
public Integer getIdCar() {
return this.idCar;
}
public void setIdCar(Integer idCar) {
this.idCar = idCar;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_carType", nullable = false)
public CarType getCarType() {
return this.carType;
}
public void setCarType(CarType carType) {
this.carType = carType;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_fleet", nullable = false)
public Fleet getFleet() {
return this.fleet;
}
public void setFleet(Fleet fleet) {
this.fleet = fleet;
}
#Column(name = "id", length = 5)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
#Column(name = "initialKm", nullable = false)
public int getInitialKm() {
return this.initialKm;
}
public void setInitialKm(int initialKm) {
this.initialKm = initialKm;
}
#Column(name = "carChassis", nullable = false, length = 20)
public String getCarChassis() {
return this.carChassis;
}
public void setCarChassis(String carChassis) {
this.carChassis = carChassis;
}
#Column(name = "note", length = 100)
public String getNote() {
return this.note;
}
public void setNote(String note) {
this.note = note;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
public Set<Acquisition> getAcquisitions() {
return this.acquisitions;
}
public void setAcquisitions(Set<Acquisition> acquisitions) {
this.acquisitions = acquisitions;
}
}
one method that uses the query:
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
two class for the query:
public interface DefRdiRepository extends JpaRepository<DefRdi, Integer>{
//#Query("SELECT CASE WHEN COUNT(c) > 0 THEN true ELSE false END FROM DefRdi c WHERE c.parName = ?1 AND c.description= ?2")
//Boolean existsByParNameAndDescription(String parName, String description);
//Query method of spring, I put findBy and then the key of research
DefRdi findByParNameAndDescription(String parName, String description);
}
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
Where is my error? I don't want Set object but only the single reference. The problem is only when I serialize. Thanks
UPDATE:
I use #JSonIgnore on all set collectionts and Eager instead lazy ad all works fine, but is there a way to retrieve all the information only when I want, for example having two different query?
So it doesn't work
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList){
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
return carList;
// return carRepository.findByFleetIdFleet(idFleet);
}
All collections need to be fetched eagerly when loading them from data base, in order to get serialized by Spring. Make sure you fetch them eagerly (e.g. FetchMode.JOIN). You could also swap #JsonManagedReference from wanted fields with #JsonIgnore to black listed fields, Spring automatically serialises every field without annotation.
Update:
Changing the data repository to something like that should work, I am not sure it compiles, but I think you will get the point:
#EntityGraph(value = "some.entity.graph", type = EntityGraph.EntityGraphType.FETCH)
#Query(
value = "SELECT c FROM Car c INNER JOIN FETCH c.acquisitions WHERE c.id = :idFleet"
)
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
For more information look at this post and read the official documentation.
Workaround:
There seems to be a workaround, however fetching those collections eager like shown above should have a positive performance impact, since there is no need for loading proxies afterwards. Also no open transactions are needed at controller level.

Categories

Resources