Equivalent of criteria in spring-data-jpa - spring

I was using hibernate but i heard that spring-data-jpa is the best so i try it and i'm satisfy with it until i face this issue.
I have a search form in my jsp with many criteria and the user can choose whatever he want.
So what is the equivalent of this request in spring-data-jpa
if(startDate!=null){
criteria.add(Expression.ge("date",startDate));
}
if(endDate!=null){
criteria.add(Expression.le("date",endDate));
}
if(volume!=null){
criteria.add(Expression.ge("volume",volume));
}
if ....

It's QueryDSL. Here's a blog post on how to use it with Spring Data.

the Equivalent in Spring Jpa Data is Specification, and you can use the repository SpecificationExecutor<T> and Jpa MetaModel to create your Jpa Criteria.
You can find an introduction about Jpa JpaSpecificationExecutor
A quick example :
Entity
#Entity
public class ClassRoom {
// id and other properties
#ManyToOne
private School school;
private Date creationDate;
private String reference;
// Getters and setters
}
2.Repository:
#Repository
public interface ClassRoomRepository extends JpaSpecificationExecutor<ClassRoom>{
}
2.Service interface:
public interface ClassRoomService {
List<ClassRoom> list(String reference, String schoolName,
Date creationDate)
}
3.Service Implementaion:
import static yourpackage.ClassRoomSpecifications.*;
import static org.springframework.data.jpa.domain.Specifications.*;
#Service
public class ClassRoomServiceImpl implements ClassRoomService {
#Resource
private ClassRoomRepository repository;
#Override
#Transactional(propagation = Propagation.SUPPORTS)
public List<ClassRoom> list(String reference, String schoolName,
Date creationDate) {
Specifications<ClassRoom> spec = null;
Specifications<ClassRoom> tempo = null;
spec = where(findPerSchool(schoolName));
if (reference != null) {
tempo = where(findPerReference(reference));
}
if (creationDate!=null) {
tempo = tempo == null ? where(findPerCreationDate(creationDate):tempo.and(findPerCreationDate(creationDate));
}
spec = tempo == null ? spec : spec.and(tempo);
return repository.findAll(spec);
}
}

With Spring data you just need to use repositories.
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Between findByStartDateBetween … where x.startDate between 1? and ?2
LessThan findByAgeLessThan … where x.age < ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
OrderBy findByAgeOrderByLastnameDesc … where x.age > ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1

Related

JpaSpecificationExecutor#findAll() doesn't anymore apply filter after updating Spring Boot 2.7.4 to 3.0.2

I have a controller that passes a filter with parameters for a search, I call my repository that extends the JpaSpecificationExecutor interface but when calling the findAll method passing the filter parameters it returns all records without filtering. Below are the codes for my controller, repository, specs class, and model class.
Controller
#GetMapping("/{codigoPedido}")
public PedidoModel buscar(#PathVariable String codigoPedido) {
Pedido pedido = emissaoPedido.buscarOuFalhar(codigoPedido);
return pedidoModelAssembler.toModel(pedido);
}
Repository
#Repository
public interface PedidoRepository extends CustomJpaRepository<Pedido, Long>,
JpaSpecificationExecutor<Pedido> {
Optional<Pedido> findByCodigo(String codigo);
#Query("from Pedido p join fetch p.cliente join fetch p.restaurante r join fetch r.cozinha")
List<Pedido> findAll(Specification<Pedido> spec);
}
Spec
public class PedidoSpecs {
public Specification<Pedido> usandoFiltro(PedidoFilter filtro) {
return (root, query, builder) -> {
root.fetch("restaurante").fetch("cozinha");
root.fetch("cliente");
var predicates = new ArrayList<Predicate>();
if (filtro.getClienteId() != null) {
predicates.add(builder.equal(root.get("cliente"), filtro.getClienteId()));
}
if (filtro.getRestauranteId() != null) {
predicates.add(builder.equal(root.get("restaurante"), filtro.getRestauranteId()));
}
if (filtro.getDataCriacaoInicio() != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get("dataCriacao"),
filtro.getDataCriacaoInicio()));
}
if (filtro.getDataCriacaoFim() != null) {
predicates.add(builder.lessThanOrEqualTo(root.get("dataCriacao"),
filtro.getDataCriacaoFim()));
}
return builder.and(predicates.toArray(new Predicate[0]));
};
}
}
Filter
#Setter
#Getter
public class PedidoFilter {
private Long clienteId;
private Long restauranteId;
#DateTimeFormat(iso = ISO.DATE_TIME)
private OffsetDateTime dataCriacaoInicio;
#DateTimeFormat(iso = ISO.DATE_TIME)
private OffsetDateTime dataCriacaoFim;
}
Request
GET- /pedidos?clienteId=1&restauranteId=1
Answer
all records without filter.
with this same implementation, it worked on Spring Boot 2.7.4 and Javax.
where can i make it work?
Cody repository:
https://github.com/raderleao/fastfood-api.git

