人非要經歷一番不同平時的劫難才能脫胎換骨,成為真正能解決問題的人
簡介初始化過程1.解析XML配置檔案1.1 Config檔案的解析1.2 Mapper檔案的解析1.2.1 解析CURD模板1.2.2 繫結Mapper到名稱空間2.建立SqlSessionFactory總結
簡介
首先我們再回顧下Mybaits的基本使用。
//載入配置檔案
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//SqlSession 的獲取
SqlSession sqlSession = sqlSessionFactory.openSession();
try{
//執行sql
User user = sqlSession.selectOne("MyMapper.selectUser", 1);//(SQL通過名稱空間+SQLID 的格式定位)
}finally {
sqlSession .close();
}
複製程式碼
一切都從SqlSessionFactoryBuilder說起。SqlSessionFactoryBuilder是通過builder設計模式來建立一個SqlSessionFactory 工廠。
建立SqlSessionFactory 最主要分為兩步,
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
// 1.xml配置檔案解析器。
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
Configuration config = parser.parse()
// 2.根據解析到的配置,建立DefaultSqlSessionFactory
return build(config);
}
//建立預設的sqlsesion工廠。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
複製程式碼
初始化過程
1.解析XML配置檔案
SqlSessionFactoryBuilder的第一步就是配置的解析。
配置檔案主要分為兩種:
Conifg檔案: 包括資料連線配置,全域性設定配置。
Mapper檔案:用於SQL的統一管理,對SQL的配置。
1.1 Config檔案的解析
Config檔案的解析是由XMLConfigBuilder做的。
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());//建立一個Configuration物件
ErrorContext.instance().resource("SQL Mapper Configuration");
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
複製程式碼
我們來看看parse()方法
public Configuration parse() {
parsed = true;
//從根節點開始解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
// 解析 properties配置
propertiesElement(root.evalNode("properties"));
// 解析 settings配置
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);
//解析environment 配置
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 解析 mapper 配置
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
複製程式碼
XMLConfigBuilder 根據約定,解析config 配置檔案中的各個標籤。並將相關配置資訊放到Configuration 物件中返回。
列如:
Properties settings = settingsAsProperties(root.evalNode("settings"));//解析settings標籤
settingsElement(settings);//設定settings配置到configuration 物件。
複製程式碼
1.2 Mapper檔案的解析
在解析Config配置檔案過程中,會伴隨Mapper.xml檔案的解析。這個解析的工作是由XMLMapperBuilder 完成的。
config中的mapper檔案位置配置
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
複製程式碼
Mapper.xml
<mapper namespace="com.wqd.dao.UserMapper">
<select id="selectUser" resultType="com.wqd.model.User">
select * from user where id= #{id}
</select>
</mapper>
複製程式碼
XMLMapperBuilder
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//1.從根標籤mapper開始解析mapper檔案
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//2.繫結mapper到名稱空間
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
//1.解析mapper檔案
private void configurationElement(XNode context) {
try {
//名稱空間必須要有
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//快取的相關設定
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
//引數型別解析
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//結果集型別解析
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql標籤配置的sql 片段
sqlElement(context.evalNodes("/mapper/sql"));
//解析select|insert|update|delete 標籤配置的sql 模板(重點)
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
//2.繫結名稱空間
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {//如果有對應的Class
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);//新增到快取
}
}
}
}
複製程式碼
這裡我們重點講講XMLMapperBuilder 中有兩個重要的點
1.2.1 解析CURD模板
也就是解析select|insert|update|delete
代表的SQL 模板。這四種標籤配置的SQL模板是我們運算元據庫時的SQL執行語句的模板。 我們可以理解為:一個select 標籤表示一類動態SQL。
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
複製程式碼
每一個select|insert|update|delete
由XMLStatementBuilder 解析
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//解析"select|insert|update|delete"標籤
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
複製程式碼
XMLStatementBuilder 除了按照約定,解析"select|insert|update|delete"對應的標籤屬性以及子標籤外。最重要的是還會通過MapperBuilderAssistant(構建助手),把解析出來的資訊 封裝成一個MappedStatement 放入到Configuration.mappedStatements 快取中
類XMLStatementBuilder
public class XMLStatementBuilder extends BaseBuilder {
private MapperBuilderAssistant builderAssistant;//構建助手
//解析"select|insert|update|delete"標籤
public void parseStatementNode() {
String id = context.getStringAttribute("id");
...解析
//助手輔助封裝成MappedStatement
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
類MapperBuilderAssistant
public class MapperBuilderAssistant extends BaseBuilder {
public MappedStatement addMappedStatement(...){
id = applyCurrentNamespace(id, false);//id的處理
//建立一個MappedStatement物件封裝每一個SQL模板資訊
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
}
}
//Configuration類
public class Configuration {
//mappedStatements快取
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
//新增MappedStatement
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);//把MappedStatement 作為KEY
}
}
複製程式碼
值得一提是id的處理。雖然我們僅僅在select標籤配置了id = "selectUser",
<select id="selectUser" resultType="com.wqd.model.User">
複製程式碼
但是在構建MappedStatement時 ,並不是把“selectUser”作為id, 而是經過applyCurrentNamespace方法進行處理。
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
return currentNamespace + "." + base;
}
複製程式碼
我們可以看出,此方法會返回 namespacec.id 作為MappedStatement.id ,同時在作為Configuration.mappedStatements 快取中MappedStatement的Key. 這就是為啥我們是通過namespace.id的形式來定位SQL的原因
最後解析的結果就是每一個SQL 模板都會建立一個MappedStatement物件(封裝sql模板相關資訊),放入到Configuration.mappedStatements 中。
這樣我們就可以 通過namespace.id 定位到對應的SQL了。
<mapper namespace="com.wqd.dao.UserMapper">
<select id="selectUser" resultType="com.wqd.model.User">
select * from user where id= #{id}
</select>
</mapper>
複製程式碼
User user = sqlSession.selectOne("com.wqd.dao.UserMapper.selectUser", 1);
複製程式碼
1.2.2 繫結Mapper到名稱空間
這一步也非常重要,如果namerspacer配置為一個Mpper類的全限定名。那麼就會以namespace對應的類的Class為KEY,以MapperProxyFactory 為value 放入到 Configuration.MapperRegistry.knownMappers快取中。
bindMapperForNamespace();
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//根據名稱空間名,查詢類Class類
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);//如果找到,就把關聯關係放到快取中
}
}
}
}
//Configuration類
public class Configuration {
//mapperRegistry快取
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
}
//Mapper註冊器
public class MapperRegistry {
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 {
//以namespace對應的Class類為KEY。新建一個MapperProxyFactory為Value
knownMappers.put(type, new MapperProxyFactory<T>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
}
複製程式碼
當我們獲取通過UserMapper獲取Mapper時,MapperProxyFactory 會為我們建立一個代理類,來執行對應CURD操作。
這樣我們就可以通過類名獲取到Mapper#selectUser了。
<mapper namespace="com.wqd.dao.UserMapper">
<select id="selectUser" resultType="com.wqd.model.User">
select * from user where id= #{id}
</select>
</mapper>
複製程式碼
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
複製程式碼
config配置檔案與mapper配置檔案解析完成後。最終得到了一個Configuration物件。 而這個Configuration物件就是Mybatis 所需要的所有配置資訊
2.建立SqlSessionFactory
SqlSessionFactoryBuilder 的第二步就是通過Configuration 物件建立一個預設的SqlSessionFactory工廠出來。
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
複製程式碼
DefaultSqlSessionFactory 用於建立Sqlsession
Sqlsession sqlsession = DefaultSqlSessionFactory.openSession()
複製程式碼
總結
Mybatis初始化的過程,其就是Config配置檔案,Mapper檔案被解析, Configuration物件被建立的過程。
- 所有的配置資訊都包含在Configuration 這個大物件中。
- 每一個SQL模板資訊會被解析成一個MappedStatement物件。根據MappedStatement物件內的SQL模板資訊,我們可以生成一類SQL。
如果本文任何錯誤,請批評指教,不勝感激 !
微信公眾號:原始碼行動
享學原始碼,行動起來,來原始碼行動