Using QueryDslRepositorySupport in combination with interface repositories - spring

since I didn't get a reply on the spring forum I'll give it a try here.
Is there a way to have a common interface repository which is extended by interfaces the following way:
#NoRepositoryBean
public interface CommonRepository<T> extends JpaRepository<T, Long>, QueryDslPredicateExecutor<T> {
T getById(final long id);
}
#Repository
public interface ConcreteRepository extends CommonRepository<ConcreteEntity> {
List<ConcreteEntity> getByNameAndAddress(final String name, final String address);
}
public class ConcreteRepositoryImpl extends QueryDslRepositorySupport implements ConcreteRepository {
private BooleanExpression nameEquals(final QConcreteEntity entity, final String name) {
return entity.eq(name);
}
public List<ConcreteEntity> getByNameAndAddress(final String name, final String address) {
QConcreteEntity entity = QConcreteEntity.concreteEntity;
return from(entity).where(entity.name.eq(name).and(entity.address.eq(address))).list(entity);
}
}
The problem with the implementation is that I have to implement getById(final long id)
in each concrete class. I don't want to do that. Normally, spring data automatically knows about each entity. Also I want to have the functionality of QueryDslRepositorySupport.
In my example it normally generates something like:
select .. from concreteentity en where en.id = ...
Is there a way to solve it? I already stumbled upon
Spring Jpa adding custom functionality to all repositories and at the same time other custom funcs to a single repository
and
http://docs.spring.io/spring-data/data-jpa/docs/current/reference/html/repositories.html#repositories.custom-implementations
but I don't think these solutions are helpful and I don't entirely understand how I can use them to solve the problem.
Thanks,
Christian

One way to create a generic getById under QuerydslRepositorySupport is like this
T getById(long id) {
return getEntityManager().find(getBuilder().getType(), id)
}

Related

#RepositoryRestResource changes url every time the application is restarted

I have a repository interface that extends JpaRepository and a NameRepositoryCustom.
My repository is annotated with #RepositoryRestRessource(collectionResourceRel="pathname", path="pathname").
The problem I have is that every second restart of my application the URL of the repository gets changed so I can't find the exposed data of the repository under the URL I defined and some features like the search of the repository aren't exposed in the API anymore either.
The "NameRepositroyCustom" is used for a search function which uses another Repository to implement Specification with JPA Criteria Api for a searchbar in my frontend.
Does anybody have a solution for this? The only repository annotated as #RepositoryRestRessource is the main repository that implements all the others. The NameRepositorySpec is annotated with #Repository, could this maybe be the cause?
Edit: I implemented the code as an example to clarify the relations between the mentioned classes and interfaces.
This is the basic repository related to the entity persisted in the database:
#RepositoryRestResource(collectionRessourceRel = "enitynames", path = "entitynames")
public interface EntitynameRepository extends JpaRepository<Entityname, Long>, EntitynameRepositoryCustom{
//custom methods in here
}
This is the custom repository:
public interface EntitynameRepositoryCustom {
Page<Entityname> search(String exampleParam1, String exampleParam2, Pageable pageable);
}
This is the implementation of the custom repository:
public class EntitynameRepositoryCustomImpl implements EntitynameRepositoryCustom{
#Autowired
EntityManager em;
#Autowired
EntitynameRepositorySpec entitynameRepositorySpec;
Specification<Entityname> querySpecification = null;
#Override
public Page<Entityname> search(String exampleParam1, String exampleParam2, Pageable pageable) {
//Code here uses the criteria builder and Specification to generate a custom query with optional parameters
CriteriaBuilder cb= em.getCriteriaBuilder();
CriteriaQuery<Entityname> cq = cb.createQuery(Entityname.class);
//Code below is done for every passed in parameter
if(exampleParam1 != null){
Specification<Entityname> param1Specification = EntitynameSpecification.likeParam1(exampleParam1);
querySpecification = Specification.where(param1Specification);
} else {
return null;
}
return entitynameRepositorySpec.findAll(specification, pageable);
}
}
This is the specification repository:
public interface EntitynameRepositorySpec extends JpaRepository<Entityname, Long>, JpaSpecificationExecutor<Entityname>{
}
And this is the implementation of the specification:
public class EntitynameSpecification {
public static Specification<Entityname> likeExampleParam1(String exampleParam1){
if(exampleParam1 == null){
return null;
}
return(root, query, cb) -> {
reutrn cb.like(root.get("fieldname"), "%"+ exampleParam1 + "%");
};
}
}
The URL of the repository gets changed to a part of the entity name compared to my example it would be something like: entityname has URL: /entityname
if the bug occurs the URL changes to /name.

