Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend SimpleMongoRepository from spring-data mongodb?

I am trying to create a BaseDAO interface which can be extended by all DAOs. The project uses spring-data with mongodb. The problem is that if I make all the individual DAOs extend MongoRepository and not write an Implementation class, then everything works fine. But if I try to add the MongoRepository to the BaseDAO interface with generics, the app doesn't work anymore because the parameters required to instantiate SimpleMongoRepository are null. This is the code I have so far :

BaseDAO.java

import com.test.mongodb.BaseEntity;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

@NoRepositoryBean
public interface BaseDAO<T extends BaseEntity, ID extends Serializable> extends         MongoRepository<T, ID> {
    public T getTestObject(ID id);
}

BaseDAOImpl.java

import com.test.mongodb.BaseEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.query.EntityInformationCreator;
import org.springframework.data.mongodb.repository.query.MongoEntityInformation;
import org.springframework.data.mongodb.repository.support.SimpleMongoRepository;
import org.springframework.data.repository.NoRepositoryBean;

import java.io.Serializable;

@NoRepositoryBean
public class BaseDAOImpl<T extends BaseEntity, ID extends Serializable> extends     SimpleMongoRepository<T,
        ID> implements BaseDAO<T, ID> {

    @Autowired
    private static MongoTemplate mongoTemplate;

    @Autowired
    private static EntityInformationCreator entityInformationCreator;

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

    @Override
    public T getTestObject(ID id){
        return findOne(id);
    }
}

UserDAO.java

import com.test.mongodb.User;
import org.springframework.stereotype.Repository;

@Repository
public interface UserDAO extends BaseDAO<User, String> {}

UserDAOImpl.java

import com.test.mongodb.User;
import org.springframework.stereotype.Repository;

@Repository
public class UserDAOImpl extends BaseDAOImpl<User, String> implements UserDAO {

    public UserDAOImpl(){
        super(User.class);
    }
}

applicationContext.xml

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xmlns:mongo="http://www.springframework.org/schema/data/mongo"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
    http://www.springframework.org/schema/data/mongo http://www.springframework.org/schema/data/mongo/spring-mongo-1.1.xsd">

    <!-- MongoFactoryBean instance -->
    <mongo:mongo host="localhost" port="27017" />

    <mongo:db-factory dbname="bank" mongo-ref="mongo" />

    <!-- MongoTemplate instance -->
    <bean id="mongoTemplate" class="org.springframework.data.mongodb.core.MongoTemplate">
        <constructor-arg name="mongoDbFactory" ref="mongoDbFactory" />
    </bean>

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

    <bean id="entityInformationCreator" class="org.springframework.data.mongodb.repository.support.DefaultEntityInformationCreator">
        <constructor-arg name="mappingContext" ref="mappingContext" />
    </bean>
    <mongo:repositories base-package="com.test.mongodb.repo"/>
</beans>

App.java

public class App {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

        UserRepository userRepository = context.getBean("userRepository", UserRepository.class);

        User user = new User("Test User");

        userRepository.save(user);

        String id = user.getId();
        System.out.println(id);

        System.out.println(userRepository.getTestObject(user.getId()));
    }
}

So when I run it, I get a NPE in BaseDAOImpl because both the mongoTemplate and the entityInformationCreator are null. How do I load them in? I also looked at the Spring MongoDB reference document but it mostly says to follow the documentation for other kind of repositories. The only thing I could find there and other places online was to create a factory bean. So I tried with that as well :

MongoRepoFactoryBean.java

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;

import java.io.Serializable;

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

    protected RepositoryFactorySupport createRepositoryFactory(Class<T> clazz, MongoTemplate mongoTemplate) {
        return new MongoRepoFactory(clazz, mongoTemplate);
    }

    private static class MongoRepoFactory extends MongoRepositoryFactory {
        private Class clazz;
        private MongoTemplate mongoTemplate;

        public MongoRepoFactory(Class clazz, MongoTemplate mongoTemplate) {
            super(mongoTemplate);
            this.mongoTemplate = mongoTemplate;
            this.clazz = clazz;
        }

        public Object getTargetRepository() {
            return new BaseDAOImpl(clazz);
        }

        public Class<?> getRepositoryBaseClass() {
            return BaseDAOImpl.class;
        }
    }
}

and changed the applicationContext.xml with :

<mongo:repositories base-package="com.test.mongodb.repo"
                    factory-class="com.test.mongodb.repo.MongoRepoFactoryBean"/>

But that doesn't work either. I tried with JavaConfig as well, but I don't know how to set the factory-class when doing the configuration using annotations. What am I doing wrong? SimpleMongoRepository doesn't come with a default constructor. Is the problem in injecting static fields?

like image 223
imgr8 Avatar asked Oct 21 '22 19:10

imgr8


1 Answers

Reference documentation is outdated, doesn't reflect the changes on spring-data-mongodb 1.1.1.RELEASE.

I had the same problem as you, here is how I workaround-ed it.

Note that I am using both MongoConfig class and app context XML, but you really don't need both, I just decided to do it that way to exemplify their usage.

MongoConfig class: (You can also do this via spring app context XML)

@Configuration
public class MongoConfig extends AbstractMongoConfiguration {
    @Override
    protected String getDatabaseName() {
        return "myDb";
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        return new Mongo("localhost");
    }
}

Spring App Context XML:

<!-- You can also expose the bean as a method in the MongoConfig class -->
<bean id="mongoRepositoryFactory" class="org.springframework.data.mongodb.repository.support.MongoRepositoryFactory">
    <!-- mongoTemplate comes from AbstractMongoConfiguration -->
    <constructor-arg ref="mongoTemplate" />
</bean>

<context:annotation-config />
<context:component-scan base-package="com.example.domain" />
<mongo:repositories base-package="com.example.domain.repository" mongo-template-ref="mongoTemplate" />

My repository/dao class:

@Repository
public class MyBeanDao extends SimpleMongoRepository<MyBean, String> {

    public MyBeanDao(MongoEntityInformation<MyBean, String> metadata, MongoOperations mongoOperations) {
        super(metadata, mongoOperations);
    }

    @Autowired
    public MyBeanDao(MongoRepositoryFactory factory, MongoOperations mongoOperations) {
        this(factory.<MyBean, String>getEntityInformation(MyBean.class), mongoOperations);
    }

    ... // more stuff
}

I know this is really ugly and only works on Java 6, but since the return type of getEntityInformation() is totally unbounded, it's either that or have the ID type be Serializable instead of your actual ID type and do the explicit cast every time you need it.

I wish there was a cleaner way but I've been looking quite some time through the API and even the source code and haven't figured out a better way to do it without coming up with your own implementation of some of the spring-data-mongodb classes.

Hope it helps.

like image 197
Russell Avatar answered Nov 01 '22 11:11

Russell