简体   繁体   中英

MyBatis multiple data sources using single mapper

I'm working on a system that uses guice to bind and inject MyBatis mappers used to remove entries from different DBs. The fact is that all DB are located in different hosts but have the same structure. Since there are a lot of them and the number and location of the hosts change quite often, I would like to install a MyBatis module with different data sources that are loaded dynamically using the same mapper.

I've been looking around but can't figure out how to solve the mapper ambiguity. I also took a look to MyBatis beans CDI plugin, that makes it easier to add named mappers with multiple data sources, but still can't get it working since I don't have a fixed list of data sources that i can name.

Am I missing an easy way to achieve this?

You need to bind your MyBatisModule privately and expose the mappings with a unique binding attribute. I've got an example below. I've verified that it works, too :)

DaoModule: This module is setup to bind a single mapper to a key with a specific data-source. Note that this class is extending a "PrivateModue" and it's exposing the key to the parent module. You'll be using this key to inject the mapping.

public class DaoModule<T> extends PrivateModule {

    private static final String ENVIRONMENT_ID = "development";

    private final Key<T> key;
    private final Class<T> mapper;
    private final Provider<DataSource> dataSourceProvider;

    public DaoModule(Key<T> key, Class<T> mapper, Provider<DataSource> dataSourceProvider) {
        this.key = key;
        this.mapper = mapper;
        this.dataSourceProvider = dataSourceProvider;
    }

    @Override
    protected void configure() {
        install(new InnerMyBatisModule());
        expose(key);
    }

    private class InnerMyBatisModule extends MyBatisModule {
        @Override
        protected void initialize() {
            bind(key).to(mapper);
            addMapperClass(mapper);

            environmentId(ENVIRONMENT_ID);
            bindDataSourceProvider(dataSourceProvider);
            bindTransactionFactoryType(JdbcTransactionFactory.class);
        }
    }
}

MyModule: This module installs two DaoModules with the same mapper type by two different keys and different data-sources.

public class MyModule extends AbstractModule {

    @Override
    protected void configure() {
        Key<MapperDao> key1 = Key.get(MapperDao.class, Names.named("Mapper1"));
        Provider<DataSource> datasource1 = null;

        Key<MapperDao> key2 = Key.get(MapperDao.class, Names.named("Mapper2"));
        Provider<DataSource> datasource2 = null;

        install(new DaoModule<MapperDao>(key1, MapperDao.class, datasource1));
        install(new DaoModule<MapperDao>(key2, MapperDao.class, datasource2));
    }
}

Main: And the main acquires the two mappers of the same type but with different data-sources.

public class Main {
    public static void main(String... args) {
        Injector i = Guice.createInjector(new MyModule());

        MapperDao mapper1 = i.getInstance(Key.get(MapperDao.class, Names.named("Mapper1")));
        MapperDao mapper2 = i.getInstance(Key.get(MapperDao.class, Names.named("Mapper2")));
    }
}

Example Injection Class: This shows how to use field injection to inject the mappers

public class MyExampleClass {

   @Inject
   @Named("Mapper1")
   MapperDao mapper1;

   @Inject
   @Named("Mapper2")
   MapperDao mapper2;

}

This answer is for slightly different scope than the question. For anyone who has fixed number of datasources and needs to share a mapper, there is also a solution without using @Named the way it is described in accepted answer.

You can simply use

interface SomeMapperForDbA extends SomeMapper {}

and add + expose SomeMapperForDbA in the corresponding PrivateModule .

Interface name here acts as a logical data source discriminator, while all the mapping queries stil stay intact in one place in SomeMapper . There are pros and cons to this approach vs named injects, but it works and might save the day for some.

Obviously, you need to inject SomeMapperForDbA to use the DbA data source. That said, it can neatly be done in constructor only, while the class member type used in the actual code can just be the SomeMapper to avoid confusion.

Alternatively, you could add some DbA-specific selects to SomeMapperForDbA , if databases have common and different parts etc. In this case I would suggest a better name, that reflects such logic.

Ie don't be afraid to extend mapper interfaces when needed.

org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource is designed for this purpose.

https://www.baeldung.com/spring-abstract-routing-data-source

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM