Spring Data REST making range check with QueryDSL binding - java

I have request like this:
http://localhost:4000/services/querywithdsl?min=30&max=90
My domain entity only consists of id :
#Entity
#Data
public class ServiceNumberPorting {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(updatable = false, nullable = false)
private Long id;
}
And my rest controller with predicate
#GetMapping(value = "/services/querywithdsl")
public ResponseEntity<List<ServiceNumberPortingMapper>>getServiceNumberPortingWithDsl(#QuerydslPredicate(root = ServiceNumberPorting.class) Predicate predicate) {
...
}
Repository:
public interface ServiceNumberPortingRepository extends JpaRepository<ServiceNumberPorting, Long>,QuerydslPredicateExecutor<ServiceNumberPorting>, QuerydslBinderCustomizer<QServiceNumberPorting>
I want to build query with querydsl like this one using bindings
..where id>min and id< max
All examples I found are directly mapping query parameters to entity column, but in this example, I have only id in my entity and I want to do range check with incoming parameter values which have different name than my entity columns.
However, since my domain won't consist of min and max columns I could't get it working with customizing binding such as using QueryDslBinderCustomizer:
bindings.bind(store.city).single((path, value) -> path.endsWith(value));
How to accomplish that?
Here is docs that I gone through:
spring data examples
binding examples

