情景
在一个仅仅作为入库的项目中使用mybatis,由于不想使用mybatis中自带的数据源而引发的使用其他数据源时该如何做,本次写一个简单的例子,使用阿里开源的durid数据源
源码地址:
pom.xml配置
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.26</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>
mybatis源码分析
mybatis-conf.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="DRUID_POOLED">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="******"/>
<property name="password" value="******"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/mapper/studentMapper.xml"/>
</mappers>
</configuration>
mybatais读取完mybatis-conf.xml配置后真正的解析是有XMLConfigBuilder类中解析,解析的过程在parseConfiguration方法中,解析mybatis使用的数据源是在这个方法的解析mybatis环境配置步骤中environmentsElement(root.evalNode("environments"))
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //进行mybatis的事务管理器和数据源的解析
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
这个方法主要是解析mybatis使用的事务管理器和数据源
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");
if (isSpecifiedEnvironment(id)) {
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
child.evalNode("dataSource")获取到mybatis-conf.xml中的dataSource节点交给dataSourceElement方法处理
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
dataSourceElement方法获取该节点的type属性去resolveClass方法中获取和type属性字符串对应的DataSourceFactory实现类,跟踪这个方法知道在XMLConfigBuilder父类BaseBuilder中有一个TypeAliasRegistry类型的类型别名注册器typeAliasRegistry,这个对象是从Configuration中获取
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
}
}
在实例化XMLConfigBuilder时同时实例化一个Configuration对象交给父类
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
实例化Configuration对象时,向这个别名注册注册可能使用到的类型,加粗的就是mybatis内置的三种数据源工厂
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
这样就可以去实现一个DataSourceFactory接口的类,模仿着PooledDataSourceFactory工厂类
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
写一个DruidDataSourceSqlSessionFactory工厂类,这个数据源是druid数据源
public class DruidDataSourceSqlSessionFactory extends UnpooledDataSourceFactory{
public DruidDataSourceSqlSessionFactory() {
this.dataSource = new DruidDataSource();
}
}
现在需要将DruidDataSourceSqlSessionFactory工厂类注册到Configuration中经过查看,在实例化SqlSessionFactoryBuilder时同时实例化一个XMLConfigBuilder,而这个parser对象中就有需要的Configuration对象,在创建完parser之后和return之前将DruidDataSourceSqlSessionFactory注册到Configuration中
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
写一个DataSourceSqlSessionFactoryBuilder类继承SqlSessionFactoryBuilder重写build方法,将DruidDataSourceSqlSessionFactory注册到Configuration对象的typeAliasRegistry对象中
public SqlSessionFactory build(Reader reader, String environment,
Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment,
properties);
parser.getConfiguration().getTypeAliasRegistry().registerAlias("DRUID_POOLED", DruidDataSourceSqlSessionFactory.class);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.",
e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
通过这个配置就可以使用到注册进去的数据源
<dataSource type="DRUID_POOLED">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="******"/>
<property name="password" value="******"/>
</dataSource>
使用方法
public class MybatisTest {
@Test
public void test1(){
InputStream is = getClass().getClassLoader().getResourceAsStream("mybatis/mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new DataSourceSqlSessionFactoryBuilder().build(is);
SqlSession session = sqlSessionFactory.openSession(true);
StudentMapper mapper = session.getMapper(StudentMapper.class);
Student stu = new Student();
stu.setId(4);
stu.setName("小明");
mapper.add(stu);
}
}
还有一种方法,就是将实现的工厂类全名复制给type这样就需要将DataSourceSqlSessionFactoryBuilder注册进Configuration中
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="com.ws.extend.mybatis.DruidDataSourceSqlSessionFactory">
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
因为根据别名获取不到时就会根据类全名反射获取该类型
public <T> Class<T> resolveAlias(String string) {
try {
if (string == null) return null;
String key = string.toLowerCase(Locale.ENGLISH); // issue #748
Class<T> value;
if (TYPE_ALIASES.containsKey(key)) {
value = (Class<T>) TYPE_ALIASES.get(key);
} else {
value = (Class<T>) Resources.classForName(string);
}
return value;
} catch (ClassNotFoundException e) {
throw new TypeException("Could not resolve type alias '" + string + "'. Cause: " + e, e);
}
}