Spring Boot: Custome #Query with 2 consecutive brackets cause that one pair is ignored - spring-boot

In my JpaRepository I have following #Query:
#Query("SELECT m FROM Msg m WHERE ((m.from = ?1 AND m.to = ?2) OR (m.from = ?2 AND m.to = ?1)) AND m.time = ?3")
Msg find(String firstId, String secondId, long lastAccess);
however in log console is this query logged without the upper brackets and it seems that is executed that way:
SELECT m FROM Msg m WHERE (m.from = ?1 AND m.to = ?2) OR (m.from = ?2 AND m.to = ?1) AND m.time = ?3
So how to properly add multiple consecutive brackets?
I use:
Derby
Spring Boot 1.4.3.RELEASE

I did some small investigation using Spring Boot 1.4.3-RELEASE and 1.5.4-RELEASE with two dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
I have mimic your JpaRespository:
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
interface PersonRepository extends JpaRepository<Person, Long> {
#Query("SELECT p FROM Person p WHERE ((p.firstName = :firstName AND p.lastName = :lastName) OR (p.firstName = :lastName AND p.lastName = :firstName)) AND p.age = :age")
Person findWithCustomQuery(#Param("firstName") String firstName, #Param("lastName") String lastName, #Param("age") Integer age);
}
Here is what Person class look like:
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity
#Builder
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
private Integer age;
}
Here is full source code: https://github.com/wololock/stackoverflow-answers/tree/master/45629734
The SQL query I see that is executed is following:
select person0_.id as id1_0_, person0_.age as age2_0_, person0_.first_name as first_na3_0_, person0_.last_name as last_nam4_0_ from person person0_ where (person0_.first_name=? and person0_.last_name=? or person0_.first_name=? and person0_.last_name=?) and person0_.age=?
As you can see brackets where simplified to
(person0_.first_name=? and person0_.last_name=? or person0_.first_name=? and person0_.last_name=?)
but it is still correct since and operator has higher priority than or operator.
More information needed
I'm eager to help you finding solution to your problem, but I will need more information, e.g.
what database do you use (I used H2 in this example and it's pretty obvious that different SQL dialect may generate different query)
what version of Spring Boot do you use?
what version of Spring Data do you use?

Related

SpringData JPA - No Dialect mapping for JDBC type: 2003

Am getting the below error
No Dialect mapping for JDBC type: 2003; nested exception is org.hibernate.MappingException: No Dialect mapping for JDBC type: 2
Repo Code is as follows
String chrPackageId = "select\n" +
"\tcombination_hr_id as \"chRuleId\",\n" +
"\tcombination_hr_name as \"chRuleName\", \n" +
"\tholdingrule_list as \"selectedRules\"\n" +
"from\n" +
"\tcombination_holding_rule chr\n" +
"where\n" +
"\tpackage_id =:packageId";
#Query(value=chrPackageId,nativeQuery = true)
List<CHRfromPackageIdDTO> repoCHRFromPackageId(int packageId);
DTO object is as below
public interface CHRfromPackageIdDTO {
int getChRuleId();
String getChRuleName();
Integer[] getSelectedRules();
}
We use Postgres DB, there is some issue in getting the Integer[] value actually.
The other answers in Stackoverflow are Hibernate specific. but we use spring-data-jpa.
Entity Class
import com.vladmihalcea.hibernate.type.array.IntArrayType;
import com.vladmihalcea.hibernate.type.array.StringArrayType;
import com.vladmihalcea.hibernate.type.json.JsonBinaryType;
import com.vladmihalcea.hibernate.type.json.JsonStringType;
import lombok.*;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#AllArgsConstructor
#NoArgsConstructor
#Getter
#Setter
#TypeDefs({
#TypeDef(
name = "string-array",
typeClass = StringArrayType.class
),
#TypeDef(
name = "int-array",
typeClass = IntArrayType.class
),
#TypeDef(name = "json", typeClass = JsonStringType.class),
#TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
#Entity
#Table(name="combination_holding_rule")
public class CombHoldingRule {
#Id
#Column(name="combination_hr_id")//Checked
private Integer combHoldingRuleId;
#Column(name="combination_hr_name")//Checked
private String combHoldingRuleName;
#Column(name="jurisdiction_id")//Checked
private Integer jurisdictionId;
#Column(name="function_group_id")//Checked
private Integer functionGroupId;
#Column(name="overall_netting_type")//Checked
private String overallNettingType;
#Column(name="package_id")//Checked
private Integer packageId;
#Type(type = "int-array")
#Column(
name = "holdingrule_list",
columnDefinition = "integer[]"
)
private int[] holdingRuleList;
}
In Repository
#Query(value="from CombHoldingRule where packageId=:packageId")
List<CombHoldingRule> repoCHRFromPackageId(#Param("packageId") int packageId);
I took the result from JPAQuery into the Entity , then did the below in the Service Layer
public List<CHRfromPackageIdDTO> getCHRFromPackageIdService(int packageId) {
List<CombHoldingRule> combHoldingRuleList = combinationHRrepo.
repoCHRFromPackageId(packageId);
List<CHRfromPackageIdDTO> combDTO = new ArrayList<>();
for ( CombHoldingRule combHoldingRule : combHoldingRuleList) {
CHRfromPackageIdDTO temp = new CHRfromPackageIdDTO(combHoldingRule.getCombHoldingRuleId(),
combHoldingRule.getCombHoldingRuleName(),
combHoldingRule.getHoldingRuleList());
combDTO.add(temp);
}
return combDTO;
}
Also pls check HERE
Note: This is kind of work around I believe, really not sure , how to directly take value from a native query into a custom Pojo instead of an entity class. I really appreciate if anyone post the answer for that. I would accept that as the Answer.

Validation in Spring Boot

I don't why when I submit a POST request with an invalid numSeatAvailable. No error is return. I don't know if I missed anything. Could anyone help me please?
This is my request Class
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import java.sql.Time;
import java.sql.Date;
#Setter
#Getter
#ToString
public class RideRequest {
private Date date;
private Time time;
private String destination;
private String modelName;
private int modelYear;
#Min(value = 18, message = "Age should not be less than 18")
#Max(value = 150, message = "Age should not be greater than 150")
private int numSeatAvailable;
private String rideIntro;
private Long driverId;
private Long rideId;
}
This is my controller
#PostMapping("/createRide")
public String createRide(#Valid #RequestBody RideRequest rideRequest) {
System.out.println("Hello");
rideService.createRide(rideRequest);
return "created";
}
In your controller, you must return message, info when the DTO is not valid.
About validation, you have 3 ways:
(1) Add Hibernate validator dependency
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.6.Final</version>
</dependency>
Then add annotations for validate bean (DTO: Data Transfer Object).
(2) Ah, have an easy way for you: Use spring-boot-starter-validator (it is also a derived of Hibernate validator with pre and auto configuration). See tutorial at
https://reflectoring.io/bean-validation-with-spring-boot/ . I suggest you use this way.
(3) Use Java validation (standard library). Guide:
https://spring.io/guides/gs/validating-form-input/
You can use javax.validation.constraints Library
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version><!--$NO-MVN-MAN-VER$ -->
</dependency>
Add your constraitns to your DTO Model
for example :
#NotNull(groups = ProductCreation.class,message = "fieldName.notSetted")
#NotBlank(groups = ProductUpdate.class,message = "fieldName.notSetted")
#Min(value=1, message="fieldName must be equal or greater than 0")
And you can make function for validation :
private void checkIfReadyToSave(ClassDTO yourDTO) {
Set<ConstraintViolation<ClassDTO>> violations =
validator.validate(yourDTO, groupCreation.class);
if (!violations.isEmpty()) {
Map<String, Object> body = new LinkedHashMap<>();
Set<String> keys =
violations.stream().map(ConstraintViolation::getMessage).collect(Collectors.toSet());
body.put("errors", keys);
throw new BeanValidationException(ErrorConstants.ClassDTO_MISSING_ERROR_MSG, body.toString());
}
}

JPA sorting query result after querying with Pageable

I am having an #Entity like this:
import java.time.LocalDateTime;
import javax.persistence.Entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.jpa.domain.AbstractPersistable;
#Entity
#Data#NoArgsConstructor#AllArgsConstructor
public class Message extends AbstractPersistable<Long> {
private LocalDateTime messageDate = LocalDateTime.now();
private String message;
}
And a repository like this:
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
public interface MessageRepository extends JpaRepository<Message, Long> {
//List<Message> findAllByOrderByMessageDateAsc(Pageable pageable);
// With this I am trying to re-sort what I get
}
And a #Controller
#GetMapping("/messages")
public String list(Model model) {
Pageable limit = PageRequest.of(0, 5, Sort.by("messageDate").descending());
model.addAttribute("messages", messageRepository.findAll(limit));
//model.addAttribute("messages", messageRepository.findAllByOrderByMessageDateAsc(limit));
return "messages";
}
I get five latest messages in descending order. But how do I get them in ascending order?
What you need is the last 5 messages in ascending order by massage date.
Two ways to solve it.
Using Custom implementation of Pageable
You can't use offset properly for PageRequest. So,
you need to use a custom implementation of Pageable for offset.
You can use custom implementation OffsetBasedPageRequest. Then use it this way.
int totalCount = (int)serviceRepository.count();
Pageable pageable = new OffsetBasedPageRequest(totalCount - limit, limit, Sort.by("messageDate"));
messageRepository.findAll(pageable);
After fetching Sort Page data
You can get the list from Page<T> using page.getContent() then sort manually the list.
Pageable pageable = PageRequest.of(0, limit, Sort.by("messageDate").descending());
List<Message> list = messageRepository.findAll(pageable ).getContent();
List<Message> sorted =list.stream().sorted(Comparator.comparing(r -> r.getMessageDate())).collect(Collectors.toList());
Then again you have to create Page<Massage> if you want.

#Query does not give desired result when native query is used

iam using spring data jpa in my project
package com.mf.acrs.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
#Data
#Entity(name= "mv_garage_asset_mapping")
public class GarageAssetMapping implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2535545189473989744L;
#Id
#Column(name="GARAGE_CODE")
private String garageCode;
#Column(name="GARAGE_NAME")
private String garageName;
#Column(name="GARAGE_ADDRESS")
private String garageAddress;
#Column(name="GARAGE_BRANCH")
private String garageBranch;
#Column(name="CONTRACT_NUMBER")
private String contractNumber;
}
this is my entity object
package com.mf.acrs.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.mf.acrs.model.GarageAssetMapping;
public interface GarageAssetMappingRepository extends JpaRepository<GarageAssetMapping, String> {
// #Query(name="select u.CONTRACT_NUMBER from mv_garage_asset_mapping u where u.GARAGE_CODE = ?1", nativeQuery = true) //**QUERY 1**
#Query("select u.contractNumber from mv_garage_asset_mapping u where u.garageCode = ?1") // **QUERY 2**
List<String> findByGarageCode(String garageCode);
}
this is my repository interface
when i use the QUERY 1 in my application the query fired by spring data jpa is
Hibernate: select garageasse0_.garage_code as garage_code1_2_, garageasse0_.contract_number as contract_number2_2_, garageasse0_.garage_address as garage_address3_2_, garageasse0_.garage_branch as garage_branch4_2_, garageasse0_.garage_name as garage_name5_2_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
but when i use QUERY 2 the query fired is
Hibernate: select garageasse0_.contract_number as col_0_0_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
QUERY 2 gives me desired result.
but my question is why spring data jpa fires a incorrect query in 1st case.
in QUERY 1 hibernate tries to pull all the data fields despite the fact i have explicitly written in query that i want to fetch only one field.
What mistake iam doing in this case?
The method defined in the controller which calls the method is below:
#PostMapping("/searchAssetsAjax")
#ResponseBody
public String searchAssetsAjax(#RequestBody SearchAssetData searchAssetData) throws IOException{
System.out.println("iam in the searchAssetsAjax "+searchAssetData);
System.out.println("iam in the searchAssetsAjax "+searchAssetData.toString());
// System.out.println("throwing exceptions" ); throw new IOException();
System.out.println("hitting the db "+searchAssetData.getGarageCode());
// List<String> contractNums = garageAssetMapRepository.findContractNumberByGarageCode(searchAssetData.getGarageCode());
List<String> contractNums = garageAssetMapRepository.findByGarageCode(searchAssetData.getGarageCode());
System.out.println("############contract num size is "+contractNums.size());
for(String contract: contractNums) {
System.out.println("contract nums are "+contract);
}
return "success";
}