Querying mongodb collection SpringWebFlux with reactivemongodb

I am developing simple spring webflux demo application with reactive mongodb and i want to read all data of Employee by name except containing name field "joe","Sara","JOE","SARA" and i have following code like:
//repository interface
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, String>{
Flux<Employee> findAllByName(String name);
}
//Service class
public class EmplyeeService
{
private EmployeeRepository employeeRepository;
public Flux<Employee> findAllByOrganizationName(String name)
{
return employeeRepository.findAllByName(name);
}
public Flux<String> getAllNameExceptSome(String name)
{
Employee emp1=new Employee();
List<Flux<Employee>> emp=Arrays.asList(employeeRepository.findAllByName(name));
Flux<Flux<Employee>> emp2=Flux.fromIterable(emp)
.filter(name->name.equalsIgnoreCase("joe"));
return emp2;
}
}
First of all, unless some particular situations, you should avoid these data structures:
List<Flux<Employee>>
Flux<Flux<Employee>>
However you are not leveraging Spring Data. You can achieve you result simply changing your repository to:
public interface EmployeeRepository extends ReactiveMongoRepository<Employee, String> {
// this find all Employee except those matching names provided as param
Flux<Employee> findAllByNameNotIn(List<String> nameList);
// this find all Employee matching names provided as param
Flux<Employee> findAllByNameIn(List<String> nameList);
}
Invoking this method you will obtain the list of Employee already filtered by name.

Spring - Access a Service interface programmatically

