This is how my entity looks like:
#Entity
public class Registration {
#Id
#GeneratedValue
private Integer id;
#org.springframework.format.annotation.DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate date;
}
This is how my repo could look like:
#Query(value = "SELECT * FROM registration WHERE MONTH(date) = ?1 AND YEAR(date) = ?2")
List<Registration> findAll(Integer month, Integer year);
And this will be service:
public List<Registration> getCurrentRegistration() {
LocalDate today = LocalDate.now();
return registrationRepository.findAll(today.getMonth().getValue(), today.getYear());
}
public List<Registration> getRegistrations(Integer month, Integer year) {
return registrationRepository.findAll(month, year);
}
How can I change my native query to be JPA query?
Will the JPA query able to work on postgresql and hsqldb?
And why JPA queries are the best for spring apps? (or why they are not)
Make a specification class and write the below specification method in it.
import javax.persistence.criteria.Predicate;
import org.springframework.data.jpa.domain.Specification;
public class RegistrationSpecification {
public static Specification<Registration > registrationSpecForDate(
LocalDate invoiceDate ) {
return (root, cq, cb) -> {
List<Predicate> predicates = new ArrayList<Predicate>();
if (invoiceDate!=(null)) {
predicates.add(cb.greaterThanOrEqualTo(root.get("date"),
invoiceDate));
}
return cb.and(predicates.toArray(new Predicate[0]));
};
}
Then in your repository inject this specification in JPA's findAll() method.
`public List<Registration> getRegistrations(LocalDate date) {
return
registrationRepository.findAll
(RegistrationSpecification.registrationSpecForDate(date));
`
You could do this using QueryDSL JPA (https://github.com/querydsl/querydsl/tree/master/querydsl-jpa) to define a predicate:
Predicate createPredicate(Integer month, Integer year) {
return QRegistration.date.year().eq(year).and(QRegistration.date.month().eq(month));
}
Then make your repo extend QueryDslPredicateExecutor:
public interface RegistrationRepository extends JpaRepository<Registration>, QueryDslPredicateExecutor {
// Your query methods here
}
This contains a List<T> findAll(Predicate predicate) method which you can pass your predicate in to obtain the items you were after, e.g.:
registrationRepository.findAll(createPredicate(1, 1970));
See here for more info about using QueryDSL with Spring: https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
Related
My system required to add filters,and I'm wonder if there any query that like this
SELECT *
FROM posts p
when byDate is not null then (where p.createAt BETWEEN :startDate AND :endDate)
when byType is not null then (where p.type = :type)
I knew that the query is not valid, but I want at one query to get the data wherever the request has (no filter or all filters or some of filters).
My goal is to create one query to achieve all cases.
It's usually not a good idea to write a big SQL query when you can tell in advance the actual query you want to run.
If you want to run a different query based on conditions you know before running the query, there are different approaches in JPA or Spring that you can use
Spring
You can define the different queries using Spring Data query methods?
public class PostRepository implements JpaRepository<Post, Long> {
List<Post> findByCreatedAtBetween(Date startDate, Date endDate);
List<Post> findByTypeIs(String type);
}
And then somewhere in the code, you can:
List<Post> results = null;
if (byDate != null) {
results = repository.findByCreatedAtBetween(startDate, endDate);
} else if (byType != null) {
results = repository.findByTypeIs(type);
} else {
results = repository.findAll();
}
Criteria
With criteria you can create a dynamic query at runtime and execute it:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(Post.class);
Root<User> user = query.from(Post.class);
if ((filter.getByDate() != null)) {
// byDate is not null
ParameterExpression<Date> startDate = builder.parameter( Date.class );
ParameterExpression<Date> endDate = builder.parameter( Date.class );
query.where(builder.between( b.get( "createdAt" ), startDate, endDate));
return em.createQuery(query)
.setParameter(startDate, ...)
.setParameter(endDate, ...)
.getResultList();
}
if (filter.getByType() != null) {
ParameterExpression<Date> typeParam = builder.parameter( Date.class );
query.where(builder.and(root.get("type"), typeParam));
return em.createQuery(query)
.setParameter(typeParam, ...)
.getResultList();
}
return entityManager.createQuery(query)
.getResultList();
}
}
Assuming that your entity has the fields type and createdAt.
This approach works well if you don't know in advance what's your query looks like. For example, when you don't know how many conditions you will have to add to it.
But, if I know already which query I want to run, then I prefer to use HQL/JPQL.
HQL
If your queries don't change and you already know what they look like,
I find it easier to define them with HQL:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
if (filter.getByDate() != null) {
return em.createQuery("from Post p where p.createdAt between :startDate and :endDate", Post.class)
.setParameter("startDate", ...)
.setParameter("endDate", ...)
.getResultList();
}
if (filter.getByType() != null) {
return em.createQuery("from Post p where p.type =:type", Post.class)
.setParameter("type", ...)
.getResultList();
}
return em.createQuery("from Post", Post.class)
.getResultList();
}
}
You can refactor the code to make it more elegant, but it should give you an idea. Note that if you need to reuse the same queries in different services, it might be helpful to define them using the annotation #NamedQuery.
Filters
In Hibernate (not JPA) you can also define filters. They are SQL filter conditions that one can apply at runtime:
#Entity
#FilterDef(name = Post.BY_DATE, defaultCondition = "createdAt between :startDate and :endDate", parameters = {#ParamDef(name = "startDate", type = "date"), #ParamDef(name = "startDate", type = "date") })
#FilterDef(name = Post.BY_TYPE, defaultCondition = "type = :type", parameters = #ParamDef(name = "startDate", type = "date"))
#Filter(name = Post.BY_DATE)
#Filter(name = Post.BY_TYPE)
class Post {
static final String BY_DATE = "Post.byDateFilter";
static final String BY_TYPE = "Post.byFilter"
private String type;
private Date createdAt;
...
}
Then:
public class PostRepository implements PostRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public List<Post> findPosts(Filter filter) {
enableFilters(em);
return em.createQuery("from Post", Post.class).getResultList();
}
private void enableFilters(Filter filter, EntityManager em) {
if (filter.getByDate() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_DATE )
.setParameter("startDate", ...)
.setParameter("endDate", ...);
} else if (filter.getByType() != null) {
em.unwrap(Session.class)
.enableFilter( Post.BY_TYPE )
.setParameter("type", ...);
}
}
}
In below query I wanted to group by just on date not time that's why I used TO_DATE function
select TO_DATE(e.created_dt, 'dd-mm-yy'),sum(CURRENT_BAL) from sbill.act_resource_t e group by TO_DATE(e.created_dt, 'dd-mm-yy');
so as of now its working fine with oracle but as per our business requirements application should support both oracle and mysql without write two different queries;
so do we have any solution for that which should works with both oracle and mysql ?
Note :- I am using hql
Below code :
Query query1 = entityManager.createQuery("select TO_DATE(e.createdDt, 'dd-mm-yy'),sum(CURRENT_BAL) from ActT e group by TO_DATE(e.createdDt, 'dd-mm-yy')");
List<Object> result=query1.getResultList();
Since the functions differ between the databases, we need a dynamic query. But we can do it efficiently by creating a custom conversion method, which will select the right implementation based on the current database type. And the query itself will remain common (The solution uses FluentJPA library):
public static final FormatModel DD_MM_YY = Format.dateModel(Format.DD, Format.MM, Format.YY);
public static boolean isOracle() {
return false; //should return the actual value in runtime
}
#Local
// picks the right implementation
public static Function1<String, Date> AS_DATE() {
if (isOracle())
return s -> TO_DATE(s, DD_MM_YY); //oracle
return s -> STR_TO_DATE(s, "%d-%m-%y"); // mysql
}
Now we can write a generic implementation:
#Entity
#Table(name = "act_resource_t", schema = "sbill")
#Data
public static class ActResource {
#Id
private int id;
private int currentBAL;
private String createdDT;
}
// for the result
#Tuple
#Data
public static class BalanceByDate {
private Date date;
private int balance;
}
...
public BalanceByDate balanceByDate() {
FluentQuery query = FluentJPA.SQL((ActResource e) -> {
// with lambda we inject the right implementation
Date createdDate = alias(AS_DATE().apply(e.getCreatedDT()), BalanceByDate::getDate);
Integer balance = alias(SUM(e.getCurrentBAL()), BalanceByDate::getBalance);
SELECT(createdDate, balance);
FROM(e);
GROUP(BY(createdDate));
});
return query.createQuery(em, BalanceByDate.class).getSingleResult();
}
This is the resulting SQL for MySQL:
SELECT STR_TO_DATE(t0.created_dt, '%d-%m-%y') AS date, SUM(t0.current_bal) AS balance
FROM sbill.act_resource_t t0
GROUP BY STR_TO_DATE(t0.created_dt, '%d-%m-%y')
I have a MongoDB collection with documents with the following fields:
date (Date obj)
offerType (str)
I want to write a method with MongoRepository to find all the documents that has date in a range of dates and offerType contains one of the string provided in a list.
Example
Documents:
date: 10-04-2019, offerType: offer1
date: 11-04-2019, offerType: offer3
date: 15-04-2019, offerType: offer2
date: 15-04-2019, offerType: offer1
I want:
dates between 9-04-2019 and 12-04-2019
following offers: offer1, offer3
In the previous example I would obtain documents 1 and 2.
My code
I use MongoRepository and a custom Object which contains the fields I require:
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ReportVocalOrderRepository extends MongoRepository<ReportVocalOrder, String> {
List<ReportVocalOrder> findByDateBetween(Date startDate, Date endDate, Pageable pageable);
List<ReportVocalOrder> findByDateBetweenAndOfferTypeContaining(Date startDate, Date endDate, List<String> offers, Pageable pageable);
}
Here is the document class:
#JsonInclude(Include.NON_NULL)
#Document(collection = Constants.Mongo.Collections.VOCAL_ORDER_REPORT)
#ApiModel
public class ReportVocalOrder {
#Id
private String id;
private Date date;
private String offerType;
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getOfferType() {
return offerType;
}
public void setOfferType(String offerType) {
this.offerType = offerType;
}
}
The first method of the MongoRepository works fine; the second one instead returns an empty list.
The problem is to query the mongoRepository to search a field that can contain one of the values of the list passed as an argument.
What's wrong with this implementation? There's a better way to implement this query?
It was simpler than I thought. I post an answer for whom is interested:
import java.util.Date;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface ReportVocalOrderRepository extends MongoRepository<ReportVocalOrder, String> {
List<ReportVocalOrder> findByDateBetweenAndOfferTypeIn(Date startDate, Date endDate, List<String> offers, Pageable pageable);
}
So the point is to use the keyword In.
We have another option to using Query
#Query(value = "{ 'groupId' : ?0 , 'user.$id' : {$in: ?1} }", delete = true)
List<GroupUser> deleteUserInGroup(String groupId, List<ObjectId> userId);
You can try it.
I am new to Spring boot and hibernate. Here I am trying run a search based optional parameter query Where i can search by name, country etc. If I kept this field null then query should all list. But the problem is my method is returning all data ignoring my search parameter. my model class look like
#Entity(name="MLFM_ORDER_OWNER")
public class ModelOrderOwner {
#Id #GenericGenerator(name = "custom_sequence", strategy =
"com.biziitech.mlfm.IdGenerator")
#GeneratedValue(generator = "custom_sequence")
#Column(name="ORDER_OWNER_ID")
private Long orderOwnerId;
#Column(name="OWNER_NAME")
private String ownerName;
#OneToOne
#JoinColumn(name="BUSINESS_TYPE_ID")
private ModelBusinessType businessTypeId;
#Column(name="SHORT_CODE")
private String shortCode;
#ManyToOne
#JoinColumn(name="OWNER_COUNTRY")
private ModelCountry ownerCountry;
// getter setter..
My Repository interface looks like
public interface OrderOwnerRepository extends
JpaRepository<ModelOrderOwner,Long>{
#Query("select a from MLFM_ORDER_OWNER a where a.businessTypeId.typeId=coalsec(:typeId,a.businessTypeId.typeId) and a.ownerCountry.countryId=coalsec(:countryId,a.ownerCountry.countryId) and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(#Param("typeId")Long typeId,#Param("countryId")Long countryId,#Param("name")String name,#Param("code")String code);
}
And here is my method in controller
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,#RequestParam("owner_name") String name,#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,#RequestParam("countryName") Long countryId,
#RequestParam("businessType") Long typeId
) {
model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code));
return "data_list";
}
Can Any one help me in this regard? please?
It is too late too answer, but for anyone who looks for a solution yet there is a more simple way as below:
In my case my controller was like:
#RestController
#RequestMapping("/order")
public class OrderController {
private final IOrderService service;
public OrderController(IOrderService service) {
this.service = service;
}
#RequestMapping(value = "/{username}/", method = RequestMethod.GET)
public ResponseEntity<ListResponse<UserOrdersResponse>> getUserOrders(
#RequestHeader Map<String, String> requestHeaders,
#RequestParam(required=false) Long id,
#RequestParam(required=false) Long flags,
#RequestParam(required=true) Long offset,
#RequestParam(required=true) Long length) {
// Return successful response
return new ResponseEntity<>(service.getUserOrders(requestDTO), HttpStatus.OK);
}
}
As you can see, I have Username as #PathVariable and length and offset which are my required parameters, but I accept id and flags for filtering search result, so they are my optional parameters and are not necessary for calling the REST service.
Now in my repository layer I have just created my #Query as below:
#Query("select new com.ada.bourse.wealth.services.models.response.UserOrdersResponse(FIELDS ARE DELETED TO BECOME MORE READABLE)" +
" from User u join Orders o on u.id = o.user.id where u.userName = :username" +
" and (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag)")
Page<UserOrdersResponse> findUsersOrders(String username, Long orderId, Long flag, Pageable page);
And that's it, you can see that I checked my optional arguments with (:orderId is null or o.id = :orderId) and (:flag is null or o.flags = :flag) and I think it needs to be emphasized that I checked my argument with is null condition not my columns data, so if client send Id and flags parameters for me I will filter the Result with them otherwise I just query with username which was my #PathVariable.
Don't know how but below code is working for me:
#Query("select a from MLFM_ORDER_OWNER a
where a.businessTypeId.typeId=COALESCE(:typeId,a.businessTypeId.typeId)
and a.ownerCountry.countryId=COALESCE(:countryId,a.ownerCountry.countryId)
and a.ownerName LIKE %:name and a.shortCode LIKE %:code")
public List <ModelOrderOwner> findOwnerDetails(
#Param("typeId")Long typeId,
#Param("countryId")Long countryId,
#Param("name")String name,
#Param("code")String code);
and in my controller class:
#RequestMapping(path="/owners/search")
public String getAllOwner(Model model,
#RequestParam("owner_name") String name,
#RequestParam("shortCode") String code,
#RequestParam("phoneNumber") String phoneNumber,
#RequestParam("countryName") Long countryId,
#RequestParam(value = "active", required = false) String active, #RequestParam("businessType") Long typeId) {
if(typeId==0)
typeId=null;
if(countryId==0)
countryId=null; model.addAttribute("ownerList",ownerRepository.findOwnerDetails(typeId, countryId, name, code, status));
return "data_list";
}
JPQL doesn't support optional parameters.
There is no easy way of doing this in JPQL. You will have to write multiple WHERE clauses with OR operator.
Refer these answers to similar questions: Answer 1 & Answer 2
PS: You might want to look into Query by Example for your use case. It supports handling of null parameters.
Use JpaSpecificationExecutor //import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
Step 1: Implement JpaSpecificationExecutor in your JPA Repository
Ex:
public interface TicketRepo extends JpaRepository<Ticket, Long>, JpaSpecificationExecutor<Ticket> {
Step 2 Now to fetch tickets based on optional parameters you can build Specification query using CriteriaBuilder
Ex:
public Specification<Ticket> getTicketQuery(Integer domainId, Calendar startDate, Calendar endDate, Integer gameId, Integer drawId) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get("domainId"), domainId));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get("createdAt"), startDate));
predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get("createdAt"), endDate));
if (gameId != null) {
predicates.add(criteriaBuilder.equal(root.get("gameId"), gameId));
}
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
Step 3: Pass the Specification instance to jpaRepo.findAll(specification), it will return you the list of your entity object (Tickets here in the running example)
ticketRepo.findAll(specification); // Pass output of function in step 2 to findAll
I have an entity with a java.util.Date field stored as a TemporalType.DATE. When passing a java.util.Date with hours, minutes or seconds to the where clause of a criteria query I can't seem to get a match from the database.
The setup is an embedded H2-database in Spring with Hibernate. I've tried using PostgreSQL instead of H2 and it works. I've also tried to set H2 in PostgreSQL-mode, but it doesn't change anything.
Given the entity
#Entity
public class SomeEntity {
#Id
private int id;
#Temporal(TemporalType.DATE)
private java.util.Date aDate;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Date getDate() {
return aDate;
}
public void setDate(Date aDate) {
this.aDate = aDate;
}
}
The following query only returns a match if the hours, minutes and seconds of the parameter have been set to 0.
public List<SomeEntity> someQueryOnDate(Date date) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<SomeEntity> query = cb.createQuery(SomeEntity.class);
Root<SomeEntity> root = query.from(SomeEntity.class);
Predicate dateEquals = cb.equal(root.get(SomeEntity_.date), date);
query.where(dateEquals);
// This list is always empty if the date in the predicate has a time part
return em.createQuery(query).getResultList();
}
A full example follows. The test fails on the last assertion, where I query the database using a Date with hours, minutes and seconds set.
#Test
public void testDateEquals() throws ParseException {
Date dateWithoutTime = new SimpleDateFormat("yyyy-MM-dd").parse("2014-07-03");
Date dateWithTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2014-07-03 09:45:01");
createEntity(dateWithoutTime);
List<SomeEntity> entitiesMatchingDateWithTime = listAllEntitiesWithDate(dateWithTime);
List<SomeEntity> entitiesMatchingDateWithoutTime = listAllEntitiesWithDate(dateWithoutTime);
Assert.assertFalse("No entities matched the date without time", entitiesMatchingDateWithoutTime.isEmpty());
Assert.assertFalse("No entities matched the date with time" , entitiesMatchingDateWithTime.isEmpty());
}
private void createEntity(Date d) {
SomeEntity entity = new SomeEntity();
entity.setDate(d);
em.persist(entity);
// For good measure
em.flush();
em.clear();
}
private List<SomeEntity> listAllEntitiesWithDate(Date date) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<SomeEntity> query = cb.createQuery(SomeEntity.class);
Root<SomeEntity> root = query.from(SomeEntity.class);
Predicate dateEquals = cb.equal(root.get(SomeEntity_.date), date);
query.where(dateEquals);
return em.createQuery(query).getResultList();
}
Maybe the solution is provided here:
Hibernate Criteria for Dates
You can use Restrictions to compare dates.