Data Rest Repository Custom Annotated Query

I'm working my way through the Spring Data-Rest guide and struggling writing a custom annotated query and not sure if it's even possible, here's the code:
CategoryRepository
package com.example.repositories
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param
import org.springframework.data.rest.core.annotation.RepositoryRestResource
import org.springframework.data.rest.core.annotation.RestResource
import com.example.entities.Category
import com.example.entities.InventoryDetail
#RepositoryRestResource(collectionResourceRel="categories", path="categories")
interface CategoryRepository extends JpaRepository<Category, Long> {
#RestResource(path="inventoryByCategory",rel="inventoryByCategory")
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
Page<InventoryDetail> queryByCategoryStartsWithIgnoreCase(#Param("name") String name, Pageable pageable)
}
The query above is the one that I'm struggling with, not sure how to properly do this. I searched for hours looking for a solution but could not find one.
Category Entity
package com.example.entities
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.FetchType
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.JoinColumn
import javax.persistence.OneToMany
import javax.persistence.Table
#Entity
#Table(name="categories")
class Category implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
#Column
String name
#Column
String description
#OneToMany(targetEntity=Inventory.class, fetch=FetchType.LAZY)
#JoinColumn(name="category")
List<Inventory> inventory
}
Inventory Entity
package com.example.entities
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.GeneratedValue
import javax.persistence.GenerationType
import javax.persistence.Id
import javax.persistence.Index
import javax.persistence.Table
#Entity
#Table(name="inventory", indexes=[ #Index(columnList="category", unique=false) ])
class Inventory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
long id
#Column
long category
#Column
String item
#Column
String description
#Column
long price
#Column
long onHand
}
InventoryDetail
package com.example.entities
import javax.persistence.Column;
class InventoryDetail {
long id
String item
String name
InventoryDetail(long id, String item, String name) {
this.id = id
this.item = item
this.name = name
}
}
If I want to select specific fields from both entities, do I need to have a custom DTO like the one above? Is it possible to just use a new map(...) instead? Either way, the query runs and I see it in the console, but in the HAL browser it gives me a 500 error, I'm sure I am overlooking something, but not sure what it is.
I appreciate your help in advance!
EDIT
Here is the output from the Hibernate query:
Hibernate: select count(category0_.id) as col_0_0_ from categories category0_ inner join inventory inventory1_ on category0_.id=inventory1_.category where upper(category0_.name) like upper(?+'%')
Hibernate: select inventory1_.id as col_0_0_, inventory1_.item as col_1_0_, category0_.name as col_2_0_ from categories category0_ inner join inventory inventory1_ on category0_.id=inventory1_.category where upper(category0_.name) like upper(?+'%') limit ?
After countless hours of testing, I decided to throw the code in a controller and access it via the EntityManager, and it worked. After getting it to work from the controller I realized that JPA/Hibernate is expecting an Entity and not an Object/DTO.
I was able to do this...
List<Object> list(String name) {
def qry = "select new map(i.id as id, i.item as item, c.name as category) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')"
List<Object> results = em.createQuery(qry).setParameter('name',name).getResultList()
return results
}
Your Query :
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
In your custom query no join mapping for Category and Inventory. Repace this line of code by following where Category and Inventory mapped by their id in join:
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where c.id=i.category upper(c.name) like upper(:name+'%')")
Note: by default on jpql join means inner join
I'm not sure if this is the cause of your 500 error, but I see something wrong with your query, particularly with how you concatenate the paramater name with the wildcard character %.
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name+'%')")
You must remove the plus sign and the single quotes.
#Query("select new com.example.entities.InventoryDetail(i.id, i.item, c.name) from Category c join c.inventory i where upper(c.name) like upper(:name%)")
See sample implementation here.

Resources