使用mybatis时都是用的sqlmapper来做的数据库到java对象的映射,因此在针对一些特定数据库方言使用时无法在多个数据库上切换。
解决方案:
-
mybatis篇
思路:
通过定义environment的id来指定使用不同的数据库映射文件,如下
<!--WizRtf2Html Charset=0 --> - <?xml version="1.0" encoding="UTF-8" ?>
- <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
- <environments default="mysql">
- <environment id="mysql">
- <transactionManager type="JDBC" />
- <dataSource type="POOLED">
- <property name="driver" value="com.mysql.jdbc.Driver" />
- <property name="url" value="jdbc:mysql://localhost:3306/test" />
- <property name="username" value="root" />
- <property name="password" value="pwd" />
- </dataSource>
- </environment>
- </environments>
- <mappers>
- <mapper resource="cn/dcr/mybatis/entity/UserMapper.xml" />
- </mappers>
- </configuration>
environment的id是mysql映射文件是cn/dcr/mybatis/entity/UserMapper.xml
那么mybatis实际是读取的mysql/cn/dcr/mybatis/entity/UserMapper.xml
实现:
以org.apache.ibatis.builder.xml.XMLConfigBuilder类为蓝本创建一个新类org.apache.ibatis.builder.xml.XMLConfigBuilderEx
添加一个成员变量
private String dialect; 修改mapperElement方法 继承org.apache.ibatis.session.SqlSessionFactoryBuilder类创建一个新类org.apache.ibatis.session.SqlSessionFactoryBuilderEx用来加载org.apache.ibatis.builder.xml.XMLConfigBuilderEx
修改environmentsElement方法设置方言
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); dialect = id.toLowerCase();//设置方言 if (isSpecifiedEnvironment(id)) { TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); Environment.Builder environmentBuilder = new Environment.Builder(id).transactionFactory(txFactory).dataSource(dsFactory.getDataSource()); configuration.setEnvironment(environmentBuilder.build()); } } } }
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); InputStream inputStream; if (resource != null && url == null) { if(dialect != null){ resource = dialect + "/" + resource;//从方言指定位置查找 } ErrorContext.instance().resource(resource); inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (url != null && resource == null) { if(dialect != null){ url = dialect + "/" + url;//从方言指定位置查找 } ErrorContext.instance().resource(url); inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else { throw new BuilderException("A mapper element may only specify a url or resource, but not both."); } } } }
覆盖父类中的build方法
public SqlSessionFactory build(InputStream inputStream, String environment, Properties props) { try { XMLConfigBuilderEx parser = new XMLConfigBuilderEx(inputStream, environment, props); Configuration config = parser.parse(); return build(config); } catch (Exception e) { throw ExceptionFactory.wrapException( "Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
调用org.apache.ibatis.builder.xml.XMLConfigBuilderEx来加载配置文件
-
spring篇
思路:
自定义mybatis配置加载Bean在spring的配置文件中添加方言的配置让自定义Bean在加载mybatis的配置时可以使用不同的数据库映射文件,如下
<bean id= "sqlSessionFactory" class= "org.mybatis.spring.SqlSessionFactoryBeanEx"> <property name= "dataSource" ref= "dataSource" /> <property name= "configLocation" value= "classpath:configuration.xml" /> <property name= "mapperLocations" value= "classpath*:${jdbc.dialect}/mappers/*.xml" /> </bean>
${jdbc.dialect}在配置文件中设置,如mysql,那么spring会把mysql/mappers下的所有映射文件加载进来
实现:
以org.mybatis.spring.SqlSessionFactoryBean为蓝本创建org.mybatis.spring.SqlSessionFactoryBeanEx类将成员变量
修改为private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
并去掉setSqlSessionFactoryBuilder方法添加setSqlSessionFactoryBuilderEx方法private SqlSessionFactoryBuilderEx sqlSessionFactoryBuilderEx = new SqlSessionFactoryBuilderEx();
覆盖buildSqlSessionFactory方法
protected SqlSessionFactory buildSqlSessionFactory() throws IOException, IllegalAccessException, InstantiationException { XMLConfigBuilderEx xmlConfigBuilderEx; Configuration configuration;if (this.configLocation != null) { try { xmlConfigBuilderEx = new XMLConfigBuilderEx(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilderEx.parse(); } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); }if (this.logger.isDebugEnabled()) { this.logger.debug("Parsed configuration file: '" + this.configLocation + "'"); } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); }if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(this.dataSource); }Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);configuration.setEnvironment(environment);if (!ObjectUtils.isEmpty(this.mapperLocations)) { Map<String, XNode> sqlFragments = new HashMap<String, XNode>();for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; }// MyBatis holds a Map using "resource" name as a key. // If a mapper file is loaded, it searches for a mapper // interface type. // If the type is found then it tries to load the mapper file // again looking for this: // // String xmlResource = type.getName().replace('.', '/') + // ".xml"; // // So if a mapper interface exists, resource cannot be an // absolute path. // Otherwise MyBatis will throw an exception because // it will load both a mapper interface and the mapper xml file, // and throw an exception telling that a mapperStatement cannot // be loaded twice. String path; if (mapperLocation instanceof ClassPathResource) { path = ((ClassPathResource) mapperLocation).getPath(); } else { // this won't work if there is also a mapper interface in // classpath path = mapperLocation.toString(); }try { XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, path, sqlFragments); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); }if (this.logger.isDebugEnabled()) { this.logger.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Property 'mapperLocations' was not specified, only MyBatis mapper files specified in the config xml were loaded"); } }return this.sqlSessionFactoryBuilderEx.build(configuration); }