OrmLite Complex query on 3 tables using QueryBuilder - java

I am trying to do a query on three tables by using QueryBuilder from OrmLite.
My problem is that my final query returns no results, while doing the query only for Info, InfoFenomeno or Instruccion do return results.
public List<PasoInstruccion> consultar(Request request) {
InfoRequest infoRequest = (InfoRequest) request;
ArrayList<PasoInstruccion> pasos = new ArrayList<>();
List<PasoInstruccion> pasosResult = null;
try {
QueryBuilder<Info, Long> infoQuery = LocalDBHelper.getInstance(context).getInfoDao().queryBuilder();
QueryBuilder<InfoFenomeno, Long> fenomenoQuery = LocalDBHelper.getInstance(context).getInfoFenomenoDao().queryBuilder();
fenomenoQuery.where()
.eq(InfoFenomeno.COLUMN_FENOMENO, infoRequest.getFenomeno());
infoQuery.join(fenomenoQuery);
QueryBuilder<Instruccion, Long> instruccionQuery = LocalDBHelper.getInstance(context).getInstruccionDao().queryBuilder();
instruccionQuery.where()
.eq(Instrucciones.COLUMN_NIVEL_AFECTACION, infoRequest.getNivelAfectacion())
.and()
.eq(Instrucciones.COLUMN_CATEGORIA_INFO, infoRequest.getCategoria())
.and()
.eq(Instrucciones.COLUMN_AMBIENTE_INFO, infoRequest.getSituacion());
QueryBuilder<PasoInstruccion, Long> pasoInstruccionQuery = LocalDBHelper.getInstance(context).getPasoInstruccionDao().queryBuilder();
instruccionQuery.join(infoQuery);
pasoInstruccionQuery.join(instruccionQuery);
pasosResult = pasoInstruccionQuery.query();
} catch (SQLException e) {
e.printStackTrace();
}
return pasosResult;
}
The structure on the DB is like this (simplified):
public class Info {
#DatabaseField(id = true, columnName = Infos.COLUMN_ID)
private long id;
#DatabaseField(columnName = Infos.COLUMN_NOMBRE)
private String nombre;
#ForeignCollectionField(eager = true, columnName = Infos.COLUMN_FENOMENO)
private ForeignCollection<InfoFenomeno> infoFenomenos;
#ForeignCollectionField(eager = true, columnName = Infos.COLUMN_INSTRUCCION)
private ForeignCollection<Instruccion> pInstrucciones;
}
public class InfoFenomeno {
#DatabaseField(generatedId = true, columnName = InfoFenomeno.COLUMN_ID)
private long id;
#DatabaseField(foreign = true, foreignAutoRefresh = true, columnName = InfoFenomeno.COLUMN_INFO)
private Info info;
#DatabaseField(foreign = true, foreignAutoRefresh = true, columnName = InfoFenomeno.COLUMN_FENOMENO)
private Fenomeno fenomeno;
}
public class Instruccion {
#DatabaseField(generatedId = true)
private long id;
#DatabaseField(columnName = Instrucciones.COLUMN_NIVEL_AFECTACION)
private String nivelAfectacionString;
#DatabaseField(columnName = Instrucciones.COLUMN_CATEGORIA_INFO)
private String categoriaInformacionString;
#DatabaseField(columnName = Instrucciones.COLUMN_AMBIENTE_INFO)
private String ambienteInformacionString;
#ForeignCollectionField(eager = true)
private ForeignCollection<PasoInstruccion> pPasosInstruccion;
#DatabaseField(foreign = true, foreignAutoRefresh = true, columnName = Instrucciones.COLUMN_INFO)
private Info info;
}
public class PasoInstruccion {
#DatabaseField(id = true, columnName = PasoInstrucciones.COLUMN_ID)
private long secuencia;
#DatabaseField(columnName = PasoInstrucciones.COLUMN_NOMBRE)
private String nombre;
#DatabaseField(columnName = PasoInstrucciones.COLUMN_INSTRUCCION, foreignAutoRefresh = true, foreign = true)
private Instruccion instruccion;
}
Values in InfoRequest parameter are right.
May be that I am not using QueryBuilder the right way?
EDIT: SQL Statement
SELECT `pasoInstrucciones`.* FROM `pasoInstrucciones` INNER JOIN `instrucciones` ON `pasoInstrucciones`.`_instruccion` = `instrucciones`.`id` INNER JOIN `infos` ON `instrucciones`.`_info` = `infos`.`_id` INNER JOIN `infoFenomeno` ON `infos`.`_id` = `infoFenomeno`.`_info` WHERE ((`instrucciones`.`_nivelAfectacion` = 'BAJO' AND `instrucciones`.`_categoriaInfo` = 'ANTES' ) AND `instrucciones`.`_ambienteInfo` = 'HOGAR' ) AND (`infoFenomeno`.`_fenomeno` = 6 )
Thanks in advance.

