I am having trouble trying to implement a query using filters and projections against a mongodb database. I created the simple GET version using mongorepository with the pagination class to return all rows from a specific collection using pagination. However I would like to modify the request to only return records based on a certain field or fields?
#RestController
public class WebSiteController {
#Autowired
private WebSiteRepository siteRepository;
#GetMapping("list")
public List<premium_Web> listWebSites(){
return siteRepository.findAll();
}
#GetMapping("listp")
public Page<premium_Web> listWebSitesPaginated(Pageable p){
return siteRepository.findAll(p);
}
Have to be able to accept the filter parameter in the request and return only those specific records:
I tried some code I had but this does not use mongorepository but instead manually creates an instance of the collection. Could someone please advise as how to use the same filtering query but using mongorepository and the pagination.
if(customerId != null) {
MongoCollection results;
int customerId = Integer.parseInt(customerId);
results = mongoTemplate.getDb().getCollection("client_publishers");
Bson filter = Filters.eq("customers", customerId);
Bson projection = Projections.fields(Projections.include("id","Url","siteId","projectId","active","origin",
"actualVisitors","dateAdded"));
FindIterable<Document> fi = results.find(filter).projection(projection);
//.sort(descending("dateAdded"));
MongoCursor<Document> cursor = fi.iterator();
try {
while(cursor.hasNext()) {
// System.out.println(cursor.next().toJson)...
EDIT:
I'm not sure how to return the results for a GET request that includes the customer 'Name' from a customer table, where the customer ID links to the client_publishers table and all records associated with that ID.
#org.springframework.data.mongodb.repository.Query(value = "{})", fields="{"id","Url","siteId","projectId","active","origin",
"actualVisitors","dateAdded"}")
Page<client_publishers> findByClientID(ObjectId customerId, Pageable pageable);
Related
I'm developing a generic API to fetch data based on the Entity name and its primary key.
URL for get mapping: api/fetch/{id}/data/{entity}
There are many entities present like student, course, instructor, class...
Based on the entity name, the API should return data for that entity by given id in URL.
What should be the best approach using spring boot and JPA?
Trying below, but cannot work when entities are large in number and keep on increasing. Need a generic approach.
#RestController
public class Datacontroller{
#Autowired
CourseRepo courserepo;
#Autowired
Studentrepo studentrepo;
#GetMapping("api/fetch/{id}/data/{entity}")
public <T> T getData(#PathVariable("id") String id, #PathVariable("entity") String entity) {
T l = null;
//depending on entity
if("course".equals(entity)) {
Optional<Course> c = courserepo.findById(id);
l=(T) c.get();
}
if("student".equals(entity)) {
Optional<Student> a = studentrepo.findById(id);
l = (T) a.get();
}
return l;
}
Maybe you should try Spring Data REST. It's a different approach than yours, but it's a Spring project, actively supported and it allows you to directly expose your repositories as REST endpoints.
We can fetch all entities and get entity class from entityName. Once we have class, we can use find method from EntityManager to get the particular record by primary id.
public static Class<?> getEntityClass(EntityManager entityManager, String entityName) {
for (EntityType<?> entity : entityManager.getMetamodel().getEntities()) {
if (entityName.equals(entity.getName())) {
return entity.getJavaType();
}
}
return null;
}
All I want to do is a simple query using spring boot and mongodb but I can't find resources online, update query to increment frequency field by one given searchString or create a new document if searchString is not found.
#Query("{'searchString': ?0 } , {'$inc' : {'frequency':1}} ")
public void incFreq(String query);
Hope you use spring-data-mongodb. Since you haven't mentioned about the Document class, I assume it as Person.class
First you #Autowire the MongoTemplate in the service implementation.
#Autowire
MongoTemplate mongoTemplate;
Then what you can do is, you can call a query like following,
public void incFreq(String given_str){
Query query=new Query(Criteria.where("searchString ").is("given_str"));
Person person=mongoTemplate.find(query,Person.class);
if(person!=null){
Update update=new Update().inc("frequency",1)
UpdateResult result=mongoTemplate.updateOne(query,update,Person.class);
// bu using the result, you can see modifiedCount(),matchCount()
}else{
// insert query
}
}
If you going to use JPA methods, then
public void incFreq(String given_str){
Optional<Person> p=personRepository.findBySearchString(String given_str);
if(p.isPresent()){
p.get().setFrequency(p.get().getFrequency()+1);
personRepository.save(p);
}else{
Person p=new Person();
p.setName("some name");
p.setFrequency(1);
personRepository.save(p);
}
}
Refer Spring data mongodb
#Autowired
MongoTemplete mongotemplate;
Update update = new Update();
Query query = new Query();
update.inc("listPrice",productItem.getListPrice());
mongoTemplate.updateMulti(query,update,ProductCatalogItem.class);
#"listPrice" : for which you wanna use $inc.
#productItem : instance of your class
I am having trouble to get the data that I retrieve from MySQL database into a list of objects. I want to return the data and store it into the list. Once I do that, I want the data available for retrieval to a frontend framework.
But I get an incompatible type error, I cannot return the data into the list.
Note: I am relatively new to Spring-Boot and JdbcTemplate.
My code
This is inside my rest controller
#Autowired
private JdbcTemplate temp;
#RequestMapping("/foo")
public List<Foo> connectAndRetrieve() {
String sql = "SELECT fooName FROM Foo;";
// Error Here
List<Foo> data = temp.queryForList(sql, Foo.class);
return data;
}
How do I return the data from the method and use that data?
Thanks!
You are actually projecting fooName instead of Foo, try the following:
#Autowired
private JdbcTemplate temp;
#RequestMapping("/foo")
public List<Foo> connectAndRetrieve() {
String sql = "SELECT * FROM Foo";
return temp.query(sql, new BeanPropertyRowMapper(Foo.class));
}
Here you can find the documentation for BeanPropertyRowMapper
I have Entity object :
#Entity(name = "table")
public class SomeEntity {
#Id
#Column(name = "id_column_name")
public final BigDecimal entityId;
#Column(name = "table_column_name")
public final String entityFieldName;
}
And I have database view defined like this:
CREATE OR REPLACE FORCE EDITIONABLE VIEW "V_TABLE" ("ID_COLUMN_NAME", "TABLE_COLUMN_NAME", "SOME_OTHER_COLUMN") AS ... (some SQL magic)
And I have repository with custom query:
#RepositoryRestResource
interface SomeEntityRepository extends PagingAndSortingRepository<SomeEntity, BigDecimal> {
#Query(value = "select id_column_name, table_column_name FROM V_TABLE where some_other_column = ?#{#parameter} order by ?#{#pageable}",
countQuery = "SELECT count(*) from V_TABLE v where some_other_column = ?#{#parameter}",
nativeQuery = true)
Page<SomeEntity> findBySomeParameter(#Param("parameter") long parameter, Pageable pageable);
}
Everything works fine when I request standard data with url:
http://localhost:8080/someEntity/search/findBySomeParameter?parameter=25&page=0&size=20
But when I add sorting information it doesn't work:
http://localhost:8080/someEntity/search/findBySomeParameter?parameter=25&page=0&size=20&sort=entityFieldName,asc
will throw following exception (I'm using Oracle database):
Caused by: java.sql.SQLSyntaxErrorException: ORA-00904: "ENTITYFIELDNAME": invalid identifier
It seems like sorting field are not translated with #Column(name), but are inlined into SQL query.
Is there any way to make pageable sort translated, so that it will use not field name but column name?
This article sheds light on the issue. Read from section 3.1 on.
Apparently dynamic sorting is not supported for native queries. Actually, if you change your findBySomeParameter method to take a Sort instead of a Pageable you will get org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting.
Using pageable you don't get the exception, and pagination actually seems to work fine, but dynamic sorting does not substitute the column name as you found. Looks to me like the only solution is to use JPQL instead of native query, which is not a problem as long as the query you need to make is the one you provide. You would need to map the view though to a SomeEntityView class in order to use JPQL.
EDIT
I thought the issue was not documented but it actually is here in the official doc
Spring Data JPA does not currently support dynamic sorting for native queries, because it would have to manipulate the actual query declared, which it cannot do reliably for native SQL. You can, however, use native queries for pagination by specifying the count query yourself, as shown in the following example:
This workaround works for me in SpringBoot 2.4.3:
#PersistenceContext
private EntityManager entityManager;
// an object ptoperty name to a column name adapter
private Pageable adaptSortColumnNames(Pageable pageable) {
if (pageable.getSort().isSorted()) {
SessionFactory sessionFactory;
if (entityManager == null || (sessionFactory = entityManager.getEntityManagerFactory().unwrap(SessionFactory.class)) == null)
return pageable;
AbstractEntityPersister persister = (AbstractEntityPersister) ((MetamodelImplementor) sessionFactory.getMetamodel()).entityPersister(CommentEntity.class);
Sort adaptedSort = pageable.getSort().get().limit(1).map(order -> {
String propertyName = order.getProperty();
String columnName = persister.getPropertyColumnNames(propertyName)[0];
return Sort.by(order.getDirection(), columnName);
}).findFirst().get();
return PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), adaptedSort);
}
return pageable;
}
#GetMapping()
public ResponseEntity<PagedResponse<CommentResponse>> findByTextContainingFts(#RequestParam(value = "text", required = false) String text, Pageable pageable) {
// apply this adapter in controller
pageable = adaptSortColumnNames(pageable);
Page<CommentEntity> page = commentRepository.find(text, pageable);
return ResponseEntity.ok().body(domainMapper.fromPageToPagedResponse(page));
}
I have a spring-mvc project that is using spring-data-jpa for data access. I have a domain object called Travel which I want to allow the end-user to apply a number of filters to it.
For that, I've implemented the following controller:
#Autowired
private TravelRepository travelRep;
#RequestMapping("/search")
public ModelAndView search(
#RequestParam(required= false, defaultValue="") String lastName,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
Page<Travel> travels = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
mav.addObject("page", page);
mav.addObject("lastName", lastName);
return mav;
}
This works fine: The user has a form with a lastName input box which can be used to filter the Travels.
Beyond lastName, my Travel domain object has a lot more attributes by which I'd like to filter. I think that if these attributes were all strings then I could add them as #RequestParams and add a spring-data-jpa method to query by these. For instance I'd add a method findByLastNameLikeAndFirstNameLikeAndShipNameLike.
However, I don't know how should I do it when I need to filter for foreign keys. So my Travel has a period attribute that is a foreign key to the Period domain object, which I need to have it as a dropdown for the user to select the Period.
What I want to do is when the period is null I want to retrieve all travels filtered by the lastName and when the period is not null I want to retrieve all travels for this period filtered by the lastName.
I know that this can be done if I implement two methods in my repository and use an if to my controller:
public ModelAndView search(
#RequestParam(required= false, defaultValue="") String lastName,
#RequestParam(required= false, defaultValue=null) Period period,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
Page travels = null;
if(period==null) {
travels = travelRep.findByLastNameLike("%"+lastName+"%", pageable);
} else {
travels = travelRep.findByPeriodAndLastNameLike(period,"%"+lastName+"%", pageable);
}
mav.addObject("page", page);
mav.addObject("period", period);
mav.addObject("lastName", lastName);
return mav;
}
Is there a way to do this without using the if ? My Travel has not only the period but also other attributes that need to be filtered using dropdowns !! As you can understand, the complexity would be exponentially increased when I need to use more dropdowns because all the combinations'd need to be considered :(
Update 03/12/13: Continuing from M. Deinum's excelent answer, and after actually implementing it, I'd like to provide some comments for completeness of the question/asnwer:
Instead of implementing JpaSpecificationExecutor you should implement JpaSpecificationExecutor<Travel> to avoid type check warnings.
Please take a look at kostja's excellent answer to this question
Really dynamic JPA CriteriaBuilder
since you will need to implement this if you want to have correct filters.
The best documentation I was able to find for the Criteria API was http://www.ibm.com/developerworks/library/j-typesafejpa/. This is a rather long read but I totally recommend it - after reading it most of my questions for Root and CriteriaBuilder were answered :)
Reusing the Travel object was not possible because it contained various other objects (who also contained other objects) which I needed to search for using Like - instead I used a TravelSearch object that contained the fields I needed to search for.
Update 10/05/15: As per #priyank's request, here's how I implemented the TravelSearch object:
public class TravelSearch {
private String lastName;
private School school;
private Period period;
private String companyName;
private TravelTypeEnum travelType;
private TravelStatusEnum travelStatus;
// Setters + Getters
}
This object was used by TravelSpecification (most of the code is domain specific but I'm leaving it there as an example):
public class TravelSpecification implements Specification<Travel> {
private TravelSearch criteria;
public TravelSpecification(TravelSearch ts) {
criteria= ts;
}
#Override
public Predicate toPredicate(Root<Travel> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Join<Travel, Candidacy> o = root.join(Travel_.candidacy);
Path<Candidacy> candidacy = root.get(Travel_.candidacy);
Path<Student> student = candidacy.get(Candidacy_.student);
Path<String> lastName = student.get(Student_.lastName);
Path<School> school = student.get(Student_.school);
Path<Period> period = candidacy.get(Candidacy_.period);
Path<TravelStatusEnum> travelStatus = root.get(Travel_.travelStatus);
Path<TravelTypeEnum> travelType = root.get(Travel_.travelType);
Path<Company> company = root.get(Travel_.company);
Path<String> companyName = company.get(Company_.name);
final List<Predicate> predicates = new ArrayList<Predicate>();
if(criteria.getSchool()!=null) {
predicates.add(cb.equal(school, criteria.getSchool()));
}
if(criteria.getCompanyName()!=null) {
predicates.add(cb.like(companyName, "%"+criteria.getCompanyName()+"%"));
}
if(criteria.getPeriod()!=null) {
predicates.add(cb.equal(period, criteria.getPeriod()));
}
if(criteria.getTravelStatus()!=null) {
predicates.add(cb.equal(travelStatus, criteria.getTravelStatus()));
}
if(criteria.getTravelType()!=null) {
predicates.add(cb.equal(travelType, criteria.getTravelType()));
}
if(criteria.getLastName()!=null ) {
predicates.add(cb.like(lastName, "%"+criteria.getLastName()+"%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Finally, here's my search method:
#RequestMapping("/search")
public ModelAndView search(
#ModelAttribute TravelSearch travelSearch,
Pageable pageable) {
ModelAndView mav = new ModelAndView("travels/list");
TravelSpecification tspec = new TravelSpecification(travelSearch);
Page<Travel> travels = travelRep.findAll(tspec, pageable);
PageWrapper<Travel> page = new PageWrapper<Travel>(travels, "/search");
mav.addObject(travelSearch);
mav.addObject("page", page);
mav.addObject("schools", schoolRep.findAll() );
mav.addObject("periods", periodRep.findAll() );
mav.addObject("travelTypes", TravelTypeEnum.values());
mav.addObject("travelStatuses", TravelStatusEnum.values());
return mav;
}
Hope I helped!
For starters you should stop using #RequestParam and put all your search fields in an object (maybe reuse the Travel object for that). Then you have 2 options which you could use to dynamically build a query
Use the JpaSpecificationExecutor and write a Specification
Use the QueryDslPredicateExecutor and use QueryDSL to write a predicate.
Using JpaSpecificationExecutor
First add the JpaSpecificationExecutor to your TravelRepository this will give you a findAll(Specification) method and you can remove your custom finder methods.
public interface TravelRepository extends JpaRepository<Travel, Long>, JpaSpecificationExecutor<Travel> {}
Then you can create a method in your repository which uses a Specification which basically builds the query. See the Spring Data JPA documentation for this.
The only thing you need to do is create a class which implements Specification and which builds the query based on the fields which are available. The query is build using the JPA Criteria API link.
public class TravelSpecification implements Specification<Travel> {
private final Travel criteria;
public TravelSpecification(Travel criteria) {
this.criteria=criteria;
}
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
// create query/predicate here.
}
}
And finally you need to modify your controller to use the new findAll method (I took the liberty to clean it up a little).
#RequestMapping("/search")
public String search(#ModelAttribute Travel search, Pageable pageable, Model model) {
Specification<Travel> spec = new TravelSpecification(search);
Page<Travel> travels = travelRep.findAll(spec, pageable);
model.addObject("page", new PageWrapper(travels, "/search"));
return "travels/list";
}
Using QueryDslPredicateExecutor
First add the QueryDslPredicateExecutor to your TravelRepository this will give you a findAll(Predicate) method and you can remove your custom finder methods.
public interface TravelRepository extends JpaRepository<Travel, Long>, QueryDslPredicateExecutor<Travel> {}
Next you would implement a service method which would use the Travel object to build a predicate using QueryDSL.
#Service
#Transactional
public class TravelService {
private final TravelRepository travels;
public TravelService(TravelRepository travels) {
this.travels=travels;
}
public Iterable<Travel> search(Travel criteria) {
BooleanExpression predicate = QTravel.travel...
return travels.findAll(predicate);
}
}
See also this bog post.