- 三哥
内容来自【自学星球】
欢迎大家来了解我的星球,和星主(也就是我)一起学习 Java ,深入 Java 体系中的所有技术。我给自己定的时间是一年,无论结果如何,必定能给星球中的各位带来点东西。
想要了解更多,欢迎访问👉:自学星球
--------------SSM系列源码文章及视频导航--------------
创作不易,望三连支持!
SSM源码解析视频
👉点我
Spring
SpringMVC
MyBatis
---------------------【End】--------------------
源码分析之前,我们还是老套路的来搭建一个 MyBatis 的小案例,方便后续的源码分析。
1、引入依赖
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.25version>
dependency>
<dependency>
<groupId>org.mybatisgroupId>
<artifactId>mybatisartifactId>
<version>3.4.5version>
dependency>
2、添加配置文件:myBatisConfig.xml
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties">properties>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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 resource="cn/j3code/studyspring/mybatis/helloworld/mapper/UserMapper.xml"/>
mappers>
configuration>
3、编写jdbc连接配置:jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/my_utils?useUnicode=true&useSSL=false&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=root
4、编写实体类:User
public class User {
private String name;
private Integer age;
// get,set,toString
}
5、编写mapper文件
public interface UserMapper {
/**
* 查询所有用户
*
* @return
*/
List<User> findAll();
/**
* 根据用户id查询用户
*
* @param id
* @return
*/
User findById(int id);
}
6、编写 UserMapper.java 对应的 UserMapper.xml ,记住两者的包路径名称记得保持一致。
<mapper namespace="cn.j3code.studyspring.mybatis.helloworld.mapper.UserMapper">
<select id="findAll" resultType="cn.j3code.studyspring.mybatis.helloworld.User">
select * from t_user;
select>
<select id="findById" parameterType="int" resultType="cn.j3code.studyspring.mybatis.helloworld.User">
select * from t_user where id=#{id};
select>
mapper>
7、测试(记得测试前把 SQL 创建好)
public class MyBatisUserMain {
public static void main(String[] args) throws Exception {
//读取配置文件流
InputStream in = Resources.getResourceAsStream("myBatisConfig.xml");
// sqlsessionfactory对象
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
// sqlsession对象
SqlSessionFactory sqlSessionFactory = builder.build(in);
// 获得会话
session = sqlSessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
System.out.println(userMapper.findAll());
}
}
很显然,MyBatis 的重要功能逻辑都体现在了第七步骤中。首先 MyBatis 通过读取我们编写的配置文件构建出 SqlSessionFactory 对象出来,然后通过 SqlSessionFactory 对象创建出操作 MySQL 的 SqlSession ,最后我们就可以通过 SqlSession 对象操作数据库。
下面我们就来分析源码把!
通过案例可知,SqlSessionFactory 对象是通过 build 方法构建出来的,所以我们的源码分析入口就在这:
SqlSessionFactory sqlSessionFactory = builder.build(in);
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建配置文件解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 调用 parser.parse() 方法解析配置文件,生成 Configuration 对象
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.
}
}
}
我们的 SqlSessionFactory 对象是通过 SqlSessionFactoryBuilder 的 build 方法创建出来的,而 build 方法内部是通过一个 XMLConfigBuilder 对象解析mybatisConfig.xml 文件生成一个 Configuration 对象,最后调用 DefaultSqlSessionFactory 构造器将 Configuration 对象传入生成我们所要的 SqlSessionFactory 。
抓住关键点 Configuration 对象,在 MyBatis 的配置文件中我们的所有配置都只能在 标签中配置,所以 标签配置的值在 Configuration 对象中都有对应的属性,即他们是一一对应的。
那,我们来看下 Configuration 对于的 XML 配置文件示例:
DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.propertis">
<property name="XXX" value="XXX"/>
properties>
<typeAliases>
<typeAlias type="cn.j3code.codehelp.entity.User" alias="user"/>
typeAliases>
<typeHandlers>
typeHandlers>
<objectFactory type="">objectFactory>
<plugins>
<plugin interceptor="cn.j3code.interceptor.MyPlugin">plugin>
plugins>
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" />
<setting name="defaultExecutorType" value="REUSE" />
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultFetchSize" value=""/>
<setting name="defaultStatementTimeout" value="5"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
<setting name="callSettersOnNulls" value="false"/>
<setting name="logPrefix" value="XXXX"/>
<setting name="logImpl" value="SLF4J"/>
<setting name="proxyFactory" value="CGLIB"/>
settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
dataSource>
environment>
environments>
<mappers>
<mapper resource="cn/j3code/codehelp/mapper/UserMapper.xml"/>
mappers>
configuration>
Configuration类属性如下:
public class Configuration {
protected Environment environment;//运行环境
protected boolean safeRowBoundsEnabled;
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
//true:有延迟加载属性的对象被调用时完全加载任意属性;false:每个属性按需要加载
protected boolean aggressiveLazyLoading;
//是否允许多种结果集从一个单独的语句中返回
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;//是否支持自动生成主键
protected boolean useColumnLabel = true;//是否使用列标签
protected boolean cacheEnabled = true;//是否使用缓存标识
protected boolean callSettersOnNulls;
protected boolean useActualParamName = true;
protected boolean returnInstanceForEmptyRow;
protected String logPrefix;
protected Class <? extends Log> logImpl;
protected Class <? extends VFS> vfsImpl;
protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" }));
protected Integer defaultStatementTimeout;
protected Integer defaultFetchSize;
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
//指定mybatis如果自动映射列到字段和属性,PARTIAL会自动映射简单的没有嵌套的结果,FULL会自动映射任意复杂的结果
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
//是否延时加载,false则表示所有关联对象即使加载,true表示延时加载
protected boolean lazyLoadingEnabled = false;
protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see Issue 300 (google code)
*/
protected Class<?> configurationFactory;
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<String>(); //已经加载过的resource(mapper)
protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
/*
* A map holds cache-ref relationship. The key is the namespace that
* references a cache bound to another namespace and the value is the
* namespace which the actual cache is bound to.
*/
protected final Map<String, String> cacheRefMap = new HashMap<String, String>();
//其他方法略
}
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);
}
typeAliasRegistry 注册了很多别名,我们看看 TypeAliasRegistry:
public TypeHandlerRegistry() {
register(Boolean.class, new BooleanTypeHandler());
register(boolean.class, new BooleanTypeHandler());
register(JdbcType.BOOLEAN, new BooleanTypeHandler());
register(JdbcType.BIT, new BooleanTypeHandler());
register(Byte.class, new ByteTypeHandler());
register(byte.class, new ByteTypeHandler());
register(JdbcType.TINYINT, new ByteTypeHandler());
register(Short.class, new ShortTypeHandler());
register(short.class, new ShortTypeHandler());
register(JdbcType.SMALLINT, new ShortTypeHandler());
register(Integer.class, new IntegerTypeHandler());
register(int.class, new IntegerTypeHandler());
register(JdbcType.INTEGER, new IntegerTypeHandler());
register(Long.class, new LongTypeHandler());
register(long.class, new LongTypeHandler());
register(Float.class, new FloatTypeHandler());
register(float.class, new FloatTypeHandler());
register(JdbcType.FLOAT, new FloatTypeHandler());
register(Double.class, new DoubleTypeHandler());
register(double.class, new DoubleTypeHandler());
register(JdbcType.DOUBLE, new DoubleTypeHandler());
register(Reader.class, new ClobReaderTypeHandler());
register(String.class, new StringTypeHandler());
register(String.class, JdbcType.CHAR, new StringTypeHandler());
register(String.class, JdbcType.CLOB, new ClobTypeHandler());
register(String.class, JdbcType.VARCHAR, new StringTypeHandler());
register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCHAR, new NStringTypeHandler());
register(String.class, JdbcType.NCLOB, new NClobTypeHandler());
register(JdbcType.CHAR, new StringTypeHandler());
register(JdbcType.VARCHAR, new StringTypeHandler());
register(JdbcType.CLOB, new ClobTypeHandler());
register(JdbcType.LONGVARCHAR, new ClobTypeHandler());
register(JdbcType.NVARCHAR, new NStringTypeHandler());
register(JdbcType.NCHAR, new NStringTypeHandler());
register(JdbcType.NCLOB, new NClobTypeHandler());
register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());
register(JdbcType.ARRAY, new ArrayTypeHandler());
register(BigInteger.class, new BigIntegerTypeHandler());
register(JdbcType.BIGINT, new LongTypeHandler());
register(BigDecimal.class, new BigDecimalTypeHandler());
register(JdbcType.REAL, new BigDecimalTypeHandler());
register(JdbcType.DECIMAL, new BigDecimalTypeHandler());
register(JdbcType.NUMERIC, new BigDecimalTypeHandler());
register(InputStream.class, new BlobInputStreamTypeHandler());
register(Byte[].class, new ByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());
register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());
register(byte[].class, new ByteArrayTypeHandler());
register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());
register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.LONGVARBINARY, new BlobTypeHandler());
register(JdbcType.BLOB, new BlobTypeHandler());
register(Object.class, UNKNOWN_TYPE_HANDLER);
register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);
register(Date.class, new DateTypeHandler());
register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());
register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());
register(JdbcType.TIMESTAMP, new DateTypeHandler());
register(JdbcType.DATE, new DateOnlyTypeHandler());
register(JdbcType.TIME, new TimeOnlyTypeHandler());
register(java.sql.Date.class, new SqlDateTypeHandler());
register(java.sql.Time.class, new SqlTimeTypeHandler());
register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());
// mybatis-typehandlers-jsr310
if (Jdk.dateAndTimeApiExists) {
Java8TypeHandlersRegistrar.registerDateAndTimeHandlers(this);
}
// issue #273
register(Character.class, new CharacterTypeHandler());
register(char.class, new CharacterTypeHandler());
}
// register 方法最终会把值放入这个 Map 中
private final Map<JdbcType, TypeHandler<?>> JDBC_TYPE_HANDLER_MAP = new EnumMap<JdbcType, TypeHandler<?>>(JdbcType.class);
让我们回到 build 方法中的 parser.parse() 方法,看看他是如何生成 Configuration 对象的。
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 解析配置,/configuration 表达式表示的就是 标签
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// 解析 properties 配置
propertiesElement(root.evalNode("properties"));
// 解析 settings 配置,并将其转换为 Properties 对象
Properties settings = settingsAsProperties(root.evalNode("settings"));
// 加载 vfs
loadCustomVfs(settings);
// 解析 typeAliases 配置
typeAliasesElement(root.evalNode("typeAliases"));
// 解析 plugins 配置
pluginElement(root.evalNode("plugins"));
// 解析 objectFactory 配置
objectFactoryElement(root.evalNode("objectFactory"));
// 解析 objectWrapperFactory 配置
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
// 解析 reflectorFactory 配置
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// settings 中的信息设置到 Configuration 对象中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析 environments 配置
environmentsElement(root.evalNode("environments"));
// 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// 解析 typeHandlers 配置
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 mappers 配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
parseConfiguration 方法通过 root 节点也就是 标签,来依次解析该标签中配置的各种子标签,解析标签如下:
ok,下面我们对这些标签的解析依次做分析。
先来看一下 properties 节点的配置内容,如下:
<properties resource="db.properties">
<property name="username" value="root"/>
<property name="password" value="123456"/>
properties>
下面我们来看看解析方法
org.apache.ibatis.builder.xml.XMLConfigBuilder#propertiesElement
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
// 解析 propertis 的子节点,并将这些节点内容转换为属性对象 Properties
Properties defaults = context.getChildrenAsProperties();
// 获取 propertis 节点中的 resource 和 url 属性值
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// 两者都不为空,则抛出异常
if (resource != null && url != null) {
throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other.");
}
if (resource != null) {
// 从文件系统中加载并解析属性文件
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
// 通过 url 加载并解析属性文件
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
// 将属性值设置到 XPathParser 中
parser.setVariables(defaults);
// 将属性值设置到 configuration 中
configuration.setVariables(defaults);
}
}
org.apache.ibatis.parsing.XNode#getChildrenAsProperties
public Properties getChildrenAsProperties() {
//创建一个Properties对象
Properties properties = new Properties();
// 获取并遍历子节点
for (XNode child : getChildren()) {
// 获取 property 节点的 name 和 value 属性
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
// 设置属性到属性对象中
properties.setProperty(name, value);
}
}
return properties;
}
org.apache.ibatis.parsing.XNode#getChildren
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
// 获取子节点列表
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
解析 properties 主要分三个步骤:
需要注意的是,propertiesElement 方法是先解析 properties 节点的子节点内容,后再从文件系统或者网络读取属性配置,并将所有的属性及属性值都放入到 defaults 属性对象中。这就会存在同名属性覆盖的问题,也就是从文件系统,或者网络上读取到的属性及属性值会覆盖掉 properties 子节点中同名的属性和及值。
先来看一下 settings 节点的配置内容,如下:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
settings>
下面我们来看看解析方法
org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsAsProperties
private Properties settingsAsProperties(XNode context) {
if (context == null) {
return new Properties();
}
// 获取 settings 子节点中的内容,解析成Properties,getChildrenAsProperties 方法前面已分析过
Properties props = context.getChildrenAsProperties();
// 创建 Configuration 类的“元信息”对象
MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
for (Object key : props.keySet()) {
// 检测 Configuration 中是否存在相关属性,不存在则抛出异常
if (!metaConfig.hasSetter(String.valueOf(key))) {
throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive).");
}
}
return props;
}
解析 settings 标签之后会将其中的 name 及 value 都都封装到 Properties 当中,最后校验 Configuration 中是否有相关的属性配置,如果没有则报错。
下面我们来看看 settings 标签值设置到 Configuration 对象中的方法
org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement
private void settingsElement(Properties props) throws Exception {
// 设置 autoMappingBehavior 属性,默认值为 PARTIAL
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
// 设置 cacheEnabled 属性,默认值为 true
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
@SuppressWarnings("unchecked")
// 解析默认的枚举处理器
Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler"));
// 设置默认枚举处理器
configuration.setDefaultEnumTypeHandler(typeHandler);
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
configuration.setLogPrefix(props.getProperty("logPrefix"));
@SuppressWarnings("unchecked")
Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
在 MyBatis 中,可以为我们自己写的有些类定义一个别名。这样在使用的时候,我们只需要输入别名即可,无需再把全限定的类名写出来。在 MyBatis 中,我们有两种方式进行别名配置。
第一种是仅配置包名,让 MyBatis 去扫描包中的类型,并根据类型得到相应的别名。这种方式的配置如下:
<typeAliases>
<package name="cn.j3code.mybatis.po"/>
typeAliases>
第二种方式是通过手动的方式,明确为某个类型配置别名。这种方式的配置如下:
<typeAliases>
<typeAlias alias="employe" type="cn.j3code.mybatis.po.Employe" />
<typeAlias type="cn.j3code.mybatis.po.User" />
typeAliases>
来看一下两种不同的别名配置是怎样解析的。代码如下:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 从指定的包中解析别名和类型的映射
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
// 从 typeAlias 节点中解析别名和类型的映射
} else {
// 获取 alias 和 type 属性值,alias 不是必填项,可为空
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
// 加载 type 对应的类型
Class<?> clazz = Resources.classForName(type);
// 注册别名到类型的映射
if (alias == null) {
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
显然,子标签名字为 package 则是通过指定的包解析别名和类型的映射反之则是从 typeAlias 节点中解析别名和类型的映射。
1)从指定的包中解析并注册别名
org.apache.ibatis.type.TypeAliasRegistry#registerAliases(java.lang.String)
public void registerAliases(String packageName){
registerAliases(packageName, Object.class);
}
org.apache.ibatis.type.TypeAliasRegistry#registerAliases(java.lang.String, java.lang.Class>)
public void registerAliases(String packageName, Class<?> superType){
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
/*
* 查找包下的父类为 Object.class 的类。
* 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。
*/
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取查找结果
Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses();
for(Class<?> type : typeSet){
// Ignore inner classes and interfaces (including package-info.java)
// Skip also inner classes. See issue #6
// 忽略匿名类,接口,内部类
if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) {
// 为类型注册别名
registerAlias(type);
}
}
}
主要分为两个步骤:
2)从 typeAlias 节点中解析并注册别名
在别名的配置中,type属性是必须要配置的,而alias属性则不是必须的。
org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.Class>)
public void registerAlias(Class<?> type) {
// 获取全路径类名的简称
String alias = type.getSimpleName();
Alias aliasAnnotation = type.getAnnotation(Alias.class);
if (aliasAnnotation != null) {
// 从注解中取出别名
alias = aliasAnnotation.value();
}
// 调用重载方法注册别名和类型映射
registerAlias(alias, type);
}
org.apache.ibatis.type.TypeAliasRegistry#registerAlias(java.lang.String, java.lang.Class>)
public void registerAlias(String alias, Class<?> value) {
if (alias == null) {
throw new TypeException("The parameter alias cannot be null");
}
// issue #748
// 将别名转成小写
String key = alias.toLowerCase(Locale.ENGLISH);
/*
* 如果 TYPE_ALIASES 中存在了某个类型映射,这里判断当前类型与映射中的类型是否一致,
* 不一致则抛出异常,不允许一个别名对应两种类型
*/
if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'.");
}
// 缓存别名到类型映射
TYPE_ALIASES.put(key, value);
}
若用户为明确配置 alias 属性,MyBatis 会使用类名的小写形式作为别名。比如,全限定类名cn.j3code.mybatis.po.User 的别名为 user。若类中有 @Alias 注解,则从注解中取值作为别名。
插件是 MyBatis 提供的一个拓展机制,通过插件机制我们可在 SQL 执行过程中的某些点上做一些自定义操作。比如分页插件,在SQL执行之前动态拼接语句,对于插件机制,后面会进行分析,先来看看插件的配置,如下:
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
plugin>
plugins>
方法源码:
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
// 获取配置信息
Properties properties = child.getChildrenAsProperties();
// 解析拦截器的类型,并创建拦截器
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 设置属性
interceptorInstance.setProperties(properties);
// 添加拦截器到 Configuration 中
configuration.addInterceptor(interceptorInstance);
}
}
}
org.apache.ibatis.session.Configuration#addInterceptor
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
// ...
}
首先是获取配置,然后再解析拦截器类型,并实例化拦截器。最后向拦截器中设置属性,并将拦截器添加到 Configuration 中。
InterceptorChain 实际上就是一个 interceptors 集合。
在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<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>
解析源码:
org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
// 获取 default 属性
environment = context.getStringAttribute("default");
}
for (XNode child : context.getChildren()) {
// 获取 id 属性
String id = child.getStringAttribute("id");
/*
* 检测当前 environment 节点的 id 与其父节点 environments 的属性 default
* 内容是否一致,一致则返回 true,否则返回 false
* 将其default属性值与子元素environment的id属性值相等的子元素设置为当前使用的Environment对象
*/
if (isSpecifiedEnvironment(id)) {
// 将environment中的transactionManager标签转换为TransactionFactory对象
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
// 将environment中的dataSource标签转换为DataSourceFactory对象
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
// 创建 DataSource 对象
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// 构建 Environment 对象,并设置到 configuration 中
configuration.setEnvironment(environmentBuilder.build());
}
}
}
}
environmentsElement 方法主要是遍历其子节点,将 Transaction 和 DataSource 创建出来,下面分别来看看如何解析:
org.apache.ibatis.builder.xml.XMLConfigBuilder#transactionManagerElement
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
// 通过别名获取Class,并实例化
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
// 通过别名获取Class,并实例化
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.");
}
中type有"JDBC"、"MANAGED"这两种配置,而我们前面Configuration中默认注册的别名中有对应的JdbcTransactionFactory.class、ManagedTransactionFactory.class这两个TransactionFactory
中type有"JNDI"、“POOLED”、"UNPOOLED"这三种配置,默认注册的别名中有对应的JndiDataSourceFactory.class、PooledDataSourceFactory.class、UnpooledDataSourceFactory.class这三个DataSourceFactory
而我们的environment配置中transactionManager type="JDBC"和dataSource type=“POOLED”,则生成的transactionManager为JdbcTransactionFactory,DataSourceFactory为PooledDataSourceFactory
我们来看看 PooledDataSourceFactory 和 UnpooledDataSourceFactory
public class UnpooledDataSourceFactory implements DataSourceFactory {
private static final String DRIVER_PROPERTY_PREFIX = "driver.";
private static final int DRIVER_PROPERTY_PREFIX_LENGTH = DRIVER_PROPERTY_PREFIX.length();
protected DataSource dataSource;
public UnpooledDataSourceFactory() {
//创建UnpooledDataSource实例
this.dataSource = new UnpooledDataSource();
}
@Override
public DataSource getDataSource() {
return dataSource;
}
......
}
//继承UnpooledDataSourceFactory
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
//创建PooledDataSource实例
this.dataSource = new PooledDataSource();
}
}
我们发现 UnpooledDataSourceFactory 创建的dataSource是 UnpooledDataSource,PooledDataSourceFactory创建的 dataSource是PooledDataSource
mapper.xml 文件的配置方式
<mappers>
<mapper resource="cn/j3code/studyspring/mybatis/helloworld/mapper/UserMapper.xml"/>
<package name="cn.j3code.Dao">package>
<mapper url="file:///var/mappers/UserMapper.xml"/>
<mapper class="org.mybatis.mappers.ManagerMapper"/>
mappers>
方法源码:
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//检测是否是package节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//读取 中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
String resource = child.getStringAttribute("resource");
//读取mapper节点的url属性
String url = child.getStringAttribute("url");
//读取mapper节点的class属性
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//根据rusource加载mapper文件
ErrorContext.instance().resource(resource);
//读取文件字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//实例化mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//从网络url资源加载mapper文件
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) {
//使用mapperClass加载文件
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
//resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
该方法对 Mapper 不同的配置方式做了不同的解析流程,后面我单独拿出一节来分析,这里,我们仅简单的看一下这个方法就好了。
2.1 节我们吧 XMLConfigBuilder 的 parse 方法中的重要步骤都过了一遍了,然后返回的就是一个完整的 Configuration 对象了,最后通过 SqlSessionFactoryBuilder 的 build 的重载方法创建了一个 SqlSessionFactory 实例DefaultSqlSessionFactory,我们来看看。
public SqlSessionFactory build(Configuration config) {
//创建DefaultSqlSessionFactory实例
return new DefaultSqlSessionFactory(config);
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
//只是将configuration设置为其属性
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
//略
}
在 2.1.6 节中,我们就是提了一下该方法,而没有做具体的分析,本就就是对它做具体分析的。
下面我们来看看 mapper 配置的方式吧!
1)接口信息进行配置
<mappers>
<mapper class="org.mybatis.mappers.UserMapper"/>
<mapper class="org.mybatis.mappers.ProductMapper"/>
<mapper class="org.mybatis.mappers.ManagerMapper"/>
</mappers>
注意:这种方式必须保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。因为是通过获取mapper中的class属性,拼接上.xml来读取UserMapper.xml,如果xml文件名不同或者不在同一个包中是无法读取到xml的。
2)相对路径进行配置
<mappers>
<mapper resource="org/mybatis/mappers/UserMapper.xml"/>
<mapper resource="org/mybatis/mappers/ProductMapper.xml"/>
<mapper resource="org/mybatis/mappers/ManagerMapper.xml"/>
mappers>
注意:这种方式不用保证同接口同包同名。但是要保证xml中的namespase和对应的接口名相同。
3)绝对路径进行配置
<mappers>
<mapper url="file:///var/mappers/UserMapper.xml"/>
<mapper url="file:///var/mappers/ProductMapper.xml"/>
<mapper url="file:///var/mappers/ManagerMapper.xml"/>
mappers>
4)接口所在包进行配置
<mappers>
<package name="org.mybatis.mappers"/>
mappers>
这种方式和第一种方式要求一致,保证接口名(例如UserMapper)和xml名(UserMapper.xml)相同,还必须在同一个包中。
注意:以上所有的配置都要保证 xml 中的 namespase 和对应的接口名相同。
下面就以 packae 属性为例,具体来分析一下。
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//检测是否是package节点
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//读取 中的mapper/userDao-mapping.xml,即resource = "mapper/userDao-mapping.xml"
String resource = child.getStringAttribute("resource");
//读取mapper节点的url属性
String url = child.getStringAttribute("url");
//读取mapper节点的class属性
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//根据rusource加载mapper文件
ErrorContext.instance().resource(resource);
//读取文件字节流
InputStream inputStream = Resources.getResourceAsStream(resource);
//实例化mapper解析器
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//执行解析mapper文件,即解析mapper/userDao-mapping.xml,文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//从网络url资源加载mapper文件
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) {
//使用mapperClass加载文件
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
//resource,url,mapperClass三种配置方法只能使用其中的一种,否则就报错
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
显然,该方法就是根据我们上面分析的四种加载映射文件的方式进行处理:
上面代码对 Mapper 文件的解析主要体现在下面两个代码:
那,我们先来看看 packae 扫描的形式,及 configuration.addMappers(mapperPackage) 方法。
org.apache.ibatis.session.Configuration#addMappers(java.lang.String)
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String)
public void addMappers(String packageName) {
// 传入包名和Object.class类型
addMappers(packageName, Object.class);
}
org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String, java.lang.Class>)
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
/*
* 查找包下的父类为 Object.class 的类。
* 查找完成后,查找结果将会被缓存到resolverUtil的内部集合中。上一篇文章我们已经看过这部分的源码,不再累述了
*/
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
// 获取查找结果
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
// 解析mapper类
addMapper(mapperClass);
}
}
该方法先找到传入的包名下所有的 class 文件,然后挨个遍历解析对应的 mapper 及方法信息,源码如下:
org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
// 该类必须为接口
if (type.isInterface()) {
// 检测该类是否已经被加载过
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/*
* 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
*/
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// 构造一个MapperAnnotationBuilder去解析这个类
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解中的信息
parser.parse();
loadCompleted = true;
} finally {
// 若上面加载时出错,则将该类移除
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
该方法对传入的 class 做一些基本的判断,如果不是接口和未解析的 class 则跳过,反之则调用 parser.parse() 方法解析 class ,我们来看看该方法源码。
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
String resource = type.toString();
// 是否已经加载过该resource(值形式类似于为:interface org.tree.study.mybatis.dao.UserDao)
if (!configuration.isResourceLoaded(resource)) {
// 加载类对应的xml文件
loadXmlResource();
// 将该resource加入已经加载过的Set中
configuration.addLoadedResource(resource);
// 设置命名空间
assistant.setCurrentNamespace(type.getName());
// 解析 Mapper 接口的 @CacheNamespace 注解,创建缓存
parseCache();
// 解析 Mapper 接口的 @CacheNamespaceRef 注解,引用其他命名空间
parseCacheRef();
// 加载类中所有的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// 解析方法上面的注解
parseStatement(method);
} catch (IncompleteElementException e) {
// 将异常解析的方法加入一个List中
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
// 重新处理异常解析的方法
parsePendingMethods();
}
该方法解析的一个重点就是 loadXmlResource() ,解析对应的 xml 文件,紧接着会对 class 中的缓存注解及方法注解做一些解析处理。
下面我们来看看 loadXmlResource() 方法源码,对于上述方法的其他代码不做分析,因为我们会在 XMLMapperBuilder#parse 中做分析(MapperAnnotationBuilder#parse 为注解版解析,XMLMapperBuilder#parse 为 xml 版解析)。
org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource
private void loadXmlResource() {
// 看是否已经加载过该xml文件
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
// 从这里我们可以看出,若是要使用加载类的方法,xml文件的路径需要和类的包名路径一致,并且xml文件名要和类名一致(如:org.tree.study.mybatis.dao.UserDao对应org/tree/study/mybatis/dao/UserDao.xml)
String xmlResource = type.getName().replace('.', '/') + ".xml";
InputStream inputStream = null;
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e) {
// ignore, resource is not required
}
if (inputStream != null) {
// 从这里我们看出,加载xml文件时,使用了XMLMapperBuilder,这正是我们要讲的另一种方式,这里的详细过程,我们待会再分析
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
xmlParser.parse();
}
}
}
由上述方法可知为啥 Mapper 类要和 Mapper.xml 文件目录一致了把!
紧接着该方法就会调用 XMLMapperBuilder 中的 parse 方法去解析我们编写的 xml 文件,那么也就来到了我们上面所说的第二个方法。
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
// 检测映射文件是否已经被解析过
if (!configuration.isResourceLoaded(resource)) {
// 解析 mapper 节点
configurationElement(parser.evalNode("/mapper"));
// 添加资源路径到“已解析资源集合”中
configuration.addLoadedResource(resource);
// 通过命名空间绑定 Mapper 接口
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
该方法我们会分析两部分:
先来看看解析 mapper 节点
在 MyBatis 映射文件中,可以配置多种节点。比如 ,, 以及
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.UserMapper">
<cache/>
<resultMap id="baseMap" type="entity.User">
<result property="id" column="id" jdbcType="INTEGER">result>
<result property="name" column="name" jdbcType="VARCHAR">result>
resultMap>
<sql id="table">
user
sql>
<select id="getAll" resultMap="baseMap">
select * from <include refid="table"/> WHERE id = #{id}
select>
mapper>
接下来我们来看看解析改 mapper 节点的源码。
org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
private void configurationElement(XNode context) {
try {
// 获取 mapper 命名空间,如 mapper.UserMapper
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置命名空间到 builderAssistant 中
builderAssistant.setCurrentNamespace(namespace);
// 解析 节点
cacheRefElement(context.evalNode("cache-ref"));
// 解析 节点
cacheElement(context.evalNode("cache"));
// 已废弃配置,这里不做分析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 节点
sqlElement(context.evalNodes("/mapper/sql"));
// 解析
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
该方法也比较好理解,主要就是解析 mapper.xml 文件中所有的可配置节点,解析步骤:
1. 获取命名空间并设置到 builderAssistant 中
2. 解析缓存节点, 和
3. 解析 节点
4. 解析 节点
5. 解析 节点
6. 解析 映射文件解析完成后,我们需要通过命名空间将绑定 mapper 接口,源码如下。
org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
// 获取映射文件的命名空间
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
// 根据命名空间解析 mapper 类型
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
// 检测当前 mapper 类是否被绑定过
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 绑定 mapper 类
configuration.addMapper(boundType);
}
}
}
}
org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
/*
* 将 type 和 MapperProxyFactory 进行绑定,MapperProxyFactory 可为 mapper 接口生成代理类
*/
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 解析注解中的信息
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
这里的重点就是为每个 Mapper 接口创建一个代理工厂,也就是 knownMappers.put(type, new MapperProxyFactory(type)) 这段代码,下面我们来看看这个 MapperProxyFactory 。
public class MapperProxyFactory<T> {
//存放Mapper接口Class
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//生成mapperInterface的代理类
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这一块就是通过 newInstance 方法为 MapperProxy 类型对象生成一个 Mapper 的代理,之后我们的所有 Mapper 操作都会在 MapperProxy 中的 invoke 方法中执行,这个后面会分析。
好了,今天的内容到这里就结束了,我是 【J3】关注我,我们下期见。
由于博主才疏学浅,难免会有纰漏,假如你发现了错误或偏见的地方,还望留言给我指出来,我会对其加以修正。
如果你觉得文章还不错,你的转发、分享、点赞、留言就是对我最大的鼓励。
感谢您的阅读,十分欢迎并感谢您的关注。