I have an entity that is exposed by the following repository:
public interface InsertRepository extends PagingAndSortingRepository<InsertEntity, Long>, QueryDslPredicateExecutor<InsertEntity>, QueryDslBinderCustomizer<QInsertEntity> {
#Override
default void customize(QuerydslBindings bindings, QInsertEntity insert) {
bindings.bind(String.class).all(StringPath path, Collection<? extends String> values) -> {
BooleanBuilder predicate = new BooleanBuilder();
values.forEach(value -> predicate.or(path.containsIgnoreCase(value)));
return predicate;
});
}
}
What I'd like it to do is that all GET query parameters are chained as logical OR so that a query like ?description=searchText&customerName=searchText would execute an SQL query that looks as follows:
WHERE description LIKE '%searchText%' OR customerName LIKE '%searchText%'
However, I must be doing something wrong because it doesn't work - it is putting all query parameters into an AND query. That results in only those records being selected that contain searchText in customerName AND their description.
You can use named parameters such as
Example 53. Using named parameters
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.query-creation
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname")
User findByLastnameOrFirstname(#Param("lastname") String lastname,
#Param("firstname") String firstname);
}
The answer provided by #kafkas is not accurate at all. There are quite few problems with it:
Annotation #Query is obsolette - Spring automatically does it for you, you just need to type the name of the method properly.
Annotations #Param are obsolette - Spring automatically takes parameters in given order, matched with those in method name.
You return single User entity, but you still use findBy instead of findOne - this leads to an error, if more than one record is found.
The last, but not least - provided method will not use LIKE comparation, but equals instead. You should use findByXXXContaining if You wish to launch SQL query like: ... WHERE firstname LIKE "%name%"
Using Spring, I suggest not using JpaRepository if You don't need to. The simplest implementation is CrudRepository and it covers most use cases.
In summary, Your method should be simplified and look somewhere like that:
public interface UserRepository extends CrudRepository<User, Long> {
User findOneByLastnameContainingOrFirstnameContaining(String lastname, String firstname);
}
This should result in query:
SELECT * FROM User u WHERE u.lastname LIKE '%lastname' OR u.firstname LIKE '%firstname%;'
Related
I have an API that will be taking in a date range. My application is using Redis and we have to query entries by indexed date strings. Because Redis doesn't allow a query like Select * from Redis where date in (...), I have to be able to create a dynamic query with an unspecified amount of 'or' conditions to the effect of select * from Redis where date = x OR date = y OR date = z. I've followed these tutorials but of course they're extremely vague and don't actually work as advertised. I will be looking into Querydsl tomorrow to see if that's a viable option. Here's the jist of my code.
#Data
#AllArgsConstructor
public class EventSpecs implements Specification<Event>{
private ArrayList<String> dates;
#Override
public Predicate toPredicate(Root<Event> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.or(
dates
.stream()
.map(value -> builder.equal(root.get("date"), value))
.toArray(Predicate[]::new)
);
}
}
Repository:
public interface EventRepository extends PagingAndSortingRepository<Event, String>, JpaSpecificationExecutor<Event> {}
and where it's being called:
#Autowired
private EventRepository eventRepository;
ArrayList<String> dateRange = new ArrayList<>();
PageRequest page = PageRequest.of(0, 10);
EventSpecs specs = new EventSpecs(dateRange);
Page<Event> events = eventRepository.findAll(specs, page);
But when I call findAll() I always get
org.springframework.data.mapping.PropertyReferenceException: No property findAll found for type Event! and I understand I should be calling by class properties like findByDate() but I don't want to query by a static amount of parameters. Am I on the right path to accomplishing what I need to? Does anyone have resources for more thorough tutorials? Spring Documentation is the exact same way, vague and their code doesn't work either.
I wrote a method, but as you can see - Category here is never used.
Is to possible to find only Dishes where Category is same as defined and only after it - sort it by parameter using plain Spring data jpa?
Or the only way to it is a custom query?
public List<Dish> findAllDishesSorted(String sortField, String sortDirection, String category) {
Sort sort = sortDirection.equalsIgnoreCase(Sort.Direction.ASC.name())
? Sort.by(sortField).ascending() : Sort.by(sortField).descending();
return dishRepository.findAll(sort);
}
You could add a method like below to your DishRepository and it should be able to achieve that without needing to write custom query with #Query() annotation
public interface DishRepository extends CrudRepository<Dish, Long> {
Dish findByCategory(String category, Sort sort);
}
Hello I wrote inner join between the foreign key and the linked tables, but I can not print the data in the 2nd table. I get an error in Javascript, probably the data in the 2nd table is not coming there. I need user_name in User table but I can not access.
BookController
#Autowired
private BooksService booksService;
#Autowired
private BookCommentsService bookCommentsService;
#RequestMapping(value = "/bookdetail")
public ModelAndView showBookDetailForm(#RequestParam long book_id)
{
ModelAndView mav= new ModelAndView("bookdetail");
List<Books> book=booksService.bookGetWithId(book_id);
List<BookComments> listBookComments= bookCommentsService.listAllDetailComments(book_id);
mav.addObject("listBook",book);
mav.addObject("listBookComments", listBookComments);
return mav;
}
BookCommentsService
#Service
public class BookCommentsService {
#Autowired
private BookCommentsRepository repository;
public List<BookComments> listAllDetailComments(long book_id)
{
return repository.listAllDetailComments(book_id);
}
}
BookCommentsRepository
public interface BookCommentsRepository extends CrudRepository<BookComments, Long> {
#Query(value = "Select b From BookComments b inner join Users u on b.comment_user_id = u.user_id where b.comment_book_id= :book_id")
public List<BookComments> listAllDetailComments(#Param("book_id") long book_id);
}
BookDetail.js
<c:forEach items="${listBookComments}" var="bookcomments">
(No problem here) ${bookcomments.book_comment}
(The error is here) <h4>${bookcomments.user_name}</h4>
</c:forEach>
You should use comment_user_id and close the braces, ${bookcomments.comment_user_id} should work fine.
To solve your problem, you must use another object (known as a Data Transfer Object; DTO) which will contain the details from BookComments and the username.
An example would be:
package com.myapp.book;
public class BookCommentsDto{
private String bookComment;
private String username;
// You can add any other attributes that you need from the table book_comments
// add them in the constructor as well
public BookCommentsDto(String bookComment, String username){
this.bookComment = bookComment;
this.username = username;
}
}
I will refer to the class BookCommentsDto as dto. The above dto is the simple dto you might need. You can add other details you need from book comment in the dto and make sure to add them in the constructor as well. I have included a dummy package name on the top because we need to mention the full classpath of the dto in the query we will write. In the query below, I am creating the dto from the result of the sql. In the query, I am using the constructor of BookCommentsDto to create the object which will be returned.
#Query(value = "Select new com.myapp.book.BookCommentsDto(b.book_comment, u.user_name) From BookComments b inner join Users u on b.comment_user_id = u.user_id where b.comment_book_id= :book_id")
public List<BookCommentsDto> listAllDetailComments(#Param("book_id") long book_id);
Update return type in the service BookCommentsService
#Service
public class BookCommentsService {
#Autowired
private BookCommentsRepository repository;
public List<BookCommentsDto> listAllDetailComments(long book_id)
{
return repository.listAllDetailComments(book_id);
}
}
Update controller
#RequestMapping(value = "/bookdetail")
public ModelAndView showBookDetailForm(#RequestParam long book_id)
{
ModelAndView mav= new ModelAndView("bookdetail");
List<Books> book=booksService.bookGetWithId(book_id);
List<BookCommentsDto> listBookCommentsDto= bookCommentsService.listAllDetailComments(book_id);
mav.addObject("listBook",book);
mav.addObject("listBookCommentsDto", listBookCommentsDto);
return mav;
}
Finally update js
<c:forEach items="${listBookCommentsDto}" var="bookcommentsDto">
${bookcommentsDto.bookComment}
<h4>${bookcommentsDto.username}</h4>
</c:forEach>
if you do the above changes, the code should work just fine.
I have a few comments for you if you want to improve your code and get better at coding.
When using Hibernate, we must think in terms of entities instead of tables found in the DB. Therefore we do not do inner joins like we do in native sql, instead we use hibernate relationship to define the relationship between the table and we join the entities instead of the tables. You can read on OneToMany, ManyToOne and ManyToMany relationships when it comes to joining entities.
List<Books> book=booksService.bookGetWithId(book_id);
A book_id is returning a list of Books? book_id should be unique per book if ids are used correctly and even if it is returning a list of books, the generic of the list should be Book, not Books; List<Book>, not List<Books>.
When hibernate is implemented correctly, you should be able to retrieve all the data at once and it would be something like:
This is the type of code is which you should be able to achieve after working with hibernate for a while (appx 1 year), however there is still a lot that can be improved about it.
In book's repository
#Query("from book b inner join b.comments c inner join c.users where b.book_id = :book_id")
Book getBook(#Param("book_id") long book_id); // bookService.getBook will call this method
Controller level
Book book = bookService.getBook(bookId); // only this one is querying the DB
List<BookComment> bookComments = book.getComments();
List<User> users = book.getComments().getUsers();
Advance topics to read on (read in same order as posted):
HQL - Hibernate Query Language
N+1 issue with Hibernate
JPA Specifications (for creating dynamic queries)
QueryDsl - an improvement of JPA Specifications
So, my problem is I have a api I am creating an I am getting data from database. So, when I do GET localhost:8080/myapp/jobs?autocomplete=0120 (which 0120 is the full value of the jobs code to show that data, which it does show that data). But, when I do localhost:8080/myapp/jobs?autocomplete=012 it wont show what data it has for 012 to display in json. Can anyone help me solve this issue with my code below. thanks!
#RequestMapping(value = "/jobs")
public List<AutoComplete> getSalary( #PathVariable("autocomplete") String autocomplete, #RequestParam(value = "jobClassCd", defaultValue = "1502") String jobClassCd) {
return autoCompleteService.retrieveSalary(jobClassCd);
}
I think your best bet would be to write a query that does something like
select * from AutoComplete where jobClassCd like 012%
Change your repository method as below.
#Repository
public interface AutoCompleteRepository extends CrudRepository<AutoComplete, String> {
#Query("select e from AutoComplete e where jobClassCd like ':jobClassCd%'")
List<AutoComplete> findByJobClassCd(#Param("jobClassCd") String jobClassCd);
#Query("select e from AutoComplete e")
public Stream<AutoComplete> streamAll();
}
I have to get all data from MySQL in a table using condition select query where fields is isdeleted=0, location=1. How could I implement this in repository and access manager.
public interface FoodCourtRepository extends JpaRepository<FoodCourtEntity, Long> {
List<FoodcaseEntity> findByIsdeleted(Boolean isDeleted);
}
In access manager
public List<FoodcaseDO> getAllFoodCourt() {
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeleted(false);
}
You need to add another condition as well, for location, e.g.:
public List<FoodcaseEntity> findByIsdeletedAndLocation(boolean deleted, int location);
And call it with false and 1 as arguments, e.g.:
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeletedAndLocation(false, 1);
This should give you the required result.
Updte
If you want to fetch the data for multiple locations then you need to write a method that supports IN, e.g.:
public List<FoodcaseEntity> findByIsdeletedAndLocationIn(boolean deleted, List<Integer> location);
And then call it like this:
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeletedAndLocation(false, Arrays.asList(2,3,4,5));
JPA provides #Query to write custom query. You can define another interface called FoodCourtRepositoryCustom and write custom query as below:
public interface FoodCourtRepositoryCustom {
#Query("SELECT fe FROM FoodcaseEntity fe WHERE fe.isdeleted=?1 AND fe.location=?2 ")
List<FoodcaseEntity> findByIsdeletedAndLocation(Boolean isDeleted, Integer location);
}
Then extends this interface in your repository inteface as below:
public interface FoodCourtRepository extends JpaRepository<FoodCourtEntity, Long>, FoodCourtRepositoryCustom{
List<FoodcaseEntity> findByIsdeleted(Boolean isDeleted);
}
Now method is available in your access manager.
public List<FoodcaseDO> getAllFoodCourt() {
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeleted(false);
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeletedAndLocation(false, 1);
}
In a repository class add,
List<FoodcaseEntity> foodCaseList = foodcourtRepository.findByIsdeletedAndLocation(boolean isSelected,int location);
then u can send selected and location values.it will return list of values based on condition.