相当于MyBatis Guice中的MyBatis XML多环境

我正在编写一个需要根据上下文使用不同数据库的服务(一个简单的字符串标签)。 每个数据库都具有完全相同的模式。 数据库列表是动态的。

查看有关多个数据源的MyBatis-Guice文档 ,该示例是预先知道数据源列表的位置,每个数据源都有不同的映射器。 同样, 这里在SO上发现的问题也有相同的要求。

如上所述,我的要求更加动态和流畅。 我们的想法是将所有当前已知的数据库(及其连接信息)放在配置中,并在服务启动时进行解析。 然后,根据任何传入请求的上下文,代码应该为正确的数据库提取SqlSessionFactory。 使用该SqlSessionFactory的所有下游代码完全相同 – 即不依赖于请求上下文。 这意味着无论使用何种数据库,都使用相同的映射器。

我的MyBatis和Guice知识无疑是新的和有限的。 但是,我无法谷歌任何显示MyBatis-Guice等同于MyBatis 的XML配置支持的多环境方法的内容。

我设法提出了一个适合我的解决方案,所以我想在这里分享一下。 使用Guice的决定已经完成,所以那里没有蠕动的空间。

首先,我编写了一个MyBatis Guice模块,用于注册单个数据源。 它是一个PrivateModule因此为一个数据源注册的所有MyBatis类都不会与其他数据源的其他注册冲突。 它使用内部MyBatisModule实现,因为Java不支持多重inheritance。 意思是我们不能做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 // // ie // // keys.add( registerMapper( FredMapper.class ); // kets.add( registerMapper( GingerMapper.class ); } private  Key registerMapper( Class mapperClass ) { Key key = Key.get( mapperClass, Names.named( datasourceLabel ) ); bind( key ).to( mapperClass ); addMapperClass( mapperClass ); return key; } } } 

JdbcHeler.MySQL :我使用JdbcHelper.MySQL作为将属性映射到连接字符串的快捷方式,并使用com.mysql.jdbc.Driver作为JDBC驱动程序。 它被声明为:

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

现在是时候注册所有的数据源了。 MyBatisModules为我们处理这个问题。 它需要datasourceLabel到jdbc属性的映射。

 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. // // ie // // bind( FredDao.class ).to( FredDaoMyBatis.class ); // bind( GingerDao.class).to( GingerDaoMyBatis.class ); } } 

现在我们只需要一些方法来获得正确的Mapper类(它本身与正确的数据源相关联)。 为此,我们实际上需要在Guice Injector上调用一个方法。 我真的不喜欢传递它的想法,所以我把它包装在MapperRetriever 。 您需要为每个Mapper实现一个检索方法。

 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 ) ) ); } } 

并举例说明DAO实现……

 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 ); } } 

您还可以创建一个自定义SqlSessionFactoryProvider,它返回一个SqlSessionFactory,它委托给正确的DataSource的SqlSessionFactory。 使用ThreadLocal确定底层的SqlSessionFactory。

 public class DelegatingSqlSessionFactory implements SqlSessionFactory { private final Map factories = new HashMap<>(); public DelegatingSqlSessionFactory(Map 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(); } }