i have several interfaces which extend a single interface.
I need to add, during a #PostCostruct method, these interfaces to a Map.
The problem is that i need to retrieve the #Service class name from the DB and i don't know ho to put the interface in the map...
I'll try to explain it better
I have a general service interface
public interface IVehicleServiceGeneral{
//methods...
}
then i have several interfaces which extend the general one.
public interface IService1 extends IVehicleServiceGeneral{
}
public interface IService2 extends IVehicleServiceGeneral{
}
the concrete implementations of these classes are annotated with #Service("service1Name"), #Service("service2Name") and so on...
Then from the DB i retrieve my Suppliers
public class Supplier {
private long id;
private String serviceName;
//getters and setters
}
Finally i need to create the map, because i need to access the implementations at runtime based on the Supplier, i created a ContextAware class to get my beans by name, but the interfaces are not beans... I also tried to put the #Qualifier on the interface, but obviously it does not work... How can I put the interface in the map?
#PostConstruct
private void createServiceMap(){
serviceMap = new HashMap<OBUSupplier, IVehicleServiceGeneral>();
List<Supplier> suppliers = supplierService.findAll();
for(Supplier s : suppliers) {
serviceMap.put(s, contextAware.getBean(s.getServiceName()));
}
}
You can create IVehicleServiceGeneral instance map like this:
class SomeClass {
Map vehicleServiceGeneralInstanceMap = new HashMap();
SomeClass(Set<IVehicleServiceGeneral> instances) {
instances.forEach(i -> vehicleServiceGeneralInstanceMap.put(i.getServiceName(), i));
}
private void createServiceMap() {
Map serviceMap = new HashMap<OBUSupplier, IVehicleServiceGeneral>();
List<Supplier> suppliers = supplierService.findAll();
for(Supplier s : suppliers) {
serviceMap.put(s, vehicleServiceGeneralInstanceMap.get(s.getServiceName()));
}
}
The only thing you require is IVehicleServiceGeneral#getServiceName which your Service1, 2 need to override with proper names that present in DB.

Why is this method in a Spring Data repository considered a query method?

We have implemented an application that should be able to use either JPA, Couchbase or MongoDB. (for now, may increase in the future). We successfully implemented JPA and Couchbase by separating repositories for each e.g. JPA will come from org.company.repository.jpa while couchbase will come from org.company.repository.cb. All repository interfaces extends a common repository found in org.company.repository. We are now targeting MongoDB by creating a new package org.company.repository.mongo. However we are encountering this error:
No property updateLastUsedDate found for type TokenHistory!
Here are our codes:
#Document
public class TokenHistory extends BaseEntity {
private String subject;
private Date lastUpdate;
// Getters and setters here...
}
Under org.company.repository.TokenHistoryRepository.java
#NoRepositoryBean
public interface TokenHistoryRepository<ID extends Serializable> extends TokenHistoryRepositoryCustom, BaseEntityRepository<TokenHistory, ID> {
// No problem here. Handled by Spring Data
TokenHistory findBySubject(#Param("subject") String subject);
}
// The custom method
interface TokenHistoryRepositoryCustom {
void updateLastUsedDate(#Param("subject") String subject);
}
Under org.company.repository.mongo.TokenHistoryMongoRepository.java
#RepositoryRestResource(path = "/token-history")
public interface TokenHistoryMongoRepository extends TokenHistoryRepository<String> {
TokenHistory findBySubject(#Param("subject") String subject);
}
class TokenHistoryMongoRepositoryCustomImpl {
public void updateLastUsedDate(String subject) {
//TODO implement this
}
}
And for Mongo Configuration
#Configuration
#Profile("mongo")
#EnableMongoRepositories(basePackages = {
"org.company.repository.mongo"
}, repositoryImplementationPostfix = "CustomImpl",
repositoryBaseClass = BaseEntityRepositoryMongoImpl.class
)
public class MongoConfig {
}
Setup is the same for both JPA and Couchbase but we didn't encountered that error. It was able to use the inner class with "CustomImpl" prefix, which should be the case base on the documentations.
Is there a problem in my setup or configuration for MongoDB?
Your TokenHistoryMongoRepositoryCustomImpl doesn't actually implement the TokenHistoryRepositoryCustom interface, which means that there's no way for us to find out that updateLastUsedDate(…) in the class found is considered to be an implementation of the interface method. Hence, it's considered a query method and then triggers the query derivation.
I highly doubt that this works for the other stores as claimed as the code inspecting query methods is shared in DefaultRepositoryInformation.

How to create count query on MongoRepository

I am creating a MongoRepository and need to create a count query. Can someone provide an example of what is the best way to do this via the SpringData MongoDB MongoRepository facility? All the examples I was able to find reference returning a List but not counts.
Here is what I am trying to do (obviously it does not work):
public interface SchoolRepository extends MongoRepository<School, String> {
#Query("db.school.count({studentStatus: ?0});")
int getCountOfStudents(int studentStatus);
}
Thanks.
-AP_
I found this question as I was trying to do something similar. Unfortunately, given what I see in org.springframework.data.repository.query.parser.PartTree:
private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get)(\\p{Upper}.*?)??By");
It does not appear to be supported.
Instead, we can add custom behaviour to the repository (see reference manual section 1.4.1) by creating a new interface and a class that implements it.
public interface SchoolRepository extends CrudRepository<School, String>, SchoolRepositoryCustom {
// find... read... get...
}
public interface SchoolRepositoryCustom {
int getCountOfStudents(int studentStatus);
}
#Service
public class SchoolRepositoryImpl implements SchoolRepositoryCustom {
#Autowired
private SchoolRepository schoolRepository;
public int getCountOfStudents(int studentStatus) {
// ...
}
}
Note that the class is named SchoolRepositoryImpl, not SchoolRepositoryCustomImpl.

Resources