Related

java.util.NoSuchElementException: No value present

So my problem is in a Test, when I call the method to test it gives this error:
java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.get(Optional.java:143)
at com.MD.Medicine.Services.SaveService.savePlans(SaveService.java:57)
at com.MD.Medicine.Services.SaveServiceTest.testSavePlans_failPills(SaveServiceTest.java:99)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
My test:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class SaveServiceTest {
#MockBean
private MedsRepo medsRepo;
#MockBean
private PlansRepo plansRepo;
#MockBean
private PlanDayRepo planDayRepo;
#Autowired
private SaveService saveService;
#Test
void testSavePlans_failPills() {
LocalDate date = LocalDate.now();
Date date3 = new Date(1673740800000L);
Set<PlanDay> setPlans = new HashSet<>();
Plans plans = new Plans(1, setPlans);
BigDecimal price = new BigDecimal(8.00);
Meds meds = new Meds(1, "Brufen", price, "Pain", 200, date, setPlans);
when(medsRepo.getReferenceById(meds.getMedsId())).thenReturn(meds);
int pillNumber = meds.getPillNumber();
List<PlanDay> planList3 = new ArrayList<PlanDay>();
PlanDay planDay3 = new PlanDay(1, date3, "Tuesday", plans, meds, 50000);
planList3.add(planDay3);
String expected3 = saveService.savePlans(planList3);
assertThat(expected3).isEqualTo("Error: No piils available (Existing Pills: " + pillNumber + ")");
}
When it gets in * String expected3 = saveService.savePlans(planList3);* it stops and prints the error.
The method:
public String savePlans(List<PlanDay> plans) throws Error {
//long planIdVerify = plans.get(0).getPlanDaysId();
Date firstDate = plans.get(0).getPlanDate();
long todayMili = System.currentTimeMillis();
long dateLimitMili = firstDate.getTime() + 604800000;
long planId = plans.get(0).getPlans().getPlanId();
Plans plansWithId = new Plans();
plansWithId.setPlanId(planId);
plansRepo.save(plansWithId);
for (int i = 0; i < plans.size(); i++) {
long planDateInMili = plans.get(i).getPlanDate().getTime();
//long planIdMultiVerify = plans.get(i).getPlanDaysId();
if (planDateInMili <= dateLimitMili && todayMili<planDateInMili ) {
PlanDay planDay = plans.get(i);
long medsId = planDay.getMeds().getMedsId();
int medsToTake = planDay.getMedsToTake();
int pillNumber = medsRepo.getReferenceById(medsId).getPillNumber();
int pillUpdate = pillNumber - medsToTake;
Meds updatePlanDay = medsRepo.findById(medsId).get();
if (pillUpdate > 0) {
updatePlanDay.setPillNumber(pillUpdate);
} else {
return "Error: No piils available (Existing Pills: " + pillNumber + ")";
}
planDayRepo.save(planDay);
} else {
return "Week time interval not correct/Invalid planId (only one plan can be saved)";
}
}
return "Saved Successfully";
}
and my entities:
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "meds")
#JsonIgnoreProperties(value = { "days" })
public class Meds {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long medsId;
#Column
private String medsName;
#Column
private BigDecimal price;
#Column
private String category;
#Column
private int pillNumber;
#Column
#CreationTimestamp
private LocalDate medsDate;
#OneToMany(mappedBy = "meds", cascade = {CascadeType.REMOVE}, fetch = FetchType.LAZY)
#OnDelete(action = OnDeleteAction.CASCADE)
private Set<PlanDay> days = new HashSet<PlanDay>();
}
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name = "planDay")
#JsonIgnoreProperties(value = { "planDaysId" })
public class PlanDay {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long planDaysId;
#Column
private Date planDate;
#Column
private String weekday;
#ManyToOne
#JoinColumn(name = "planId", nullable = false)
private Plans plans;
#ManyToOne
#JoinColumn(name = "medsId", nullable = false)
private Meds meds;
#Column
private int medsToTake;
}
I have been looking for a solution and the orElse() method was one of the options but I can't make it work.. What would be a solution for this problem?
Kind Regards.
MedsRepo is a MockBean when you call medsRepo.findById(medsId) it will return an empty optional because you have no when for that method.
int pillNumber = medsRepo.getReferenceById(medsId).getPillNumber(); //extract the Meds as a variable and keep using this
int pillUpdate = pillNumber - medsToTake;
Meds updatePlanDay = medsRepo.findById(medsId).get(); //trying to get the same as you did above
Should be
Meds updatePlanDay = medsRepo.getReferenceById(medsId);
int pillNumber = updatePlanDay.getPillNumber();
int pillUpdate = pillNumber - medsToTake;
//Meds updatePlanDay = medsRepo.findById(medsId).get(); no longer needed
Also on a different note you should look into the difference between findById and getReferenceById and what happens when there is no Meds with that medsId

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.

