JPA Query Methods: findTopX where X is a given (variable) number? - java

I'm creating a RESTful API. I need to to be able to send a number to the API specifying the amount of news items it needs to return.
I already looked for some things and according to the documentation it is possible to limit your results, but I can't find anything about how to make it variable. So my question is: Is this even possible to do or do I need to write my own custom query for this.
I already tried things like:
Iterable<NewsItem> findTopAmountByOrderByDatetimeDesc(Integer amount); where Amount would be filled in by the given Integer "amount", but maybe it is stupid to even think this would be possible although it would be a nice feature in my opinion.
What I have now (not variable, so it doesn't care about the given number):
NewsItemApi:
#RequestMapping(value = "/newsitems/amount",
produces = { "application/json" },
method = RequestMethod.GET)
NewsItemRepository:
Iterable<NewsItem> findTop2ByOrderByDatetimeDesc();
NewsItemApiController:
public ResponseEntity<Iterable> getLastNewsItems(#NotNull #ApiParam(value = "amount of news items to return", required = true) #Valid #RequestParam(value = "amount", required = true) Integer amount) {
return ResponseEntity.accepted().body(this.newsItemRepository.findTop2ByOrderByDatetimeDesc());
}

You can create repository and use #Query annotation to make custom requests, something like this:
public interface NewsRepository extends JpaRepository<News, Long> {
#Query(value = "SELECT * FROM NEWS ODER BY FIELD_YOU_WANT_TO_ODER_BY DESC LIMIT ?1", nativeQuery = true)
List<News> getLatestNews(Integer amount);
}

You would have to use #Query, something like:
#Query("select n from NewsItem n order by n.datetime desc limit :num", nativeQuery = true)
Iterable<NewsItem> findTopXByOrderByDatetimeDesc(#Param("num") int num);
Of course use limit keyword according to your database.

Related

How to Fetch Custom Fields In Spring JPA

I want to group all similar hero names and sum the total of their 'killCount' relative to that name like:
--heroname-- --killCount--
Guson 999
Garen 934
Magnus 445
I have a Hero Entity which has these fields (but I dont intend to fetch all columns from SQL, I only need heroname and killCount)
Long heroid;
String heroname;
Integer killCount;
String heroClass;
String faction;
In my repository class I want to create a JPA query where I want to fetch rows where in I group them by 'heroname' column and the corresponding SUM/TOTAL of that hero's 'killCount'.
This blog by Baeldung suggest I create an interface. But the blog doesnt show how to actually use that in the app controller or service class. It just says create an interface.
Here's my current repository class:
public interface HeroRepository extends JpaRepository<Hero,Long>{
#Query( value = "SELECT h.heroname , SUM(h.killCount) FROM Heroes AS h GROUP BY h.heroname ORDER BY h.heroname",nativeQuery = true)
List<IHero> findAllHeroByGroupName();
}
Am I doing this right?
Service class:
public class HeroService {
#Autowired HeroRepository heroRepository;
public List<IHero> findAllHeroByGroupName() {
return heroRepository.findAllHeroByGroupName();
}
}
Interface
public interface IHero {
String getHeroName();
Integer getTotalKillCounts();
}
--UPDATE[SOLVED]--
When I do this
List<IHero> heroList = heroService.findAllHeroByGroupName();
and print via loop:
for(IHero hero: heroList){
System.out.println(hero.getHeroName);
System.out.println(hero.getTotalKillCounts);
}
hero.getHeroName contains correct value while hero.getTotalKillCounts contains NULL. Why is that? Im almost there.
ANSWER: You need to use alias equal to the field name in your Entity. So I used alias for SUM(killCount) to 'killCount' only. Bamm!
#Query( value = "SELECT h.heroname , SUM(h.killCount) as killCount FROM Heroes AS h GROUP BY h.heroname ORDER BY h.heroname",nativeQuery = true)
in the repository try to specify the parameter heroName and update the query like this by adding where clause:-
public interface HeroRepository extends JpaRepository<Hero,Long>{
#Query( value = "SELECT h.heroname , SUM(h.killCount) FROM Heroes AS h where h.heroname = ?1 GROUP BY h.heroname ORDER BY h.heroname",nativeQuery = true)
//code below will cause errors, I need to use interface says Baeldung
List<Hero> findAllHeroByGroupName(String heroName);
}

Provide limit on Spring Data Mongo repository

Using the latest Spring Data Mongo (2.1.1 at time of writing), how do I specify to get the first record of a "custom" query method? Here is an example:
#Query(value="{name: ?0, approval: {'$ne': null}}",
sort="{'approval.approvedDate': -1}",
fields = "{ _id: 1 }")
List<Item> getLatestApprovedIdByName(String name, Pageable pageable);
/**
* Finds the id of the most recently approved document with the given name.
*/
default Item getLatestApprovedIdByName(String name) {
return getLatestApprovedIdByName(name, PageRequest.of(0, 1)).stream()
.findFirst()
.orElse(null);
}
Ideally I could just annotate getLatestApprvedIdByName taking only the String parameter.
There doesn't seem to be a limit field on the org.springframework.data.mongodb.repository.Query annotation.
It seems odd because I can emulate everything the named methods do except findFirst.
Without the Pageable, I get IncorrectResultSizeDataAccessException, and returning a List is not acceptable because I don't want to waste time returning an arbitrarily large result, plus the complicated code needing to deal with the possibility of 0 or 1 items.
Because your query returns multiple documents, there's no way to make it return a single Item directly.
Using Stream
// Repository
#Query(value="{name: ?0, approval: {'$ne': null}}",
sort="{'approval.approvedDate': -1}",
fields = "{ _id: 1 }")
Stream<Item> getLatestApprovedIdByName(String name);
// Service
default Item getLatestApprovedIdByName(String name) {
return getLatestApprovedIdByName(name).stream().findFirst().orElse(null);
}
Due to the way Stream works, you'll only fetch the first query result instead of the entire result set. For more information, please see the documentation.
Using Page and Pageable
// Repository
#Query(value = "{name: ?0, approval: {'$ne': null}}", fields = "{ _id: 1 }")
Page<Item> getLatestApprovedIdByName(String name, Pageable pageable);
// Service
default Item getLatestApprovedIdByName(String name) {
PageRequest request = new PageRequest(0, 1, new Sort(Sort.Direction.DESC, "approval.approvedDate"));
return getLatestApprovedIdByName(name, request).getContent().get(0);
}
By making use of PageRequest, you can specify how many results you want as well as specify the sort order. Based on this answer.

Add optional query parameter using spring data mongodb repository

I want to add optional query parameters using spring data mongodb.
Controller code:
#RestController
private final ActionService actionService;
#RequestMapping(value = "/action/{id}", method = RequestMethod.GET)
public ResponseEntity<List<Action>> getActionList(#PathVariable("id") long id,
#RequestParam(value = "actionType", required = false) ActionType actionType,
#RequestParam(value = " ", required = false) String[] params) {
List<Action> actionList = actionService.getAction(id, actionType, params);
return new ResponseEntity<>(actionList, HttpStatus.OK);
}
ActionServiceImpl.java
private ActionRepository actionRepository;
public List<Action> getAction(long id, ActionType type, String... var1) {
return actionRepository.getByActionType(id, type, var1.length > 0 ? var1[0] : "", var1.length > 1 ? var1[1] : "", var1.length > 2 ? var1[2] : "");
}
ActionRepository.java
#Repository
public interface ActionRepository extends MongoRepository<Action, String> {
#Query(value = "{ 'id' : ?0 , 'actionType' : ?1 , 'param1' : ?2 , 'param2': ?3 , 'param3' : ?4 } ")
List<Action> getByActionType(long id, ActionType type, String var1, String var2, String var3);
}
Note: 'id' is mandatory field and action type and params are optional. I want to get data based on 'id' whether I pass action type/params or not. Currently, i am getting null pointer exception in 'ActionServiceImpl' as I am not passing params and action Type. 'Action Type' is enumeration.
Can someone help me to change ActionRepository #Query tag so, that I can get data based on id without passing actionType or params. e.g. if I pass action type then mongo query should give result based on 'id $or actionType'.
You cannot achieve this using #Query. Other possible alternatives are
Create two methods in Repository class. One which takes only id and other which takes id and other arguments. And in your service class, you can decide which one to call based on the data in hand. (Not Scalable)
Use QueryDsl. With this you can create search criteria based on data you have dynamically. Some helpful links
https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#core.extensions.querydsl
http://www.baeldung.com/queries-in-spring-data-mongodb
You can use Example. Here is the link for documentation.(This has some limitations)
In my personal experience using QueryDsl is the best way to tackle these cases and it can be easily extended for further changes in requirement.