One of the solutions I can think of is this:
In your repository,I have defined a method getValueLyingBetween and written a simple query:
public interface ServiceNumberPortingRepository extends JpaRepository...{
#Query("SELECT m FROM <YOUR_TABLE_NAME> m where ( m.id >=:min and m.id <= :max )")
public ServiceNumberPorting getValueLyingBetween(#Param("min") String min, #Param("max") String max);
}
And then you can simply call this repository method from your controller with min and max values.
Is this what you were looking for?

Related

How do I populate two fields of entity using Hibernate?

I am building an API to return two fields as such:
{
currentPoints: 325,
badgeName: "Some Badge"
}
However, I am having trouble using hibernate in order populate those two fields. I made two attempts and both are throwing errors. Both of these errors can be found in their respective Repository file. In the 2nd attempt, I am using native=true and am able to get it to work using a SELECT *. However, I am trying to only populate and return two fields of the entity.
One solution I thought about is using the 2nd approach with a SELECT * and creating another package named response with CurrentInfoResponse class and just returning that class. However, I wanted to see if there was a way to avoid this using the current model that I have.
Possible Solution:
#Getter
#AllArgsConstructor
public class CurrentInfoResponse{
private Integer currentPoints;
private String badgeName
}
Package Structure:
Controller.java:
#GetMapping("/current-badge/{userId}")
public CurrentBadgeInfoModel getCurrentBadge(#PathVariable Integer userId){
return currentBadgeInfoService.getCurrentBadge(userId);
}
ServiceImpl.java:
#Override
public CurrentBadgeInfoModel getCurrentBadge(Integer userId){
return currentBadgeInfoRepository.getCurrentBadge(userId);
}
CurrentBadgeInfoModel.java:
#Getter
#Entity
#Table(name = "user_current_badge_info")
public class CurrentBadgeInfoModel {
#Id
#Column(name = "user_current_info_id")
private Integer userCurrentBadgeInfo;
#Column(name = "user_id")
private Integer userId;
#Column(name = "current_points")
private Integer currentPoints;
#ManyToOne
#JoinColumn(name = "badge_id")
private BadgeModel badgeModel;
}
BadgeModel.java
#Getter
#Entity
#Table(name = "badge_info")
public class BadgeModel {
#Id
#JoinColumn(name= "badge_id")
private Integer badgeId;
#Column(name = "badge_name")
private String badgeName;
}
Repository.java - ATTEMPT 1:
#Repository
public interface CurrentBadgeInfoRepository extends JpaRepository<CurrentBadgeInfoModel, Integer> {
#Query("SELECT cbim.currentPoints, cbim.badgeModel.badgeName FROM CurrentBadgeInfoModel cbim JOIN
cbim.badgeModel WHERE cbim.userId=?1")
CurrentBadgeInfoModel getCurrentBadge(Integer userId);
}
//Error: No converter found capable of converting from type [java.lang.Integer] to type [com.timelogger.model.CurrentBadgeInfoModel]
Repository.java - ATTEMPT 2:
#Repository
public interface CurrentBadgeInfoRepository extends JpaRepository<CurrentBadgeInfoModel, Integer> {
#Query(value = "SELECT current_points, badge_name FROM user_current_badge_info ucbi JOIN badge_info bi ON ucbi.badge_id=bi.badge_id WHERE user_id=?1", nativeQuery = true)
CurrentBadgeInfoModel getCurrentBadge(Integer userId);
}
//Error: Column 'user_current_info_id' not found
Using the SELECT clause of HQL should help you here.
If you don't have that constructor, you can add it
#Query("SELECT new CurrentBadgeInfoModel(cbim.currentPoints, cbim.badgeModel.badgeName) FROM CurrentBadgeInfoModel cbim JOIN
cbim.badgeModel WHERE cbim.userId=?1")
Notice the usage of new CurrentBadgeInfoModel(cbim.currentPoints, cbim.badgeModel.badgeName)
I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(CurrentBadgeInfoModel.class)
public interface CurrentInfoResponse {
Integer getCurrentPoints();
#Mapping("badgeModel.badgeName")
String getBadgeName();
}
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
CurrentInfoResponse findByUserId(Integer userId);
The best part is, it will only fetch the state that is actually necessary!

ListJoin and multiselect with criteriaquery with Polymorphism

I was wondering if anyone knows the cause of the Exception being thrown? I have the following entities below. Is it because some Employees returned aren't DriverEmployees and thus do not have routes?
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
}
#Entity
public class DriverEmployee extends Employee {
#OneToMany(fetch = FetchType.LAZY)
private List<Routes> routes;
}
#Entity
public class Routes {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
private String name;
private String description;
}
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
// Want routes to be returned as a list in the multiselect.
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
query.getResultList().forEach((t) -> {
process(t.get("name", String.class));
processList(t.get("routes", List.class).size());
});
The error I'm receiving is below. Anyone know how I can get the following to run successfully?
Caused by: java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.IdentNode
+-[IDENT] IdentNode: 'routes' {originalText=routes}
In this current example you have tuple which contains one object of DriverEmployee and one object of Routes (I recommend you to rename this entity to Route and set #Table(name = "routes"). According to JavaDoc of multiselect():
If the type of the criteria query is CriteriaQuery (i.e., a criteria query object created by either the createTupleQuery method or by passing a Tuple class argument to the createQuery method), a Tuple object corresponding to the arguments of the multiselect method, in the specified order, will be instantiated and returned for each row that results from the query execution.
So it means that you CAN'T make a Tuple like Tuple<DriverEmployee, List<Routes>>
The way how you can reach such behavior of your current query is to do it by yourself. For instance:
your method with criteria:
CriteriaQuery<Tuple> criteria = criteriaBuilder.createQuery(Tuple.class);
Root<Employee> employee = criteria.from(Employee.class);
Root<DriverEmployee> driverEmployee = criteriaBuilder.treat(employee, DriverEmployee.class);
ListJoin<DriverEmployee, Routes> routes = driverEmployee.joinList("routes");
criteria.multiselect(employee.get("name").alias("name"), routes.alias("routes"));
TypedQuery<Tuple> query = em.createQuery(criteria);
List<Tuple> resultList = query.getResultList();
Map<String, List<Routes>> resultMap = getMapFromResultList(resultList);
resultMap.entrySet().forEach((name, routesList) -> {
process(name);
processList(routesList);
});
and the method of obtaining the map:
private Map<String, List<Routes>> getMapFromResultList(List<Tuple> tuples) {
Map<String, List<Routes>> map = new HashMap<>();
tuples.forEach(tuple -> {
String name = tuple.get("name", String.class);
Routes routes = tuple.get("routes", Routes.class);
map.computeIfPresent(name, (key, value) -> {
value.add(routes);
return value;
});
map.computeIfAbsent(name, (key) -> {
List<Routes> routesList = new ArrayList<>();
routesList.add(routes);
return routesList;
});
});
return map;
}
I guess the Hibernate JPA Criteria implememtation doesn't support that. If you really want to use the JPA Criteria API to do this, you are probably out of luck. In JPQL/HQL you could model this like SELECT e.name, r FROM DriverEmployee e LEFT JOIN e.routes r. On top of that you have to extract the values like Andrew Kolesnyk mentioned.
However, this is is the perfect use case for Blaze-Persistence Entity Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(DriverEmployee.class)
interface EmployeeDto {
#IdMapping
Integer getId();
String getName();
List<RoutesDto> getRoutes();
}
#EntityView(Routes.class)
interface RoutesDto {
#IdMapping
Integer getId();
String getName();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EmployeeDto dto = entityViewManager.find(entityManager, EmployeeDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features and it can also be saved back. Here a sample repository
#Repository
interface EmployeeRepository {
EmployeeDto findOne(Long id);
}
It will only fetch the mappings that you tell it to fetch.
Here you can see an example project: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-webmvc

Spring Boot custom query group by and count

I'm having a problem with accessing data from a custom query.
Here is the POJO:
#Getter
#Setter
#NoArgsContructor
#AllArgsConstructor
#Entity
#Table(name = "run_list")
public class RunList {
#Id
#Column(name = "id")
private Long id;
#Column(name = "control_run_name"
private String serverName;
#Column(name = "control_run_date"
private Date controlRunDate
<.. bunch of other fields .. >
Here is the repository:
public interface RunListRepository extends JpaRepository<RunList, Long> {
#Query("SELECT u.serverName,count(u) as controlRunCount from RunList u where u.controlRunDate < :lastUploadDate group by u.serverName")
List<RunList> findAllControlRunAfterDate(#Param("lastUploadDate") Date lastUploadDate);
In the controller I invoke the repository method like so:
Date date = new SimpleDateFormat("yyyy-MM-dd").parse("2020-03-01");
model.addAttribute("runList",runListRepository.findAllControlRunAfterDate(date);
The binding to the date in the query works ok I get a first result of the group by so that is solved. The thing is I get an error while runing this :
Failed to convert from type[java.lang.Object[]] to type [#org.springframework.data.jpa.Query my.package.name.RunList} for value '{server1,14}';
When I use the SQL query against the database from it's CLI I get a nice grouping of the serverName and the count.
I'm guessing the problem is in the conversion of the count field that is popping out of nowhere from the repository method and Spring doesn't know what to link this too.
I've tried to use a RunListDTO here that has only a String serverName and an Ingeter controlRunCount with no luck - the repository interface didn't like me using a DTO in the output of a method used in a interface created with .
Is there a way to make this a custom Array/Object in flight when the repository does the count?
Regards,
Jarek.
Since you are selecting a field and a count there is no way it can be mapped to your entity, so this query returns an array of values you specified, like you can see in the exception: '{server1,14}'.
public interface RunListRepository extends JpaRepository<RunList, Long> {
#Query("SELECT u.serverName,count(u) as controlRunCount from RunList u where u.controlRunDate < :lastUploadDate group by u.serverName")
List<Object[]> findAllControlRunAfterDate(#Param("lastUploadDate") Date lastUploadDate);
So in your service you can work with this data like:
List<Object[]> list = runListRepository.findAllControlRunAfterDate(yourDate);
for (Object[] ob : list){
String serverName = (String)ob[0];
Integer count = (Integer)ob[1];
}

Select only some columns from a table

Is there a way to select only some columns from a table using jpa?
My tables are huge and I am not allowed to map all the columns in my entities. I tried to create an entity (as a side note, I don't have PKs in my tables):
#Entity
#Table(name = "SuperCat")
#Getter
#Setter
public class Cat{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(name="nameCat")
private String name;
}
and then in my repository to
public interface CatRepository extends
CrudRepository<Cat, Long> {
#Query(
"SELECT name FROM Cat")
Page<Cat> getAlCats(Pageable pageable);
This is only a simple example, but the idea is the same. I have searched a lot and I found projections, but there you need to map the whole table, then I found native queries, but still doesn't apply. I know I can return an Object and the other solution is to use query with NEW and create my own object (no #entity, like a pojo). But is there a way that I can do this using jpa, to be able to use repository and services, if I am creating my own pojo then i will create a #transactional class put the queries (with NEW) there and this is it. I don't like this approach and I don't think that the jpa does't allow you to select only some columns, but I didn't find a proper way.
Maybe you will ask what is the result if I am doing like this:
I get this error: "Cannot create TypedQuery for query with more than one return using requested result type [java.lang.Long]"
(For new queries, I am talking about : http://www.java2s.com/Tutorials/Java/JPA/4800__JPA_Query_new_Object.htm maybe I was not clear)
You can do the same by using below approach.
Just create a constructor in entity class with all the required parameters and then in jpa query use new operator in query like below.
String query = "SELECT NEW com.dt.es.CustomObject(p.uniquePID) FROM PatientRegistration AS p";
TypedQuery<CustomObject> typedQuery = entityManager().createQuery(query , CustomObject.class);
List<CustomObject> results = typedQuery.getResultList();
return results;
And CustomObject class should look like below with the constructor.
public class CustomObject {
private String uniquePID;
public CustomObject(String uniquePID) {
super();
this.uniquePID = uniquePID;
}
public String getUniquePID() {
return uniquePID;
}
public void setUniquePID(String uniquePID) {
this.uniquePID = uniquePID;
}
}
spring-data-jpa projection not need to map the whole table, just select the necessary fileds :
// define the dto interface
public interface CatDto {
String getName();
// other necessary fields
...
}
#Query(value = "select c.name as name, ... from Cat as c ...)
Page<CatDto> getAllCats(Pageable pageable);
By this way, CatDto is an interface and it only includes some fileds part of the whole table. Its fields name need to match the select field's alias name.

what to use instead of fetch.EAGER in spring data?

I'm working with spring-boot and angular5 , i have this entity in spring :
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Contrat implements Serializable{
#Id #GeneratedValue
private Long id;
private Date dateDebut ;
private Date dateFin ;
#ManyToOne
#JoinColumn(name = "Id_Project")
#JsonBackReference(value="projet-contrat")
private Project project;
#ManyToOne
#JoinColumn(name = "Id_AppUser")
#JsonBackReference(value="appuser-contrat")
private AppUser appUser;
}
A repository :
public interface ContratRepo extends JpaRepository<Contrat,Long> {
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}
As the fetch.lazy is the default one , when i try to call the method findByAppUser i get as result :
{id: 1, dateDebut: 1526083200000, dateFin: 1526083200000}
Which is normal , what i want for my case is to load also the object 'project' that exists in the entity , but i don't wan't to use the fetch.EAGER , any solution for this goal ?
Your entity is one-many relationship object. If you don't use EAGER, spring data will get the object without related member object. And if you get that with contract.getProject().getName(), then another query will be sent to get that member.
If you log the SQL, you can see that, there will be 2 queries. But if you set the field as EAGER, there will be only 1 query. You can get improvement obviously.
But you should not use EAGER always. If in 90% of time, you just need the Contract object, but no need the project data of it. It is a waste of time to get that. Because in SQL, it will relate 2 tables and get all columns of data.
SO, you should make this decision based on your usage of this entity.
[Updated based on comment]
You can use Query to write your sql expression. for example, I have a method to get the entity with detail:
#Query("select s from Contract s left join fetch s.project pr where s.id = ?1 ")
Contract findOneWithDetail(Long id);
If I need to get the detail in ONE sql, I can use this method. If I don't need the project detail, I just use findOne(Long id), which is provided interface.
And, if you just want to get some columns, you need to define a DTO, with a constructor, and write your method like this:
#Query("SELECT NEW com.mypackage.dto.ContractDTO(s.id, s.name, s.status) FROM Contract AS s WHERE s.status = ?1")
List<ContractDTO> findDTOAllByStatus(String status);
Provide the query in your repo method, e.g. (syntax may be wrong, just show you the idea)
public interface ContratRepo extends JpaRepository<Contrat,Long> {
#Query(query="from Contrat c left join fetch c.project " +
"where c.userApp = :userApp")
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}

Categories