Equivalent of MyBatis XML multiple environments in MyBatis Guice

旧城冷巷雨未停 提交于 2019-12-06 04:25:42

I managed to come up with a solution that works for me, so thought I'd share it here. The decision to use Guice had already been made, so there was no wriggle room there.

First, I wrote a MyBatis Guice module for registering a single datasource. It is a PrivateModule so that all the MyBatis classes that get registered for one datasource do not conflict with other registrations for other datasources. It makes use of an internal MyBatisModule implementation because Java doesn't support multiple inheritance. Meaning we can't do public class MyMyBatisModule extends PrivateModule, MyBatisModule {...}.

public class MyMyBatisModule extends PrivateModule {

    private final String     datasourceLabel;
    private final Properies  datasourceProperties;
    private       List< Key<?> > exposedKeys = new ArrayList< Key<?> >();

    public MyMyBatisModule( String datasourceLabel, Properties datasourceProperties ) {

        this.datasourceLabel = datasourceLabel;
        this.datasourceProperties = datasourceProperties;
    }

    @Override
    protected void configure() {

        install( new InternalMyMyBatisModule( ) );

        for( Key<?> key: keys ) {
            expose( key );
        }
    }

    private class InternalMyMyBatisModule extends MyBatisModule {

        @Override
        protected void initialize( ) {

            environmentId( datasourceLabel );
            Names.bindProperties( binder(), properties );

            install( JdbcHelper.MySQL ); // See JDBC Helper commentary below

            bindDataSourceProviderType( C3p0DataSourceProvider.class ); // Choose whichever one you want
            bindTransactionFactoryType( JdbcTransactionFactory.class );

            // Register your mapper classes here.  These mapper classes will have their
            // keys exposed from the PrivateModule
            //
            // i.e.
            // 
            // keys.add( registerMapper( FredMapper.class );
            // kets.add( registerMapper( GingerMapper.class );
        }

        private <T> Key<T> registerMapper( Class<T> mapperClass ) {
            Key<T> key = Key.get( mapperClass, Names.named( datasourceLabel ) );
            bind( key ).to( mapperClass );
            addMapperClass( mapperClass );
            return key;
        }
    }
}

JdbcHeler.MySQL: I've used JdbcHelper.MySQL as a shortcut to map properties to the connection string, and use com.mysql.jdbc.Driver as the JDBC driver. It's declared as:

MySQL("jdbc:mysql://${JDBC.host|localhost}:${JDBC.port|3306}/${JDBC.schema}", "com.mysql.jdbc.Driver"),

Now it's time to register all your datasources. MyBatisModules handles this for us. It requires a map of datasourceLabel to jdbc properties.

public class MyBatisModules extends AbstractModule {

    private Map< String, Properties > connectionsProperties;

    public MyBatisModules( Map< String, Properties > = new HashMap< String, Properties > connectionsProperties ) {
        this.connectionsProperties = connectionsProperties; // consider deep copy if appropriate
    }

    @Override
    protected void configure( ) {

        for( Entry< String, Properties > datasourceConnectionProperties : this.connectionsProperties.entrySet() ) {
            install( new MyMyBatisModule( datasourceConnectionProperties.getKey(), datasourceConnectionProperties.getValue() ) );
        }

        bind( MapperRetriever.class ); // See MapperRetriever later

        // bind your DAO classes here.  By wrapping MyBatis Mapper use in DAO implementations, theoretically we
        // can fairly easily change from MyBatis to any other database library just by changing the DAO implementation.
        // The rest of our codebase would remain the same.
        //
        // i.e.
        //
        // bind( FredDao.class ).to( FredDaoMyBatis.class );
        // bind( GingerDao.class).to( GingerDaoMyBatis.class );
    }
}

Now we just need some way of getting the right Mapper class (which itself is associated with the right datasource). To do this, we actually need to call a method on the Guice Injector. I don't really like the idea of passing that around, so I wrapped it in MapperRetriever. You need to implement a retrieval method for each of your Mappers.

public class MapperRetriever {

    private final Injector injector;

    @Inject
    public MapperRetriver( Injector injector ) {
        this.injector = injector;
    }

    // The follwing two methods use the example Mappers referenced in the MyMyBatisModule implementation above

    public FredMapper getFredMapper( String datasourceLabel ) {
        return this.injector.getInstance( Key.get( FredMapper.class, Names.named( datasourceLabel ) ) );
    }

    public GingerMapper getGingerMapper( String datasourceLabel ) {
        return this.injector.getInstance( Key.get( GingerMapper.class, Names.named( datasourceLabel ) ) );
    }
}

And an example DAO implementation ...

public interface FredDao {
    Fred selectFred( String datasourceLable, String fredId );
}    

public class FredDaoMyBatis implements FredDao {

    private MapperRetriever mapperRetriever;

    @Inject
    public FredDaoMyBatis( MapperRetriever mapperRetriever ) {
        this.mapperRetriever = mapperRetriever;
    }

    @Override
    public Fred selectFred( String datasourceLabel, String fredId ) {
        FredMapper fredMapper = this.mapperRetriever.getFredMapper( datasourceLabel );
        return fredMapper.getFred( fredId );
    }
}

You can also create a custom SqlSessionFactoryProvider which returns a SqlSessionFactory which delegates to the correct DataSource's SqlSessionFactory. Using a ThreadLocal to determine the underlying SqlSessionFactory.

public class DelegatingSqlSessionFactory implements SqlSessionFactory {
    private final Map<String, SqlSessionFactory> factories = new HashMap<>();

    public DelegatingSqlSessionFactory(Map<String, DataSource> dataSources) throws ClassNotFoundException {
        dataSources.forEach((key, ds) -> {
            factories.put(key, createSqlSessionFactory(ds));
        });
    }

    private SqlSessionFactory delegate() {
        // Read from a ThreadLocal to determine correct SqlSessionFactory key
        String key = findKey();
        return factories.get(key);
    }

    @Override
    public SqlSession openSession() {
        return delegate().openSession();
    }

    @Override
    public SqlSession openSession(boolean autoCommit) {
        return delegate().openSession(autoCommit);
    }

    @Override
    public SqlSession openSession(Connection connection) {
        return delegate().openSession(connection);
    }

    @Override
    public SqlSession openSession(TransactionIsolationLevel level) {
        return delegate().openSession(level);
    }

    @Override
    public SqlSession openSession(ExecutorType execType) {
        return delegate().openSession(execType);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
        return delegate().openSession(execType, autoCommit);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
        return delegate().openSession(execType, level);
    }

    @Override
    public SqlSession openSession(ExecutorType execType, Connection connection) {
        return delegate().openSession(execType, connection);
    }

    @Override
    public Configuration getConfiguration() {
        return delegate().getConfiguration();
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!