相当于 MyBatis XML MyBatis Guice 中的多个环境
Equivalent of MyBatis XML multiple environments in MyBatis Guice
我正在编写一项服务,需要根据上下文(一个简单的字符串标签)使用不同的数据库。每个数据库都有完全相同的模式。数据库列表是动态的。
浏览 MyBatis-Guice documentation on multiple data sources, the example is where the list of datasources are known upfront, and each datasource has a different mapper. Similarly, a question found here on SO 假定相同的要求。
如前所述,我的要求更具动态性和流动性。这个想法是将所有当前已知的数据库(及其连接信息)放在一个配置中,并在服务启动时对其进行解析。然后,根据任何传入请求的上下文,代码应该为正确的数据库提取 SqlSessionFactory。使用该 SqlSessionFactory 的所有下游代码完全相同 - 即不依赖于请求上下文。这意味着无论需要什么数据库,都使用相同的映射器。
我的 MyBatis 和 Guice 知识固然很新,也很有限。但是,我无法 google 显示 MyBatis-Guice 等同于 MyBatis 的 multiple environment approach supported by the XML configuration 的任何内容。
我想出了一个适合我的解决方案,所以我想在这里分享它。使用 Guice 的决定已经做出,因此没有回旋余地。
首先,我编写了一个用于注册单个数据源的MyBatis Guice 模块。它是一个 PrivateModule
以便所有为一个数据源注册的 MyBatis classes 不会与其他数据源的其他注册冲突。它使用内部 MyBatisModule 实现,因为 Java 不支持多重继承。意思是我们不能做 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:我使用 JdbcHelper.MySQL
作为将属性映射到连接字符串的快捷方式,并使用 com.mysql.jdbc.Driver
作为JDBC driver。它声明为:
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.
//
// i.e.
//
// bind( FredDao.class ).to( FredDaoMyBatis.class );
// bind( GingerDao.class).to( GingerDaoMyBatis.class );
}
}
现在我们只需要一些方法来获得正确的 Mapper class(它本身与正确的数据源相关联)。为此,我们实际上需要在 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,其中 returns 一个 SqlSessionFactory 委托给正确的数据源的 SqlSessionFactory。使用 ThreadLocal 确定基础 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();
}
}
我正在编写一项服务,需要根据上下文(一个简单的字符串标签)使用不同的数据库。每个数据库都有完全相同的模式。数据库列表是动态的。
浏览 MyBatis-Guice documentation on multiple data sources, the example is where the list of datasources are known upfront, and each datasource has a different mapper. Similarly, a question found here on SO 假定相同的要求。
如前所述,我的要求更具动态性和流动性。这个想法是将所有当前已知的数据库(及其连接信息)放在一个配置中,并在服务启动时对其进行解析。然后,根据任何传入请求的上下文,代码应该为正确的数据库提取 SqlSessionFactory。使用该 SqlSessionFactory 的所有下游代码完全相同 - 即不依赖于请求上下文。这意味着无论需要什么数据库,都使用相同的映射器。
我的 MyBatis 和 Guice 知识固然很新,也很有限。但是,我无法 google 显示 MyBatis-Guice 等同于 MyBatis 的 multiple environment approach supported by the XML configuration 的任何内容。
我想出了一个适合我的解决方案,所以我想在这里分享它。使用 Guice 的决定已经做出,因此没有回旋余地。
首先,我编写了一个用于注册单个数据源的MyBatis Guice 模块。它是一个 PrivateModule
以便所有为一个数据源注册的 MyBatis classes 不会与其他数据源的其他注册冲突。它使用内部 MyBatisModule 实现,因为 Java 不支持多重继承。意思是我们不能做 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:我使用 JdbcHelper.MySQL
作为将属性映射到连接字符串的快捷方式,并使用 com.mysql.jdbc.Driver
作为JDBC driver。它声明为:
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.
//
// i.e.
//
// bind( FredDao.class ).to( FredDaoMyBatis.class );
// bind( GingerDao.class).to( GingerDaoMyBatis.class );
}
}
现在我们只需要一些方法来获得正确的 Mapper class(它本身与正确的数据源相关联)。为此,我们实际上需要在 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,其中 returns 一个 SqlSessionFactory 委托给正确的数据源的 SqlSessionFactory。使用 ThreadLocal 确定基础 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();
}
}