为多个数据源定制Spring Data存储库bean名称

12

我有一个项目,使用Spring Data(在这个例子中是MongoDB)与具有相同模式的多个数据库进行交互。这意味着每个数据库都使用相同的实体和存储库类。因此,例如:

public class Thing {
    private String id;
    private String name;
    private String type;
    // etc...  
}

public interface ThingRepository extends PagingAndSortingRepository<Thing, String> {
    List<Thing> findByName(String name);
}

@Configuration
@EnableMongoRepositories(basePackageClasses = { ThingRepository.class })
public MongoConfig extends AbstractMongoConfiguration {
    // Standard mongo config
}

如果我只连接一个数据库,这个方法可以正常工作。但是当我想要同时连接多个数据库时,情况就变得更加复杂了:

@Configuration
@EnableMongoRepositories(basePackageClasses = { ThingRepository.class },
    mongoTemplateRef = "mongoTemplateOne")
public MongoConfigOne extends AbstractMongoConfiguration {

    @Override
    @Bean(name = "mongoTemplateOne")
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(this.mongo(), "db_one");
    }

    // Remaining standard mongo config

}

@Configuration
@EnableMongoRepositories(basePackageClasses = { ThingRepository.class },
    mongoTemplateRef = "mongoTemplateTwo")
public MongoConfigTwo extends AbstractMongoConfiguration {

    @Override
    @Bean(name = "mongoTemplateTwo")
    public MongoTemplate mongoTemplate() throws Exception {
        return new MongoTemplate(this.mongo(), "db_two");
    }

    // Remaining standard mongo config

}
我可以使用不同的 MongoTemplate 实例创建相同存储库的多个实例,但我不知道正确的引用和注入方法。我想能够将各个存储库实例注入到不同的控制器中,如下所示:
我可以使用不同的 MongoTemplate 实例创建相同存储库的多个实例,但我不知道正确的引用和注入方法。我想能够将各个存储库实例注入到不同的控制器中。
@Controller
@RequestMapping("/things/one/")
public class ThingOneController {
    @Resource private ThingRepository thingRepositoryOne;
    ...
}

@Controller
@RequestMapping("/things/two/")
public class ThingTwoController {
    @Resource private ThingRepository thingRepositoryTwo;
    ...
}

像这样的配置是可能的吗? 我是否可以以某种方式控制已实例化接口的bean名称,以便我可以使用@Resource@Autowired引用它们?

奖励问题:是否也可以使用自定义存储库工厂来完成此操作?


你可能需要使用工厂手动实例化存储库实现,此时可以使用常规的@Bean技术对它们进行命名。 - chrylis -cautiouslyoptimistic-
@chrylis:你能提供一个例子作为答案吗?我不太确定创建工厂bean和存储库实例的最佳方式是什么样子的。 - woemler
我只是大致了解手动创建的过程,对此我无法给你很好的建议。Oliver Gierke 很可能会在几个小时内出现。 - chrylis -cautiouslyoptimistic-
2个回答

12

使用@NoRepositoryBean创建您的存储库接口,我们自己来进行连接:

@NoRepositoryBean
public interface ModelMongoRepository extends MongoRepository<Model, String> {
}      

然后,在一个@Configuration类中,使用MongoRepositoryFactoryBean实例化2个存储库bean。两个存储库将返回相同的Spring Data Repository接口,但我们将为它们分配不同的MongoOperations(即:数据库详细信息):

@Configuration
@EnableMongoRepositories
public class MongoConfiguration {

    @Bean
    @Qualifier("one")
    public ModelMongoRepository modelMongoRepositoryOne() throws DataAccessException, Exception {
        MongoRepositoryFactoryBean<ModelMongoRepository, Model, String> myFactory = new MongoRepositoryFactoryBean<ModelMongoRepository, Model, String>();
        myFactory.setRepositoryInterface(ModelMongoRepository.class);
        myFactory.setMongoOperations(createMongoOperations("hostname1", 21979, "dbName1", "username1", "password1"));
        myFactory.afterPropertiesSet();
        return myFactory.getObject();
    }

    @Bean
    @Qualifier("two")
    public ModelMongoRepository modelMongoRepositoryTwo() throws DataAccessException, Exception {
        MongoRepositoryFactoryBean<ModelMongoRepository, Model, String> myFactory = new MongoRepositoryFactoryBean<ModelMongoRepository, Model, String>();
        myFactory.setRepositoryInterface(ModelMongoRepository.class);
        myFactory.setMongoOperations(createMongoOperations("hostname2", 21990, "dbName2", "username2", "password2"));
        myFactory.afterPropertiesSet();
        return myFactory.getObject();
    }

    private MongoOperations createMongoOperations(String hostname, int port, String dbName, String user, String pwd) throws DataAccessException, Exception {
        MongoCredential mongoCredentials = MongoCredential.createScramSha1Credential(user, dbName, pwd.toCharArray());
        MongoClient mongoClient = new MongoClient(new ServerAddress(hostname, port), Arrays.asList(mongoCredentials));
        Mongo mongo = new SimpleMongoDbFactory(mongoClient, dbName).getDb().getMongo();
        return new MongoTemplate(mongo, dbName);
    }
    //or this one if you have a connection string
    private MongoOperations createMongoOperations(String dbConnection) throws DataAccessException, Exception {
        MongoClientURI mongoClientURI = new MongoClientURI(dbConnection);
        MongoClient mongoClient = new MongoClient(mongoClientURI);
        Mongo mongo = new SimpleMongoDbFactory(mongoClient, mongoClientURI.getDatabase()).getDb().getMongo();
        return new MongoTemplate(mongo, mongoClientURI.getDatabase());
    }
}

