I'm setting up an ItemRepositoryReader for the reader in a springBatch step.
public ItemReader<EcheanceEntity> reader(){
RepositoryItemReader<EcheanceEntity> reader = new RepositoryItemReader<EcheanceEntity>();
reader.setRepository(echeanceRepository);
reader.setMethodName("findById");
List parameters = new ArrayList();
long a = 0;
parameters.add(a);
reader.setArguments(parameters);
Map<String, Direction> sort = new HashMap<String, Direction>();
sort.put("id", Direction.ASC);
reader.setSort(sort);
return reader;
}
this is the line in my repository.
public interface EcheanceRepository extends JpaRepository<EcheanceEntity, Long>{
public EcheanceEntity findById(long id);
#Override
public List<EcheanceEntity> findAll();
If a use the method findAll(), so without any arguments, it works fine. But if I use the method findById(long id) I get "no such method exception, findById(java.lang.Long, org.springframework.data.domain.PageRequest)" from the ItemRepositoryReader. The method works fine without using the reader when I'm testing it by using immediately the repository.
Thank you.
In a case of using the RepositoryItemReader#setMethodName method you need to add an argument of type Pageable in your repository method signature at last position:
public interface EcheanceRepository extends JpaRepository<EcheanceEntity, Long> {
public Page<EcheanceEntity> findById(long id, Pageable pageable);
...
You can find description of it in the documentation: http://docs.spring.io/spring-batch/apidocs/org/springframework/batch/item/data/RepositoryItemReader.html#setMethodName-java.lang.String-
public void setMethodName(java.lang.String methodName)
Specifies what method on the repository to call. This method must
take Pageable as the last argument.
Related
I am a beginner in Spring Data JDBC. I am following Spring in action 6th edition and have created the repository as follows:
package com.springinaction.tacocloud;
import java.util.Optional;
import org.springframework.data.repository.CrudRepository;
public interface IngredientRepository extends CrudRepository<Ingredient, String>{
public Optional<Ingredient> getById(String id);
public Iterable<Ingredient> getIngredients();
public Ingredient save(Ingredient ingredient);
}
And I have injected this repository in my controller.
#Slf4j
#Controller
#RequestMapping("/design")
public class DesignTacoController {
protected IngredientRepository ingredientRepo;
public DesignTacoController(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
#ModelAttribute
public void addIngredientsToModel(Model model) {
Iterable<Ingredient> ingredientsFromJdbc = ingredientRepo.getIngredients();
//converting Iterable returned by JdbcTemplate to a List
List<Ingredient> ingredients = new ArrayList<Ingredient>();
ingredientsFromJdbc.forEach(ingredients::add);
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),filterByType(ingredients, type));
}
}
.......................................................................
For invoking the method getIngredients() in the controller it throws the following exception
nested exception is org.springframework.data.mapping.PropertyReferenceException: No property 'getIngredients' found for type 'Ingredient'!
What am I missing here?
Just remove the method getIngredients() and use findAll() (provided by the super interface) instead.
You should also remove the other methods, because they are already provided by the super interface
save() <- provided by CrudRepository
getById() <- CrudRepository provide Optional<T> findById(ID id);
getIngredients <- CrudRepository provide Iterable<T> findAll();
I have a springboot application where I am saving a list with my Meeting Entity. I can save my entities all at once, but I want to check before each save if one entity inside my list already exists inside my Db and then just save the ones which are not inside. But I am kinda stuck and do not know how to do it. Could someone look at my code and give me an advice?
MeetingController:
#PostMapping("/")
public void saveMeeting(#RequestBody List<Meeting> meeting){
List<Meeting> exist =
meetingService.findAllMeetingsWithName(meeting.stream().map(m -> m.getMeetingName()).collect(Collectors.toList()));
meeting.removeAll(exist);
meetingService.saveMeeting(meeting);
}
MeetingService:
public void saveMeeting(List<Meeting> meeting){
meetingRepository.saveAll(meeting);
}
Repository:
#Repository
public interface MeetingRepository extends JpaRepository<Meeting, Long> {
}
This is the functionality that you want
#Transactional
public void saveMeeting(List<Meeting> meeting){
List<Meeting> alreadyExist = meetingRepository.findByMeetingNameIn(meeting.stream().map(m -> m.getMeetingName()).collect(Collectors.toList());
meeting.removeAll(alreadyExist);
meetingRepository.saveAll(meeting);
}
and then just define that repository method
#Repository
public interface MeetingRepository extends JpaRepository<Meeting, Long> {
List<Meeting> findByMeetingNameIn (List<String> meetingNames);
}
Remember to override equals and hashcode in Meeting entity to consider the id field.
You can check whether your meetings are in the DB. Try this.
List<Meeting> findByValueIn(List<Meeting> values);
Since you already are planning to use JpaRepository, take advantage of the #Query and batch queries
#Repository
//custom query using IN
public interface MeetingRepository extends JpaRepository<Meeting, Long>
{
#Query("SELECT m from MEETING m where m.name IN (:names)")
public List<Meetings> findAllMeetingWithNames(List<String> meetings)
}
#Service
class MeetingService{
private MeetingRepository meetingRepository;
...
//other code....
#Autowired
public MeetingService(MeetingRepository meetingRepository){
this.meetingRepository = meetingRepository;
}
public void saveAllMeetings(List<Meeting> meetings){
//call repository custom query here
names = meetings.stream()
.map((m) -> m.name)
.collect(Collectors.toList());
List<Meeting> existingMeetings = meetingRepository.findAllMeetingWithNames(names);
//delete using a single query
meetingRepository.deleteAllInBatch(existingMeetings);
meetingRepository.saveAllAndFlush(meetings);
}
.....
}
So I'd like a "Void-Repository" through which to gain access to stored procedures that are not necessarily operation on entities.
#Repository
public interface StoredProceduresRepository extends CrudRepository<Void, Long> {
#Procedure("my_answer_giver")
String getMyAnswer(#Param("input") String input);
}
But that does, of course, not work because the CrudRepository expects Void to be an entity.
Is there a way to use the #Procedure annotation without having to create dummy entities or am I stuck with an implemented class that makes use of the EntityManager to query via prepared statements?
Because let's be honest, that's fugly:
#Repository
public class StoredProceduresRepository {
#PersistenceContext
EntityManager em;
public String getMyAnswer(String input) {
Query myAnswerGiver = em
.createStoredProcedureQuery("my_answer_giver")
.registerStoredProcedureParameter("input", String.class, ParameterMode.IN)
.setParameter("input", input);
Object result = ((Object[]) myAnswerGiver.getSingleResult())[0];
return (String) result;
}
}
If it is ok for you you can use any Entity you have, in place of this Void. The Entity provided there should not matter.
public interface StoredProceduresRepository extends JpaRepository<SomeUnrelatedEntity, Long> {
#Procedure("my_answer_giver")
String getMyAnswer(#Param("input") String input);
}
I have been using it like this with database views.
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.
I'm using Spring Data JPA and I have a bunch of repositories like this one:
public interface CustomerRepository extends JpaRepository<Customer, Long> {}
Under repositories I have services and a lot of them need to have implemented method findOrCreate(String name) like this one:
#Override
#Transactional
public List<Customer> findOrCreate(final String name) {
checkNotNull(name);
List<Customer> result = this.customerRepository.findByName(name);
if (result.isEmpty()) {
LOGGER.info("Cannot find customer. Creating a new customer. [name={}]", name);
Customer customer = new Customer(name);
return Arrays.asList(this.customerRepository.save(customer));
}
return result;
}
I would like to extract method to the abstract class or somewhere to avoid implementing it for each services, testing and so on.
Abstract class can be look like this:
public abstract class AbstractManagementService<T, R extends JpaRepository<T, Serializable>> {
protected List<T> findOrCreate(T entity, R repository) {
checkNotNull(entity);
checkNotNull(repository);
return null;
}
}
And the problem is it due to the fact that I need to find object by name as a string before creating a new one. Of course interface JpaRepository doesn't offer this method.
How can I solve this problem?
Best Regards
Create a custom JpaRepository implementation that includes this behaviour. See this post for an example of writing a custom JpaRepository implementation.