Mybatis原始碼解析之配置載入(一)
Mybatis原始碼解析之配置載入(一)
用了好幾年的mybatis了,但是很少來鑽研mybatis原理所在,最近抽出空來,就把這一整套原始碼都研究了下,然後發現就是這些東西,mybatis沒啥難度,於是決定把研究的這一整套寫一個mybatis系列,記錄一下,在這些完了以後,順便寫一個小的mybatis框架。
1. demo演示
還是從用法開始吧,就不開始就從原始碼說了,mybatis的conf配置檔案不再多講,這裡就是定義了一個domain,對應mapper檔案,main程式,具體如下:
Domain
package cn.com.domain;
/**
* @author xiaxuan
* @date 2018/4/10.
*/
public class User {
private Long id;
private String username;
private String password;
private Integer age;
private String mobile;
private String hotelAddress;
....
}
Mapper
import cn.com.domain.User;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis. annotations.ResultMap;
import org.apache.ibatis.annotations.Select;
/**
* @author xiaxuan
* @date 2018/4/10.
*/
public interface UserMapper {
User getUser(int id);
@ResultMap("BaseResultMap")
@Select("select id, username, password, age, mobile, hotel_address from user where id=#{id};")
User getUser2 (@Param("id") Long id);
void updateUser(@Param("age") Integer age, @Param("id") Long id);
void save(User user);
void deleteById(@Param("id") Long id);
}
xml檔案:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.com.mapper.UserMapper">
<!--
對映資料庫具體欄位
column: 資料庫欄位
property: 對應實體類欄位
-->
<resultMap id="BaseResultMap" type="cn.com.domain.User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="password" property="password"/>
<result column="age" property="age"/>
<result column="mobile" property="mobile"/>
<result column="hotel_address" property="hotelAddress"/>
</resultMap>
<!--
根據id查詢得到一個user物件
-->
<select id="getUser" parameterType="int" resultMap="BaseResultMap">
select id, username, password, age, mobile, hotel_address from user where id=#{id}
</select>
<update id="updateUser">
update user set age = #{age} WHERE id = #{id};
</update>
<insert id="save">
insert into user(username, password, age, mobile, hotel_address) values(#{username}, #{password}, #{age}, #{mobile}, #{hotelAddress});
</insert>
<delete id="deleteById" parameterType="java.lang.Long">
delete from user where id = #{id};
</delete>
</mapper>
想了想還是把conf檔案也貼出來,因為等會分析時需要對照conf檔案來分析,conf如下:
<?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="development">
<environment id="development">
<transactionManager type="JDBC" />
<!--
配置資料庫連線資訊
連線池儲存的是連線mysql的資料庫連線
-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver" />
<!--
連線mysql的網路地址
jdbc:mysql://localhost:3306/mybatis
jdbc:mysql: 協議
localhost: 地址 本地指向127.0.0.1
3306: 埠
mybatis:資料庫名
-->
<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
<property name="username" value="root" />
<property name="password" value="" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="cn/com/mapper/userMapper.xml"/>
</mappers>
</configuration>
main程式如下:
public class Main {
public static void main(String[] args) {
//mybatis的配置檔案
String resource = "conf.xml";
//使用類載入器載入mybatis的配置檔案(它也載入關聯的對映檔案)
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
//構建sqlSession的工廠
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = sessionFactory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
//執行查詢返回一個唯一user物件的sql
User user = userMapper.getUser(1);
System.out.println(user);
User user2 = userMapper.getUser2(1L);
System.out.println(user2);
userMapper.updateUser(23, 1L);
User user3 = userMapper.getUser2(1L);
System.out.println(user3);
session.close();
}
}
以上就是一個demo的完整演示,但是執行結果就不再貼出來了,畢竟不是使用指南,下面就就上面的演示程式具體講解mybatis原始碼。
2.初始化配置分析
接下來開始對mybatis原始碼進行分析與講解,首先說下配置檔案是如何載入的,這裡最主要的就是上圖main程式中的前幾行程式碼,如下:
//mybatis的配置檔案
String resource = "conf.xml";
//使用類載入器載入mybatis的配置檔案(它也載入關聯的對映檔案)
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
//構建sqlSession的工廠
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
將xml配置檔案轉換成stream流後,解析stream生成SqlsesssionFactory物件,進入到build原始碼中,如下:
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
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.
}
}
}
這裡的XMLConfigBuilder無非是把stream轉換成XML解析物件,這一步跳過,重點在於**parser.parse()**方法,進入到其方法中。
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
在parseConfiguration(parser.evalNode("/configuration"))這行中,首先獲得了configuration節點,這個就對應上了上面配置檔案conf.xml中的configuration配置,在獲取configuration節點後開始解析configuration中內容,進入到parseConfiguration方法。
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
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);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
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);
}
}
這裡開始就是對各種配置的解析,後面對properties、environments、甚至是重點的mappers的配置解析太多,單獨放在這一篇裡面未免篇幅過長,我這把配置解析分成兩部分,這一部分原始碼講解放在下一篇中。