I am trying to get the total number (count) for a given field from an index using a generic repository. The index mapping is huge and I do not wish to have an equivalent mapping on the Springboot side. The query is extremely simple;
http://localhost:9203/type_index/_count?q=person.name:john;
Should I have a generic class for the repository?
Can I use the #Query annotation?
How do I define which type and index that the repository should go to? Normally, this is done by the #Document(indexName="type_index) annotation on the Entity class but in this case, I will not have one.
#Repository
public interface GenericElasticsearchRepository<T, ID> extends ElasticsearchRepository<T, ID> {
#Query("{\"bool\": {\"must\": [{\"match\": {\"person.name\": \"?0\"}}]}}")
long countByName(String name);
}
When I use the above code, I am getting the following error;
ElasticSearchConfiguration: Invocation of init method failed;
nested exception is org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class java.lang.Object!
Thanks!
As for building a query method without having the entity you could go for a custom implementation (https://docs.spring.io/spring-data/elasticsearch/docs/current/reference/html/#repositories.single-repository-behavior).
Define an interface (not extending ElasticsearchRepository) like
interface CountByNameRepository {
long countByName(String name);
}
and an implementation (just typing this here, not using an IDE, ao there might be errors):
public class CountByNameRepositoryImpl {
private final ElasticsearchOperations operations;
public CountByNameRepositoryImpl(ElasticsearchOperations operations) {
this.operations = operations;
}
public long countByName(String name) {
Query query = new CriteriaQuery(Criteria.where("person.name").is(name));
return operations.count(query, IndexCoordinates.of("type-index"));
}
}
Your respository then will need to extend bot ElasticsearchRepository and CountByNameRepository, Spring Data Elasticsearch will use the provided implementation.
I have the index name hard coded here, as I don't know where you have access to this. You might be able to pass it as a second parameter to the method, but this depends on your concrete use case.
Related
I have created a simple controller
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(com.querydsl.core.types.Predicate predicate) {
return repository.findAll(predicate);
}
When I call the GET /playerAccount API, I get the exception IllegalStateException "No primary or default constructor found for interface com.querydsl.core.types.Predicate" (thrown by org.springframework.web.method.annotation.ModelAttributeMethodProcessor#createAttribute).
After some (deep!) digging, I found out that if I delete the following line in my spring.xml file:
<mvc:annotation-driven />
And if I add the following line in my Spring.java file:
#EnableWebMvc
then the problem disappears.
I really don't understand why. What could be the cause of that ? I thought that these were really equivalent (one being a xml based configuration, the other being java/annotation based).
I read this documentation on combining Java and Xml configuration, but I didn't see anything relevant there.
edit:
from the (few) comments/answers that I got so far, I understand that maybe using a Predicate in my API is not the best choice.
Although I would really like to understand the nature of the bug, I first want to address the initial issue I'm trying to solve:
Let's say I have a MyEntity entity that is composed of 10 different fields (with different names and types). I would like to search on it easily. If I create the following (empty) interface:
public interface MyEntityRepository extends JpaRepository<MyEntity, Long>, QuerydslPredicateExecutor<MyEntity> {
}
then without any other code (apart from the xml configuration ), I am able to easily search a myEntity entity in the database.
Now I just want to expose that functionality to a Rest endpoint. And ideally, if I add a new field to my MyEntity, I want that API to automatically work with that new field, just like the MyEntityRepository does, without modifying the controller.
I thought this was the purpose of Spring Data and a good approach, but please tell me if there's a better / more common way of creating a search API to a given Entity.
I didn't see that it returned an exception, that's why I thought it was a dependency problem.
Try to make your code look like this, and it will do it.
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts() {
return repository.findAll();
}
If you have a parameter in your request you add #RequestParam.
Code time (yaaaaaay) :
#RestController
public class MyClass {
#Autowired
private final MyRepository repository;
#GetMapping("/playerAccount")
public Iterable<PlayerAccount> getPlayerAccounts(#RequestParam(required = false) Long id) {
return repository.findById(id);
}
Ps: the request should keep the same variable name e.g
.../playerAccount?id=6
assuming I have the following entities:
#Entity
public class Word { ... }
#Entity
public class Noun extends Word { ... }
#Entity
public class Verb extends Word { ... }
(Plus the usual Disriminator- and Join-Strategy stuff, simply assume that the entities work fine, which they do.)
I tried ...
public interface WordRepository extends CrudRepository<Word, Long>{
#Query("SELECT x FROM Word x WHERE type(x) = ?1")
<T extends Word> List<T> findByClass(Class<T> clz);
}
...but this gives me an exception, caused by:
org.hibernate.QueryException: Not all named parameters have been set: [1] [SELECT x FROM Word x WHERE type(x) = ?1]
One solution is to replace Class<T> with Class<?>, then the code works, but obviously that's not type-safe anymore since then I can write...
List<Verb> verbs = repository.findByClass(Noun.class);
...which runs, but obviously throws a ClassCastException whenever I try to access the verbs (since all the objects in the list are actually Nouns, not Verbs)
Is there any way to write this type-safe with spring-data, preferably without hardcoding all types into their own methods (findNouns, findVerbs, etc.) or defining Repositories for all types?
Edit: The problem seems to be the Parameter.isDynamicProjectionParameter(MethodParameter) method, that seems to define a special behavior for Class<T> parameters, so they are only used for dynamic parameters but cannot be given into the query itself. Hm. Wish anyone had a way around that.
I just faced the same issue. For your/every other user´s information i filed a spring data jpa issue, which you folks can follow here:
https://jira.spring.io/browse/DATAJPA-1257
I am writing repository for variables table and wish to access specific rows with it. For this I am trying to autowire main repository into custom implementation, like this:
public interface VariableRepo extends CrudRepository<Variable, Long>, VariableRepoCustom {
Variable getByName(String name);
}
public interface VariableRepoCustom {
...
Variable getPopulationSingle();
...
}
public class VariableRepoCustomImpl implements VariableRepoCustom {
private final VariableRepo variableRepo;
#Autowired
public VariableRepoCustomImpl(VariableRepo variableRepo) {
this.variableRepo = variableRepo;
}
#Override
public Variable getPopulationSingle() {
return getByName("Population single");
}
...
}
Unfortunately, Spring like to go crazy with this, throwing an exception:
Error creating bean with name 'variableRepo': Invocation of init
method failed; nested exception is
org.springframework.data.mapping.PropertyReferenceException: No
property getPopulationSingle found for type Variable!
I.e. it tries to find repository method inside entity class (Variable) which of course should not contained inside.
How to solve this?
What you named VariableRepoCustom/VariableRepoCustomImpl should be in fact a service (not a repository in terms of spring-data-jpa).
The interface VariableRepoCustom should not be present in JPA searchpath, to prevent JPA to generate an implementation
I think might be your repository impl does not have #Repository annotation. Please make both repository and impl as #Repository. it should fix your problem.
For best practice, No need to autowire VariableRepo interface into impl class.
Edit:
Also remove custom from impl. The name will be RepoName by append impl. it is definition like VariableRepoImpl
So I was following the tutorial here: https://spring.io/guides/gs/accessing-data-jpa/
and it works fine, I'm trying to implement it in my application (Because it makes JPA so easy to use), but I'm confused.
Where it has
#Bean
public CommandLineRunner demo(CustomerRepository repository)
and then it acts on the repository
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
How can it act on an interface? CustomerRepository is an interface, and I can't instantiate it
CustomerRepository c = new CustomerRepository()
cant be done, and I don't have any classes that implement the interface. I just wanted to do something like
CustomerRepository c = new CustomerRepository()
c.save(new Customer("whatever", "whatever")
but I can only use it in the CommandLineRunner bean method. Why can I do this with commandlinerunner but cant do it with another class?
I was thinking I could just make a class that extends CustomerRepository, but then I read that the interface actually does all the method implementation itself (JPA does it) so you don't have to worry about it.
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
so if I extended it, wouldn't I have to override the findbylastname() method, meaning JPA wouldn't do it itself?
Thanks for any assistance.
so if I extended it, wouldn't I have to override the findbylastname()
method, meaning JPA wouldn't do it itself?
No, it is not JPA which does the job but Spring which generates by APO some JPA processings.
With Spring Repository, you have multiples ways of doing :
write your own SQL/JPQL query.
use naming convention in the method name to write the query
In both cases, you don't need to implement directly the interface you declare here :
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
Because as you understood, the job is performed for you by Spring.
For example, in the case you quote, you use naming convention in the method name to write the query.
When Spring inspects your interface and sees the method findByLastName(String lastName), it associates the method name to a query which does a find with a match by lastName field. So, it generate a JPQL query like that :
select c from Customer c where c.lastName=%lastName
and it sets the lastName param with the effective parameter used when the method is call.
I extended it, wouldn't I have to override the findbylastname()
method, meaning JPA wouldn't do it itself ?
No, you don't need to implement the methods as spring-data-jpa will take care of it, you can look here on how Spring data repository interfaces are actually implemented by proxy at runtime.
The main point that you need to remember is that spring data has got few rules to derive the sql queries from the method names (like findByLastName(), findByLastnameOrderByFirstnameAsc(), etc..), you can look here to understand how method names work and they are related to field names of your entity bean.
If you wanted to write some complex queries which can't be derived from method names you can use #Query for your methods.
If I made a class public class Cust implements CustomerRepository what
would I do when it asks me I have to implement the
findByLastName(String lastName); method that JPA is supposed to take
care of ?
If you wanted to implement repository to provide your custom behaviour for few of the methods for few of your methods, you can do that (like class Cust implements CustomerRepository), you can refer the section "Using JpaContext in custom implementations", it is well explained in the ref doc.
I would like to create a Spring Data JPA repository with custom behavior, and implement that custom behavior using Specifications. I have gone through the Spring Data JPA documentation for implementing custom behavior in a single repository to set this up, except there is no example of using a Spring Data Specification from within a custom repository. How would one do this, if even possible?
I do not see a way to inject something into the custom implementation that takes a specification. I thought I would be tricky and inject the CRUD repository portion of the repository into the custom portion, but that results in a circular instantiation dependency.
I am not using QueryDSL. Thanks.
I guess the primary source for inspiration could be how SimpleJpaRepository handles specifications. The key spots to have a look at are:
SimpleJpaRepository.getQuery(…) - it's basically creating a CriteriaQuery and bootstraps a select using a JPA Root. Whether the latter applies to your use case is already up to you. I think the former will apply definitely.
SimpleJpaRepository.applySpecificationToCriteria(…) - it basically uses the artifacts produced in getQuery(…) (i.e. the Root and the CriteriaQuery) and applies the given Specification to exactly these artifacts.
this is not using Specification, so not sure if it's relevant to you, but one way that I was able to inject custom behavior is as follows,
Basic structure: as follows
i. create a generic interface for the set of entity classes which are modeled after a generic parent entity. Note, this is optional. In my case I had a need for this hierarchy, but it's not necessary
public interface GenericRepository<T> {
// add any common methods to your entity hierarchy objects,
// so that you don't have to repeat them in each of the children entities
// since you will be extending from this interface
}
ii. Extend a specific repository from generic (step 1) and JPARepository as
public interface MySpecificEntityRepository extends GenericRepository<MySpecificEntity>, JpaRepository<MySpecificEntity, Long> {
// add all methods based on column names, entity graphs or JPQL that you would like to
// have here in addition to what's offered by JpaRepository
}
iii. Use the above repository in your service implementation class
Now, the Service class may look like this,
public interface GenericService<T extends GenericEntity, ID extends Serializable> {
// add specific methods you want to extend to user
}
The generic implementation class can be as follows,
public abstract class GenericServiceImpl<T extends GenericEntity, J extends JpaRepository<T, Long> & GenericRepository<T>> implements GenericService<T, Long> {
// constructor takes in specific repository
public GenericServiceImpl(J genericRepository) {
// save this to local var
}
// using the above repository, specific methods are programmed
}
specific implementation class can be
public class MySpecificEntityServiceImpl extends GenericServiceImpl<MySpecificEntity, MySpecificEntityRepository> implements MySpecificEntityService {
// the specific repository is autowired
#Autowired
public MySpecificEntityServiceImpl(MySpecificEntityRepository genericRepository) {
super(genericRepository);
this.genericRepository = (MySpecificEntityRepository) genericRepository;
}
}