spring data neo4j crud - many optional param?

I use Spring-data-neo4j with one CrudRepository
#Repository
public interface PersonRepository extends GraphRepository<Person> {}
I have a Html form with 3 inputs FirstName, Name, Age, so I have possible a multiple criteria choose : All, FirstName, FirstName + Name, FirstName + Age etc....
I would like to make a "multiple criteria find" with Map or other stuff. Is it possible?
I try this in my CRUD:
List<Person> findByFirstnameAndNameAndAge(String firstname, String name, int age);
but it's not work if one or all parameters is null.
Try to use a map and a #Query annotation
#Query("MATCH (u:Person) WHERE u.name = {param}.name OR u.age = {param}.age RETURN u")
List<Person> findDynamic(#Param("param") Map params);
Hi #Michael Hunger Thank you for your response. It's not exactly what I expected but you delivered me some fine search stuff
finally I do this :
import org.apache.commons.collections.map.HashedMap;
import com.google.common.collect.Lists;
(...)
#Autowired
private EventRepository eventRepository; //#Repository extends GraphRepository<Event>
(...)
public List<Event> findByDynamicParam(HashedMap params) {
String query = "match (event)-[:user]-(user), (event)-[:action]-(action)";
if (!params.isEmpty()) {
query += " where";
}
if (params.containsKey("actionId")) {
query += " id(action) = {actionId} and";
}
if (params.containsKey("userId")) {
query += " id(user) = {userId} and";
}
if (!params.isEmpty()) {
query = query.substring(0, query.length() - 4);
}
query += " return (event)";
return Lists.newArrayList(eventRepository.query(query, params));
}
client's caller :
HashedMap params = new HashedMap();
if (actionId != null) {
params.put("actionId", actionId);
}
if (userId != null) {
params.put("actionId", userId);
}
List<Event> events = eventService.findByDynamicParam(params);
What do you think? Is it possible to optimize this function?
Regards
Olivier from Paris

Spring Data JPA map the native query result to Non-Entity POJO

I have a Spring Data repository method with a native query
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
and I'd like to map the result to Non-Entity POJO GroupDetails.
Is it possible and if so, could you please provide an example ?
I think the easiest way to do that is to use so called projection. It can map query results to interfaces. Using SqlResultSetMapping is inconvienient and makes your code ugly :).
An example right from spring data JPA source code:
public interface UserRepository extends JpaRepository<User, Integer> {
#Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
public static interface NameOnly {
String getFirstname();
String getLastname();
}
}
You can also use this method to get a list of projections.
Check out this spring data JPA docs entry for more info about projections.
Note 1:
Remember to have your User entity defined as normal - the fields from projected interface must match fields in this entity. Otherwise field mapping might be broken (getFirstname() might return value of last name et cetera).
Note 2:
If you use SELECT table.column ... notation always define aliases matching names from entity. For example this code won't work properly (projection will return nulls for each getter):
#Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
But this works fine:
#Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);
In case of more complex queries I'd rather use JdbcTemplate with custom repository instead.
Assuming GroupDetails as in orid's answer have you tried JPA 2.1 #ConstructorResult?
#SqlResultSetMapping(
name="groupDetailsMapping",
classes={
#ConstructorResult(
targetClass=GroupDetails.class,
columns={
#ColumnResult(name="GROUP_ID"),
#ColumnResult(name="USER_ID")
}
)
}
)
#NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")
and use following in repository interface:
GroupDetails getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
According to Spring Data JPA documentation, spring will first try to find named query matching your method name - so by using #NamedNativeQuery, #SqlResultSetMapping and #ConstructorResult you should be able to achieve that behaviour
I think Michal's approach is better. But, there is one more way to get the result out of the native query.
#Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(#Param("userId") Integer userId, #Param("groupId") Integer groupId);
Now, you can convert this 2D string array into your desired entity.
You can write your native or non-native query the way you want, and you can wrap JPQL query results with instances of custom result classes.
Create a DTO with the same names of columns returned in query and create an all argument constructor with same sequence and names as returned by the query.
Then use following way to query the database.
#Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")
Create DTO:
package example;
public class CountryAndCapital {
public String countryName;
public String capitalName;
public CountryAndCapital(String countryName, String capitalName) {
this.countryName = countryName;
this.capitalName = capitalName;
}
}
This is my solution for converting to Map and then to custom Object
private ObjectMapper objectMapper;
public static List<Map<String, Object>> convertTuplesToMap(List<?> tuples) {
List<Map<String, Object>> result = new ArrayList<>();
tuples.forEach(object->{
if(object instanceof Tuple single) {
Map<String, Object> tempMap = new HashMap<>();
for (TupleElement<?> key : single.getElements()) {
tempMap.put(key.getAlias(), single.get(key));
}
result.add(tempMap);
}else{
throw new RuntimeException("Query should return instance of Tuple");
}
});
return result;
}
public <T> List<T> parseResult(List<?> list, Class<T> clz){
List<T> result = new ArrayList<>();
convertTuplesToMap(list).forEach(map->{
result.add(objectMapper.convertValue(map, clz));
});
return result;
}
public static class CustomDTO{
private String param1;
private Integer param2;
private OffsetDateTime param3;
}
public List<CustomDTO> doSomeQuery(){
Query query = entityManager.createNativeQuery("SELECT param1, param2 param3 ... ", Tuple.class);
return parseResult(query.getResultList(), CustomDTO.class);
}
Use the default method in the interface and get the EntityManager to get the opportunity to set the ResultTransformer, then you can return the pure POJO, like this:
final String sql = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = ? WHERE g.group_id = ?";
default GroupDetails getGroupDetails(Integer userId, Integer groupId) {
return BaseRepository.getInstance().uniqueResult(sql, GroupDetails.class, userId, groupId);
}
And the BaseRepository.java is like this:
#PersistenceContext
public EntityManager em;
public <T> T uniqueResult(String sql, Class<T> dto, Object... params) {
Session session = em.unwrap(Session.class);
NativeQuery q = session.createSQLQuery(sql);
if(params!=null){
for(int i=0,len=params.length;i<len;i++){
Object param=params[i];
q.setParameter(i+1, param);
}
}
q.setResultTransformer(Transformers.aliasToBean(dto));
return (T) q.uniqueResult();
}
This solution does not impact any other methods in repository interface file.
USE JPA PROJECTIONS
In your case it may be desirable to retrieve data as objects of customized types. These types reflect partial views of the root class, containing only properties we care about. This is where projections come in handy.
first declare Entity as #immutable
#Entity
#Immutable
public class Address {
#Id
private Long id;
set your Repository
public interface AddressView {
String getZipCode();
}
Then use it in a repository interface:
public interface AddressRepository extends Repository<Address, Long> {
#Query("EXEC SP_GETCODE ?1")
List<AddressView> getAddressByState(String state);
}
If you are looking for running a custom SQL query in spring boot with #repository and #service structures. Please have a look.
https://stackoverflow.com/a/71501509/4735043
You can do something like
#NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,
query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName,
cat.issueCategory, idc.issueDescriptor, idc.description)
from Department dep
inner join dep.issues iss
inner join iss.category cat
inner join cat.issueDescriptor idc
where idc.id in(?1)")
And there must be Constructor like
public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
String description) {
super();
this.id = id;
this.department = department;
this.issueName = issueName;
this.issueCategory = issueCategory;
this.issueDescriptor = issueDescriptor;
this.description = description;
}

Is it possible to avoid typecast on custom #Query?

Imagine that we have an entity:
#Entity
public class Person implements Serializable {
#Id
private String name;
private Long age;
private Boolean isMad;
...
}
And a repository with a trivial (and unnecessary) example for a custom query:
#Repository
public interface PersonRepository extends PagingAndSortingRepository<Info, String> {
#Query("select p.isMad, count(*) from Person p group by p.isMad")
List<Object> aggregateByMadness();
}
Now to parse this List we need to do something like this:
for (Object element : list) {
Object[] result = (Object[]) element;
Boolean isMad = (Boolean) result[0];
Long count = (Long) result[1];
}
which is a pain, can we cast the result of the query directly to List of a POJO?
Yes, you could use the JPQL construction expression:
package com.foo;
public class Madness {
public Madness(boolean isMad, Number count) { /* ...*/ }
}
And in your repository:
#Query("select new com.foo.Madness(p.isMad, count(*)) from Person p group by p.isMad")
List<Madness> aggregateByMadness();

Spring Hibernate - Dao Return By Id

I use Spring & Hibernate and i would like to get a product with his id in my DAO.
#Repository
#Transactional
public class ProductDaoImpl implements ProductDao {
protected final Log logger = LogFactory.getLog(getClass());
#Autowired
private SessionFactory sessionFactory;
public List<Product> getProductList() {
return sessionFactory.getCurrentSession().createQuery("from Product p order by p.productName asc").list();
}
public Product getProductById(int productId) {
String hql = "from Product p where p.productId = :id";
Query query = sessionFactory.getCurrentSession().createQuery(hql);
query.setInteger("id", productId);
return null;
}
}
For example when i would like to get all my products i return list of them (calling function getProductList() ), but now i want to call getProductById but i don't know how i could return something with the "Product" type.
Thanks.
In your getProductById(int productId) method:
return (Product) query.uniqueResult();

Resources