分析版本
我们先分析xml配置文件形式的 mybatis, 等我们分析完毕了, 可以去分析 mybatis 和 spring 的对接
pom.xml
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.2</version>
</dependency>
案例代码
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
AreaMapper mapper = sqlSession.getMapper(AreaMapper.class);
List<Area> all = mapper.getAll();
for (Area item : all)
System.out.println(item.getAreaName());
} finally {
sqlSession.close();
}
}
mybatis-config.xml
<?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>
<!-- 引入外部配置文件 -->
<properties resource="db.properties"></properties>
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
<environments default="default">
<environment id="default">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.test.first.batis.mapper.AreaMapper"></mapper>
</mappers>
</configuration>
mapper
public interface AreaMapper {
List<Area> getAll();
}
<mapper namespace="com.test.first.batis.mapper.AreaMapper">
<resultMap id="baseMap" type="com.test.first.batis.pojo.Area">
<result property="id" column="id" jdbcType="INTEGER"></result>
<result property="areaNo" column="area_no" jdbcType="VARCHAR"></result>
<result property="areaName" column="area_name" jdbcType="VARCHAR"></result>
</resultMap>
<select id="getAll" resultMap="baseMap">
select * from bd_area LIMIT 10
</select>
</mapper>
bd.propoerties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zhzg?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
我们从 build 开始分析源码
SqlSessionFactoryBuilder().build(inputStream)
public class SqlSessionFactoryBuilder {
// 默认构造器里面没有东西, 所以我们直接跳过吧
public SqlSessionFactoryBuilder() {
}
// 1
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
// 2, 最终的build
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建一个 xml 配置加载器, 解析mybatis-config.xml呗
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} 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.
}
}
}
}
class XMLConfigBuilder {
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
// 创建一个XPath 解析器
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
// 我们关注一下这个 new Configuration()
// Configuration 存储对 mybatis-config.xml 的对象解析, 把对象从xml 里面创建出来
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
parser.parse()
上面的代码我们已经看过 XMLConfigBuilder 是怎么构造的了,
build(parser.parse());
分为两步, 先解析, 再构建, 我们先分析解析
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 我们现在分析这个
return build(parser.parse());
} catch (Exception e) {
} finally {
}
}
class XMLConfigBuilder {
public Configuration parse() {
// 防止重复解析
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 获取 xml 树, configuration 也就是 mybatis-config.xml 最外层结构
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
// 我们不需要关注 root.evalNode("properties")
// 而是关注 解析节点之后做了什么
// 只带各位分析一点点
try {
// 1, 获取参数配置
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// 2, 创建事务工厂和数据源
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 3, 解析创建 mapper 代理对象
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
}
propertiesElement(root.evalNode(“properties”));
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/zhzg?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
class XMLConfigBuilder {
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
if (resource != null && url != null) {
// ... resource 和 url 只能存在一个, 否则抛异常
}
if (resource != null) {
// 我们进这个分支, 然后会把 db.properties 解析出来成 Properties
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
// 默认这里是个null
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
// 此时我们的 configuration 里面存入了连接信息
configuration.setVariables(defaults);
}
}
environmentsElement(root.evalNode(“environments”));
这一步的步骤是创建了 事务工厂 和 数据源
class XMLConfigBuilder {
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)) {
// 解析 environments > id = environment > transactionManager
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);
// 将事务工厂和数据源封装成 Environment
// 这里使用建造者模式
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
// 解析transactionManager
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
// 怎么知道这个实例是谁呢, 其实在 configuration 被初始化的时候就已经默认配置了
// type = JDBC
// 所以这个实例为 JdbcTransactionFactory.class
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
protected Class<?> resolveClass(String alias) {
if (alias == null) {
return null;
}
try {
return resolveAlias(alias);
} catch (Exception e) {
throw new BuilderException("Error resolving class. Cause: " + e, e);
}
}
protected Class<?> resolveAlias(String alias) {
return typeAliasRegistry.resolveAlias(alias);
}
}
Configuration 在构建的时候就已经加入了一批别名
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);
}
mapperElement(root.evalNode(“mappers”));
看方法名都知道肯定是跟mapper有关, 这个步骤就是将配置的mapper 全部创建一个代理对象
<mappers>
<mapper class="com.test.first.batis.mapper.AreaMapper"></mapper>
</mappers>
class XMLConfigBuilder {
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
// 如果配置的是包名就会扫码包路径下所有的mapper
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
// 我们案例只配置了class
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
// 本地路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
// 网络路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
// 进这个分支
Class<?> mapperInterface = Resources.classForName(mapperClass);
// 我们看 configuration.addMapper 都做了什么
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
}
class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// 防止重复配置 throw new BindingException....
}
boolean loadCompleted = false;
try {
// 创建了一个代理对象
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析mapper, Select, Insert, Update, Delete,
// 这个方法里面的 loadXmlResource() 就会去解析 xxxMapper.xml 文件
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
build(parser.parse())
OK, 我们回过去看build(parser.parse()); 中的build
build 就是使用 parse 以后的 Configuration 对象 ,创建一个DefaultSqlSessionFactory
class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} 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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
}