您现在拥有两个具有不同 @Qualifier 名称的豆子,每个豆子都针对不同的数据库进行配置,并使用相同的模型。

您可以使用 @Qualifier 注入它们:


@Autowired
@Qualifier("one")
private ModelMongoRepository mongoRepositoryOne;

@Autowired
@Qualifier("two")
private ModelMongoRepository mongoRepositoryTwo;

为简单起见,我在配置类中硬编码了值,但是您可以从application.properties/yml的属性中注入它们。 编辑以回答评论: 如果您想创建一个自定义实现而不失去spring数据接口存储库的好处,则可以进行修改。规范说:
通常需要为几个仓库方法提供自定义实现。Spring Data存储库很容易允许您提供自定义存储库代码并将其与通用CRUD抽象和查询方法功能集成。为了丰富存储库的自定义功能,您首先定义一个接口和自定义功能的实现。使用您提供的存储库接口来扩展自定义接口。最重要的一点是找到该类的Impl后缀与核心存储库接口上的名称进行比较(请参见下文)。
创建一个新接口,这与spring数据技术实际上没有任何关系,只是普通的接口。
public interface CustomMethodsRepository {
    public void getById(Model model){
}

让您的存储库接口扩展此新接口:

@NoRepositoryBean
public interface ModelMongoRepository extends MongoRepository<Model, String>, CustomMethodsRepository {
} 

然后,创建您的实现类,该类实现您的非Spring Data接口:

public class ModelMongoRepositoryImpl  implements CustomModelMongoRepository {
    private MongoOperations mongoOperations;

    public ModelMongoRepositoryImpl(MongoOperations mongoOperations) {
        this.mongoOperations = mongoOperations;
    }
    public void getById(Model model){
        System.out.println("test");
    }
}

将Java配置更改以添加myFactory.setCustomImplementation(new ModelMongoRepositoryImpl());

@Bean
@Qualifier("one")
public ModelMongoRepository modelMongoRepositoryOne() throws DataAccessException, Exception {
    MongoRepositoryFactoryBean<ModelMongoRepository, Model, String> myFactory = new MongoRepositoryFactoryBean<ModelMongoRepository, Model, String>();
    MongoOperations mongoOperations = createMongoOperations("hostname1", 21979, "dbName1", "usdername1", "password1");
    myFactory.setCustomImplementation(new ModelMongoRepositoryImpl(mongoOperations));
    myFactory.setRepositoryInterface(ModelMongoRepository.class);
    myFactory.setMongoOperations(mongoOperations);

    myFactory.afterPropertiesSet();
    return myFactory.getObject();
}

如果您不是通过Java配置手动连接仓库的话,那么这个实现类必须被命名为ModelMongoRepositoryImpl以匹配接口ModelMongoRepository +"Impl"。并且Spring会自动处理它。

在我添加了自定义方法的情况下,例如ThingRepository.findByName(),我该怎么办?或者如果我在ThingRepositoryImpl类中实现了自定义方法呢?我需要在Spring将注入MongoTemplate的具体类中手动实现它们吗?如果是这样,那么使用Spring Data的存储库工厂还有什么意义呢? - woemler
我明白你的意思了,我会更新代码,使用接口+MongoRepositoryFactoryBean。 - alexbt
谢谢,这看起来会起作用,但现在我卡在了如何将MongoTemplates注入到ThingRepositoryImpl中,如果我想自定义实现findByName(..)或另一个方法。 有什么想法吗? - woemler
在这里,我相信这是你一直在寻找的最终解决方案!请查看最后一部分(“编辑以回答评论”)。你可以用最少的代码实现你的目标。 - alexbt
成功了!在我的情况下,我还需要添加一行代码 myFactory.setRepositoryBaseClass(MyBaseRepository.class);,因为我有一个基础仓库类继承了 SimpleMongoRepository,但是除此之外,配置按照描述的方式工作。这对我帮助很大,谢谢! - woemler

2

对于一般的@Repository,您只需添加(value="someDao")来命名创建的Bean,如果MongoRepository扩展了Repository,那么这应该可以工作。


这对我没有帮助,因为每个存储库的多个实例正在被创建。使用@Repository(name="myRepository")将导致两个具有相同名称的bean。我想确保由存储库工厂bean创建的对象在程序上命名不同。 - woemler
你可以使用 @Autowire @Qualifier("mongoTemplateOne") 来实现这个。 - Gandalf
我并不是试图注入单独的 MongoTemplate 对象,而是试图注入它们用于创建的仓库。在我提供的示例中,您将如何注入使用任一数据源创建的 ThingRepository - woemler
你怎么从已连接的mongoTemplate获取仓库? - raok1997

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接