PlayFramework 2.x Ebean query match manytomany property from a collection

I have a model object that looks like this:
#SuppressWarnings("serial")
#Entity
#Table(name = "selections")
public class Selection extends Model {
....
#ManyToMany
private Set<Market> markets;
....
}
Where Selection and Market both have id properties and static Finder<Long, *> find() methods.
And i'm trying to find all the Selection objects which contain a Market that's within a Set.
#Override #Transactional(readOnly = true) public List<Selection> findSelections(Set<Market> markets) {
// Query?
return Selection.find().where()...findList();
}
I know i can do something like:
return Selection.find().where().eq("markets.id", market.id).findList();
to find a single market object - but what about finding those objects from the Set? Without iterating over the Set?
return Selection.find().where().in("markets",markets).findList();
I have been struggling with the same problem, and after reading another SO question and the Ebean's Interface Query documentation I came out with this:
// Prepare the OQL query string
String oql = "find selection " +
"where markets.id in (:marketList) " +
"group by id " +
"having count(distinct markets.id) = :marketCount";
// Create the query
Query<Selection> query = Selection.find.setQuery(oql);
// Set the list of market IDs
List<Long> marketIds = Arrays.asList(1, 30, 9, 15, 6);
// Set query parameters (list of market IDs and how many they are)
query.setParameter("marketList", marketIds);
query.setParameter("marketCount", marketIds.size());
// Get the matching results
List<Selection> selections = query.findList();
I am using Play 2.4.2 with sbt-play-ebean plugin version 2.0.0, wich provides avaje-ebeanorm version 6.8.1.
For some extra functionality you might be interested in reading this question.

setMaxResults for Spring-Data-JPA annotation?