JPA CriteriaBuilder with CONCAT_WS function throws NullPointerException

I have the following CriteriaQuery that I use to filter orders.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<OrderReducedDTO> cq = cb.createQuery(OrderReducedDTO.class);
Root<Order> root = cq.from(Order.class);
Join<Order, Customer> joinCustomer = root.join(Order_.customer);
Join<Order, Shipment> joinShipment = root.join(Order_.shipment);
Join<Shipment, Carrier> joinCarrier = joinShipment.join(Shipment_.carrier);
Join<Order, Payment> joinPayment = root.join(Order_.payment);
Join<Payment, PaymentMethod> joinPaymentMethod = joinPayment.join(Payment_.paymentMethod);
Join<Shipment, Country> joinCountry = joinShipment.join(Shipment_.country);
cq.select(cb.construct(
OrderReducedDTO.class,
root.get(Order_.id),
root.get(Order_.incrementId),
root.get(Order_.state),
root.get(Order_.couponCode),
root.get(Order_.totalDiscount),
root.get(Order_.total),
root.get(Order_.originChannel),
root.get(Order_.branchOffice),
joinCarrier.get(Carrier_.carrierCode),
cb.function("CONCAT_WS", String.class,
cb.literal(","),
joinShipment.get(Shipment_.streetName),
joinShipment.get(Shipment_.streetNumber),
joinShipment.get(Shipment_.city),
joinCountry.get(Country_.name),
joinShipment.get(Shipment_.zipCode)
),
joinPaymentMethod.get(PaymentMethod_.code),
joinPayment.get(Payment_.paymentDate),
root.get(Order_.createdAt),
root.get(Order_.updatedAt),
root.get(Order_.externalId),
joinCustomer.get(Customer_.fullName)
));
... filters and predicates...
The part that's giving me trouble and causing a NPE to be thrown is this
cb.function("CONCAT_WS", String.class,
cb.literal(","),
joinShipment.get(Shipment_.streetName),
joinShipment.get(Shipment_.streetNumber),
joinShipment.get(Shipment_.city),
joinCountry.get(Country_.name),
joinShipment.get(Shipment_.zipCode)
)
More, specifically, when I use the CONCAT_WS function. If I use CONCAT, it works.
This is the stacktrace I get:
java.lang.NullPointerException: null
at org.hibernate.hql.internal.NameGenerator.generateColumnNames(NameGenerator.java:27)
at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.generateColumnNames(SessionFactoryHelper.java:434)
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeColumnNames(SelectClause.java:270)
at org.hibernate.hql.internal.ast.tree.SelectClause.finishInitialization(SelectClause.java:260)
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:255)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:1026)
...
This is my OrderReducedDTO
#Getter
public class OrderReducedDTO {
#JsonProperty("order_id")
private Integer orderId;
#JsonProperty("increment_id")
private String incrementId;
private OrderStates state;
#JsonProperty("coupon_code")
private String couponCode;
#JsonProperty("total_discount")
private BigDecimal totalDiscount;
private BigDecimal total;
#JsonProperty("origin_channel")
private String originChannel;
#JsonProperty("branch_office")
private String branchOffice;
#JsonProperty("shipping_method")
private String shippingMethod;
#JsonProperty("shipping_address")
private String shippingAddress;
#JsonProperty("payment_method")
private String paymentMethod;
#JsonProperty("payment_date")
private Timestamp paymentDate;
#JsonProperty("created_at")
private Timestamp createdAt;
#JsonProperty("updated_at")
private Timestamp updatedAt;
#JsonProperty("external_id")
private String externalId;
#JsonProperty("customer_full_name")
private String customerFullName;
#Setter
private List<OrderProductReducedDTO> products;
public OrderReducedDTO(Integer orderId,
String incrementId,
OrderStates state,
String couponCode,
BigDecimal totalDiscount,
BigDecimal total,
String originChannel,
String branchOffice,
String shippingMethod,
String shippingAddress,
String paymentMethod,
Object paymentDate,
Object createdAt,
Object updatedAt,
String externalId,
String customerFullName) {
this.orderId = orderId;
this.incrementId = incrementId;
this.state = state;
this.couponCode = couponCode;
this.totalDiscount = totalDiscount;
this.total = total;
this.originChannel = originChannel;
this.branchOffice = branchOffice;
this.shippingMethod = shippingMethod;
this.shippingAddress = shippingAddress;
this.paymentMethod = paymentMethod;
this.paymentDate = (Timestamp) paymentDate;
this.createdAt = (Timestamp) createdAt; //https://hibernate.atlassian.net/browse/HHH-4179
this.updatedAt = (Timestamp) updatedAt;
this.externalId = externalId;
this.customerFullName = customerFullName;
}
}
What I mainly want to know is if I'm using the function method correctly. I assume I am because CONCAT works.
After hours of debugging within Hibernate, I finally arrived at the root of the problem:
org/hibernate/hql/internal/ast/tree/ConstructorNode.java
private Type[] resolveConstructorArgumentTypes() throws SemanticException {
SelectExpression[] argumentExpressions = this.collectSelectExpressions();
if (argumentExpressions == null) {
return new Type[0];
} else {
Type[] types = new Type[argumentExpressions.length];
for(int x = 0; x < argumentExpressions.length; ++x) {
types[x] = argumentExpressions[x].getDataType();
}
return types;
}
}
argumentExpressions[x].getDataType() was returning null.
I googled and found out that this could be caused by Hibernate not knowing the actual return type of the given SQL function (apparently it only knows the most common ones).
I then followed this answer and implemented a custom MetadataBuilderContributor like so:
public class SqlFunctionsMetadataBuilderContributor implements MetadataBuilderContributor {
#Override
public void contribute(MetadataBuilder metadataBuilder) {
metadataBuilder.applySqlFunction(
"concat_ws",
new StandardSQLFunction("concat_ws", StandardBasicTypes.STRING)
);
}
}
And on my application.properties I added:
spring.jpa.properties.hibernate.metadata_builder_contributor=ar.com.glamit.glamitoms.config.hibernate.SqlFunctionsMetadataBuilderContributor
After relaunching the app, argumentExpressions[x].getDataType() now returns a StringType and the NullPointerException is gone.

how to add subquery into specification in 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);
}
}

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.

Categories

Resources