Spring Boot JPA Query modify dynamically - spring

Using Spring boot,I am working on one business use case where i need to modify the JPA query generated at runtime based on configuration.
For Example .. if query that JPA generates is
select * from customers where id=1234
I want to modify it in runtime like based on user's logged in context. (Context has one attribute business unit) like given below ..
select * from customers where id=1234 and ***business_unit='BU001'***
Due to certain business use case restrictions i can't have statically typed query.
Using Spring boot and Postgres SQL.

Try JPA criteria builder , it let you to create dynamics query programmatically.
Take look in this post

What is stopping you to extract the business unit from the context and pass it to the query?
If you have this Entity
#Entity
CustomerEntity {
Long id;
String businessUnit;
//geters + setters
}
you can add this query to your JPA Repository interface:
CustomerEntity findByIdAndBusinessUnit(Long id, String businessUnit)
This will generate the following "where" clause:
… where x.id=?1 and x.businessUnit=?2
for complete documentation check Spring Data Jpa Query creation guide.

you would do something like this, this lets you dynamically define additional predicates you need in your query. if you don't want to have all the conditions in your query with #Query
The below example just adds a single predicate.
import java.util.ArrayList;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import study.spring.data.jpa.models.TicketPrice;
#Component
public class TricketPriceCriteriaRepository {
#Autowired
TicketPriceJpaRepository ticketPriceJpaRepository;
public List<TicketPrice> findByCriteria(int price) {
return ticketPriceJpaRepository.findAll(new Specification<TicketPrice>() {
#Override
public Predicate toPredicate(Root<TicketPrice> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (price > 0) {
predicates.add(
criteriaBuilder.and(criteriaBuilder.greaterThan(root.get("basePrice"), price)));
}
// Add other predicates here based on your inputs
// Your session based predicate
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}
}
Your base repository would be like
// Other imports
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TicketPriceJpaRepository
extends JpaRepository<TicketPrice, Long>, JpaSpecificationExecutor<TicketPrice> {}
the model consists basePrice
#Column(name = "base_price")
private BigDecimal basePrice;

Related

Why is Spring Cassandra not using the Values set in #Table with Scala?

I have the following code...
#Table(keyspace = "ks", name="otherThing" )
class Thing extends Serializable{
...
}
However when I run...
repo.findAll()
I get an error that looks like it isn't using the values I provided...
Query; CQL [SELECT * FROM Thing;]; unconfigured table Thing
I would expect
Select * from ks.otherThing;
What am I missing?
Update
I tried converting to the following Pojo
import com.datastax.driver.mapping.annotations.Table;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
#Table( keyspace="ks", name="otherThing" )
public class Thing implements Serializable {
...
}
And my repo is pretty simple...
import org.springframework.stereotype.Repository;
#Repository
public interface ThingRepository extends CassandraRepository<Thing, ThingId> { }
but
thingRepo.findAll();
gives...
Query; CQL [SELECT * FROM thing;]; unconfigured table thing
So the mistake I made was trying to do this with a connection that was set up using my company's spring autoconfig. This allowed me to configure the connection via application.properties. I noticed that the keyspace was previously declared there so I went back and changed my domain object to...
import org.springframework.data.cassandra.core.mapping.Table
#Table("otherThing" )
class Thing extends Serializable{
...
}
Now it is working. Leaving this answer up in case others get confused.

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.

Getting aggregate data from table

I want to get aggregate data from a table using spring data.
#Query("SELECT COUNT(*) AS TOTAL_1, MAX(FIELD_1) AS MAX_1 FROM TABLE_NAME WHERE GROUP_ID = :groupId")
Mono<SummaryEntity> getSummary(#Param("groupId" Long groupId));
package com.refinitiv.eit.kv.label.enity.response;
import lombok.AllArgsConstructor;
import lombok.Data;
#Data
#AllArgsConstructor
public class SummaryResponse {
#Column("TOTAL_1")
private Double total_1;
#Column("MAX_1")
private Double max_1;
}
However I get this error : "Could not read property #org.springframework.data.annotation.Id() " ...
There should be no ID, only a single row with the summary data.
Any ideas on getting the summary data?
(the code is more complex but cleared up for this)
First of all, if you need your entity SummaryResponse to be managed by JPA and eventually persist it, you need to annotate it as #Entity and assign it either id or composite id (annotated with #Id).
If you just want to use that DTO for fetching the data, you can use a Spring's interface based projection for that:
public interface SummaryResponseProjection{
getTotal1();
getMax1();
}
and then use it for mapping the results of the query:
#Query("SELECT COUNT(*) AS TOTAL_1, MAX(FIELD_1) AS MAX_1 FROM TABLE_NAME WHERE GROUP_ID = :groupId")
Mono<SummaryResponseProjection> getSummary(#Param("groupId" Long groupId));
Found the reason:
This method was part of a repository defined as ReactiveCrudRepository<RawEntity, Long>, with RawEntity having the id defined.
Moving the method into a new repo defined as ReactiveCrudRepository<SummaryEntity, Void> solves the issue.
Thanks all!

#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";
}

Dynamic Queries in Spring Data JPA

I am looking for a solution to dynamically build queries using Spring Data JPA. I have a GameController which has a RESTful service endpoint /games which takes 4 optional parameters: genre, platform, year, title. The API may be passed none of those, all 4, and every combination in between. If any parameter is not passed it defaults to null. I need a method in the Repository that will build the appropriate query and ideally also still allow Spring Data JPA Paging, although I'm not sure if that is possible.
I found this article but this doesn't seem to be what I need unless I am misunderstanding. http://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
I know JPA has a Query Criteria API but really have no idea how to implement this.
I realize I could create a method for each possible scenario but that seems like really bad practice and a lot of unnecessary code.
GameRepository:
package net.jkratz.igdb.repository;
import net.jkratz.igdb.model.Game;
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;
public interface GameRepository extends JpaRepository<Game, Long> {
#Query("select g from Game g, GamePlatformMap gpm, Platform p where g = gpm.game and gpm.platform = p and p.id = :platform")
Page<Game> getGamesByPlatform(#Param("platform") Long platformId, Pageable pageable);
#Query("select g from Game g where g.title like :title")
Page<Game> getGamesByTitle(#Param("title") String title, Pageable pageable);
#Query("select g from Game g, GameGenreMap ggm, Genre ge where g = ggm.game and ggm.genre = ge and ge.id = :genreId")
Page<Game> getGamesByGenre(#Param("genre") Long genreId, Pageable pageable);
}
I would say that using QueryDSL is one way of doing what you want.
For example I have a repository defined as below:
public interface UserRepository extends PagingAndSortingRepository<User, Long>, QueryDslPredicateExecutor<User> {
public Page<User> findAll(Predicate predicate, Pageable p);
}
I can call this method with any combination of parameters, like below:
public class UserRepositoryTest{
#Autowired
private UserRepository userRepository;
#Test
public void testFindByGender() {
List<User> users = userRepository.findAll(QUser.user.gender.eq(Gender.M));
Assert.assertEquals(4, users.size());
users = userRepository.findAll(QUser.user.gender.eq(Gender.F));
Assert.assertEquals(2, users.size());
}
#Test
public void testFindByCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Edinburgh"));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Stirling"));
Assert.assertEquals(1, users.size());
}
#Test
public void testFindByGenderAndCity() {
List<User> users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.M)));
Assert.assertEquals(2, users.size());
users = userRepository.findAll(QUser.user.address.town.eq("Glasgow").and(QUser.user.gender.eq(Gender.F)));
Assert.assertEquals(1, users.size());
}
}
For those using Kotlin (and Spring Data JPA), we've just open-sourced a Kotlin JPA Specification DSL library which lets you create type-safe dynamic queries for a JPA Repository.
It uses Spring Data's JpaSpecificationExecutor (i.e. JPA criteria queries), but without the need for any boilerplate or generated metamodel.
The readme has more details on how it works internally, but here's the relevant code examples for a quick intro.
import au.com.console.jpaspecificationsdsl.* // 1. Import Kotlin magic
////
// 2. Declare JPA Entities
#Entity
data class TvShow(
#Id
#GeneratedValue
val id: Int = 0,
val name: String = "",
val synopsis: String = "",
val availableOnNetflix: Boolean = false,
val releaseDate: String? = null,
#OneToMany(cascade = arrayOf(javax.persistence.CascadeType.ALL))
val starRatings: Set<StarRating> = emptySet())
#Entity
data class StarRating(
#Id
#GeneratedValue
val id: Int = 0,
val stars: Int = 0)
////
// 3. Declare JPA Repository with JpaSpecificationExecutor
#Repository
interface TvShowRepository : CrudRepository<TvShow, Int>, JpaSpecificationExecutor<TvShow>
////
// 4. Kotlin Properties are now usable to create fluent specifications
#Service
class MyService #Inject constructor(val tvShowRepo: TvShowRepository) {
fun findShowsReleasedIn2010NotOnNetflix(): List<TvShow> {
return tvShowRepo.findAll(TvShow::availableOnNetflix.isFalse() and TvShow::releaseDate.equal("2010"))
}
/* Fall back to spring API with some extra helpers for more complex join queries */
fun findShowsWithComplexQuery(): List<TvShow> {
return tvShowRepo.findAll(where { equal(it.join(TvShow::starRatings).get(StarRating::stars), 2) })
}
}
For more complex and dynamic queries it's good practice to create functions that use the DSL to make queries more readable (as you would for QueryDSL), and to allow for their composition in complex dynamic queries.
fun hasName(name: String?): Specifications<TvShow>? = name?.let {
TvShow::name.equal(it)
}
fun availableOnNetflix(available: Boolean?): Specifications<TvShow>? = available?.let {
TvShow::availableOnNetflix.equal(it)
}
fun hasKeywordIn(keywords: List<String>?): Specifications<TvShow>? = keywords?.let {
or(keywords.map { hasKeyword(it) })
}
fun hasKeyword(keyword: String?): Specifications<TvShow>? = keyword?.let {
TvShow::synopsis.like("%$keyword%")
}
These functions can be combined with and() and or() for complex nested queries:
val shows = tvShowRepo.findAll(
or(
and(
availableOnNetflix(false),
hasKeywordIn(listOf("Jimmy"))
),
and(
availableOnNetflix(true),
or(
hasKeyword("killer"),
hasKeyword("monster")
)
)
)
)
Or they can be combined with a service-layer query DTO and mapping extension function
/**
* A TV show query DTO - typically used at the service layer.
*/
data class TvShowQuery(
val name: String? = null,
val availableOnNetflix: Boolean? = null,
val keywords: List<String> = listOf()
)
/**
* A single TvShowQuery is equivalent to an AND of all supplied criteria.
* Note: any criteria that is null will be ignored (not included in the query).
*/
fun TvShowQuery.toSpecification(): Specifications<TvShow> = and(
hasName(name),
availableOnNetflix(availableOnNetflix),
hasKeywordIn(keywords)
)
for powerful dynamic queries:
val query = TvShowQuery(availableOnNetflix = false, keywords = listOf("Rick", "Jimmy"))
val shows = tvShowRepo.findAll(query.toSpecification())
JpaSpecificationExecutor supports paging, so you can achieve pageable, type-safe, dynamic queries!
I have got a solution for this. I wrote some code to extend the spring-data-jpa .
I call it spring-data-jpa-extra
spring-data-jpa-extra comes to solve three problem:
dynamic native query support like mybatis
return type can be anything
no code, just sql
You can try it : )

Resources