Spring Data Mongo Repository:: Common shared metho

2019-01-24 17:18发布

问题:

Use Case

I am trying to use Adding custom behaviour to all repositories functionality of Spring Data MongoDB.

The documentation unhelpfully describes how to connect using JPA. Anyways got the config setup with Mongo equivalent.

I want to add a findByCategoryName(String categoryName) method to all entities as all my entities will have a Category . Category is a DBRef object so have to use custom query.

Below is relevant part of the config

<!-- Activate Spring Data MongoDB repository support -->
<mongo:repositories base-package="com.domain.*.repo" repository-impl-postfix="CustomImpl" 
    factory-class="com.domain.commonrepo.CommonMongoRepoFactoryBean"/>

<bean id="mappingContext" class="org.springframework.data.mongodb.core.mapping.MongoMappingContext" />

<mongo:mapping-converter mapping-context-ref="mappingContext">
    <mongo:custom-converters base-package="com.domain.mongo.converter" />
</mongo:mapping-converter>

<bean id="entityInformationCreator" class="org.springframework.data.mongodb.repository.support.DefaultEntityInformationCreator">
    <constructor-arg name="mappingContext" ref="mappingContext" />
</bean>

. .

The FactoryBean

    @NoRepositoryBean
    public class CommonMongoRepoFactoryBean<T extends MongoRepository<?,?>, ID extends        
    Serializable> extends MongoRepositoryFactoryBean{

@Autowired
private static MongoTemplate mongoTemplate;

protected MongoRepositoryFactory getRepositoryFactory(Class<T> clazz) {
    return new CommonMongoRepoFactory(clazz);
}

private static class CommonMongoRepoFactory extends MongoRepositoryFactory {
    private Class clazz;

    public CommonMongoRepoFactory(Class clazz) {
        super(mongoTemplate);
        this.clazz = clazz;
    }

    public CommonMongoRepoImpl getTargetRepository() {
        return new CommonMongoRepoImpl(clazz);
    }

    public Class<?> getRepositoryClass() {
        return CommonMongoRepoImpl.class;
    }
}

I know it's a bit of a hack but with no documentation it is a pain. If anyone knows better PLEASE give me a github link :-)

Common Repo interface

    @NoRepositoryBean
    public interface CommonMongoRepo<T, ID extends Serializable> extends MongoRepository<T,ID> {

public List<T> findByCategoryName(String categoryName);        

Implementation

    @NoRepositoryBean
    public class CommonMongoRepoImpl<T, ID extends Serializable> extends SimpleMongoRepository<T,    
    ID> implements CommonMongoRepo<T, ID> {

private Class<T> type;

@Autowired
private static MongoTemplate mongoOperations;

@Autowired
private static EntityInformationCreator entityInformationCreator;

@Autowired
private CategoryRepo categoryRepo;

public CommonMongoRepoImpl(Class<T> type) { 
    super((MongoEntityInformation<T, ID>) entityInformationCreator.getEntityInformation(type), mongoOperations);
}

@Override
public List<T> findByCategoryName(String categoryName) {

    Category category = categoryRepo.findByName(categoryName);

    return mongoOperations.find(query(where("categories.$id").is(category.getId())), type);
}

PROBLEM

Now when I am trying to use the common method I get an exception

No Property category found in "Entity". Which is I guess when mongo repo is trying to auto implement the method. This is inspite of me declaring the bean as @NoRepositoryBean

PLEASE HELP!!! Dont want to add the same custom method to all the entities

回答1:

Here is the best solution!

Step One:
Add a custom method to interface!
增加一个自定义的方法

#custom interface

/**
 * Basic Repository for common custom methods
 * @author liangping
 */

import java.io.Serializable;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

@NoRepositoryBean
public interface WootideRepositoryCustom <T, ID extends Serializable>
      extends PagingAndSortingRepository<T, ID>, MongoRepository<T, ID> {

      public Page<T> search(Query query, Pageable pageable);
}

Implementation

Step Two:
Add implement for your custom method!
实现你的自定义方法

/**
 * implement for wootide basic repository
 * @author liangping
 */

import java.io.Serializable;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;

public class WootideRepositoryImpl<T, ID extends Serializable> extends
        SimpleMongoRepository<T, ID> implements WootideRepositoryCustom<T, ID> {

    public WootideRepositoryImpl(MongoEntityInformation<T, ID> metadata,
            MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
    }

    @Override
    public Page<T> search(Query query, Pageable pageable) {
        long total = this.getMongoOperations().count(query, this.getEntityInformation().getJavaType() );
        return new PageImpl<T>(this.getMongoOperations().find(query.with(pageable), this.getEntityInformation().getJavaType()), pageable, total);
    }

}

Create a new factory for custom repository

/**
 * Repository Factory for all Subrepository
 * @author liangping
 */

import java.io.Serializable;

import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.mapping.BasicMongoPersistentEntity;
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MappingMongoEntityInformation;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;

public class WootideRepositoryFactoryBean<R extends MongoRepository<T, I>, T, I extends Serializable>
        extends MongoRepositoryFactoryBean<R, T, I> {

    @Override
    protected RepositoryFactorySupport getFactoryInstance(
            MongoOperations operations) {
        return new WootideMongoRepositoryFactory<T,I>( operations );
    }

    private static class WootideMongoRepositoryFactory<T, ID extends Serializable>
            extends MongoRepositoryFactory {

        private MongoOperations mongo;
        public WootideMongoRepositoryFactory(MongoOperations mongoOperations) {
            super(mongoOperations);
            this.mongo = mongoOperations;
        }

        @SuppressWarnings("unchecked")
        protected Object getTargetRepository(RepositoryMetadata metadata) {

            TypeInformation<T> information =  ClassTypeInformation.from((Class<T>)metadata.getDomainType());
            MongoPersistentEntity<T> pe = new BasicMongoPersistentEntity<T>(information);
            MongoEntityInformation<T,ID> mongometa = new MappingMongoEntityInformation<T, ID>(pe);

            return new WootideRepositoryImpl<T, ID>( mongometa,  mongo);
        }

        protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
            return WootideRepositoryCustom.class;
        }
    }
}

Make it works

<mongo:repositories base-package="com.***.mongodb" 
factory-class="com.***.mongodb.custom.WootideRepositoryFactoryBean"/>

Good Luck! 祝你好运!



回答2:

Somewhat delayed but here is sample code that does this for a Spring web app project. The salient points are:

  1. Interface used in Controller
  2. Implementation done in a separate class that inherits from a base
  3. The base implementation provides common methods that any other Controller can use with just a quick inheritance