How to create count query on MongoRepository - spring

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.

Related

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.

How do I autowire a repository which has primitive type dependency injection?

I have three text files, they all contain data of the same type, but data is stored differently in each file.
I want to have one interface:
public interface ItemRepository() {
List<Item> getItems();
}
And instead of creating three implementations I want to create one implementation and use dependency injection to inject a path to the text file
and an analyser class for each text file:
public class ItemRepositoryImpl() implements ItemRepository {
Analyser analyser;
String path;
public ItemRepositoryImpl(Analyser analyser, String path) {
this.analyser = analyser;
this.path = path;
}
public List<Item> getItems() {
// Use injected analyser and a path to the text file to extract the data
}
}
How do I wire everything and inject the ItemRepositoryImpl into my controller?
I know I could simply do:
#Controller
public class ItemController {
#RequestMapping("/items1")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser1(), "file1.txt");
return itemRepository.getItems();
}
#RequestMapping("/items2")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser2(), "file2.txt");
return itemRepository.getItems();
}
#RequestMapping("/items3")
public List<Item> getItems1() {
ItemRepository itemRepository = new ItemRepositoryImpl(new Analyser3(), "file3.txt");
return itemRepository.getItems();
}
}
But I don't know how to configure Spring to autowire it.
You can achieve it in many different ways and it probably depends on your design.
One of them can be initialising 3 different analyzers in spring context and wiring all the three analyzers in ItemRepositoryImpl using '#Qualifier' annotation. With the help of an extra method parameter, ItemRepositoryImpl can decide which analyzer it should route the requests to.
For the path variable also you can follow a similar approach.
If your question is specific about how to wire the primitive type in the bean, check this post . It specifies how to initialize a String variable in spring context.

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.

Using QueryDslRepositorySupport in combination with interface repositories

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)
}

Can I use inter-type declaration to add a property?

We have domain objects that extend an abstract base class to support a timestamp
abstract class TimestampedObject {
private Date timestamp;
public Date getTimestamp(){return timestamp;}
public void setTimestamp(final Date timestamp){this.timestamp = timestamp;}
}
But this clutters our hierarchy.
Could we use Spring AOP introductions or Aspectj ITDs to achieve this ?
An example right out of the AspectJ in Action book (from memory not tested) would go something like this:
public interface Timestamped {
long getTimestamp();
void setTimestamp();
public static interface Impl extends Timestamped {
public static aspect Implementation {
private long Timestamped.Impl.timestamp;
public long Timestamped.Impl.getTimestamp(){ return timestamp; }
public void Timestamped.Impl.setTimestamp(long in) { timestamp = in; }
}
}
//and then your classes would use it like this:
public class SomeClass implements Timestamped.Impl {
private void someFunc() {
setTimestamp(12);
long t = getTimestamp();
}
}
Not sure if the book had it that way or not but I usually create a separate Impl interface (as shown above) that just extends the main one so that some of my classes can implement timestamping differently without acquiring the ITD implementation. Like so :
public class SomeOtherClass implements Timestamped {
private long myOwnPreciousTimestamp;
public long getTimestamp() {
//Oh! I don't know should I give it to you?!
//I know, I will only give you a half of my timestamp
return myOwnPreciousTimestamp/2;
}
//etc.....
}
Yes, this is exactly what ITDs are for.

Resources