I am trying to incorporate Spring-Data-JPA into my project.
One thing that confuses me is how do I achieve setMaxResults(n) by annotation ?
for example, my code:
public interface UserRepository extends CrudRepository<User , Long>
{
#Query(value="From User u where u.otherObj = ?1 ")
public User findByOtherObj(OtherObj otherObj);
}
I only need to return one (and only one) User from otherObj, but I cannot find a way to annotate the maxResults. Can somebody give me a hint ?
(mysql complains :
com.mysql.jdbc.JDBC4PreparedStatement#5add5415: select user0_.id as id100_, user0_.created as created100_ from User user0_ where user0_.id=2 limit ** NOT SPECIFIED **
WARN util.JDBCExceptionReporter - SQL Error: 0, SQLState: 07001
ERROR util.JDBCExceptionReporter - No value specified for parameter 2
)
I found a link : https://jira.springsource.org/browse/DATAJPA-147,
I tried but failed. It seems not possible now?
Why is such an important feature not built into Spring-Data?
If I implement this feature manually:
public class UserRepositoryImpl implements UserRepository
I have to implement tons of predefined methods in CrudRepository, this would be terrible.
environments : spring-3.1 , spring-data-jpa-1.0.3.RELEASE.jar , spring-data-commons-core-1.1.0.RELEASE.jar
As of Spring Data JPA 1.7.0 (Evans release train).
You can use the newly introduced Top and First keywords that allow you to define query methods like this:
findTop10ByLastnameOrderByFirstnameAsc(String lastname);
Spring Data will automatically limit the results to the number you defined (defaulting to 1 if omitted). Note that the ordering of the results becomes relevant here (either through an OrderBy clause as seen in the example or by handing a Sort parameter into the method). Read more on that in the blog post covering new features of the Spring Data Evans release train or in the documentation.
For previous versions
To retrieve only slices of data, Spring Data uses the pagination abstraction which comes with a Pageable interface on the requesting side as well as a Page abstraction on the result side of things. So you could start with
public interface UserRepository extends Repository<User, Long> {
List<User> findByUsername(String username, Pageable pageable);
}
and use it like this:
Pageable topTen = new PageRequest(0, 10);
List<User> result = repository.findByUsername("Matthews", topTen);
If you need to know the context of the result (which page is it actually? is it the first one? how many are there in total?), use Page as return type:
public interface UserRepository extends Repository<User, Long> {
Page<User> findByUsername(String username, Pageable pageable);
}
The client code can then do something like this:
Pageable topTen = new PageRequest(0, 10);
Page<User> result = repository.findByUsername("Matthews", topTen);
Assert.assertThat(result.isFirstPage(), is(true));
Not that we will trigger a count projection of the actual query to be executed in case you use Page as return type as we need to find out how many elements there are in total to calculate the metadata. Beyond that, be sure you actually equip the PageRequest with sorting information to get stable results. Otherwise you might trigger the query twice and get different results even without the data having changed underneath.
If you are using Java 8 and Spring Data 1.7.0, you can use default methods if you want to combine a #Query annotation with setting maximum results:
public interface UserRepository extends PagingAndSortingRepository<User,Long> {
#Query("from User u where ...")
List<User> findAllUsersWhereFoo(#Param("foo") Foo foo, Pageable pageable);
default List<User> findTop10UsersWhereFoo(Foo foo) {
return findAllUsersWhereFoo(foo, new PageRequest(0,10));
}
}
There is a way you can provide the equivalent of "a setMaxResults(n) by annotation" like in the following:
public interface ISomething extends JpaRepository<XYZ, Long>
{
#Query("FROM XYZ a WHERE a.eventDateTime < :before ORDER BY a.eventDateTime DESC")
List<XYZ> findXYZRecords(#Param("before") Date before, Pageable pageable);
}
This should do the trick, when a pageable is sent as parameter.
For instance to fetch the first 10 records you need to set pageable to this value:
new PageRequest(0, 10)
Use Spring Data Evans (1.7.0 RELEASE)
the new release of Spring Data JPA with another list of modules together called Evans has the feature of using keywords Top20 and First to limit the query result,
so you could now write
List<User> findTop20ByLastname(String lastname, Sort sort);
or
List<User> findTop20ByLastnameOrderByIdDesc(String lastname);
or for a single result
List<User> findFirstByLastnameOrderByIdDesc(String lastname);
Best choice for me is native query:
#Query(value="SELECT * FROM users WHERE other_obj = ?1 LIMIT 1", nativeQuery = true)
User findByOhterObj(OtherObj otherObj);
new PageRequest(0,10) doesn't work in newer Spring versions (I am using 2.2.1.RELEASE). Basically, the constructor got an additional parameter as Sort type. Moreover, the constructor is protected so you have to either use one of its child classes or call its of static method:
PageRequest.of(0, 10, Sort.sort(User.class).by(User::getFirstName).ascending()))
You can also omit the use of Sort parameter and implicitly user the default sort (sort by pk, etc.):
PageRequest.of(0, 10)
Your function declaration should be something like this:
List<User> findByUsername(String username, Pageable pageable)
and the function will be:
userRepository.findByUsername("Abbas", PageRequest.of(0,10, Sort.sort(User.class).by(User::getLastName).ascending());
It's also posible using #QueryHints. Example bellow uses org.eclipse.persistence.config.QueryHints#JDBC_MAX_ROWS
#Query("SELECT u FROM User u WHERE .....")
#QueryHints(#QueryHint(name = JDBC_MAX_ROWS, value = "1"))
Voter findUser();
If your class #Repository extends JpaRepository you can use the example below.
int limited = 100;
Pageable pageable = new PageRequest(0,limited);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();
getContent return a List<Transaction>.
Use
Pageable pageable = PageRequest.of(0,1);
Page<Transaction> transactionsPage = transactionRepository.findAll(specification, pageable);
return transactionsPage.getContent();

Categories

Resources