Mybatis源码解析之Mybatis初始化过程
一、搭建一個簡單的Mybatis工程
為了了解Mybatis的初始化過程,這里需要搭建一個簡單的Mybatis工程操作數據庫,工程結構如下:
一個UserBean.java
操作數據庫接口以及對應的Mapper
public interface UserMapper {void insertUser(UserBean user);} <?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="com.zjp.mapper.UserMapper"><insert id="insertUser" parameterType="userBean">insert into user_t (id,user_name,password,age) values(#{id},#{username},#{password},#{age})</insert></mapper>mybatis配置文件mybatis-config以及jdbc配置文件
<?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="jdbc.properties"><property name="dialect" value="mysql" /></properties><settings><setting name="logImpl" value="LOG4J" /></settings><typeAliases><typeAlias type="com.zjp.bean.UserBean" alias="userBean" /></typeAliases><!-- 配置mybatis運行環境 --><environments default="cybatis"><environment id="cybatis"><!-- type="JDBC" 代表使用JDBC的提交和回滾來管理事務 --><transactionManager type="JDBC" /><!-- mybatis提供了3種數據源類型,分別是:POOLED,UNPOOLED,JNDI --><!-- POOLED 表示支持JDBC數據源連接池 --><!-- UNPOOLED 表示不支持數據源連接池 --><!-- JNDI 表示支持外部數據源連接池 --><dataSource type="POOLED"><property name="driver" value="${driver}" /><property name="url" value="${url}" /><property name="username" value="${username}" /><property name="password" value="${password}" /></dataSource></environment></environments><mappers><!-- 映射文件方式1,一個一個的配置--><mapper resource="com/zjp/mapper/UserBeanMapper.xml" /> </mappers> </configuration> driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8 username=root password=root #\u5B9A\u4E49\u521D\u59CB\u8FDE\u63A5\u6570 initialSize=0 #\u5B9A\u4E49\u6700\u5927\u8FDE\u63A5\u6570 maxActive=20 #\u5B9A\u4E49\u6700\u5927\u7A7A\u95F2 maxIdle=20 #\u5B9A\u4E49\u6700\u5C0F\u7A7A\u95F2 minIdle=1 #\u5B9A\u4E49\u6700\u957F\u7B49\u5F85\u65F6\u95F4 maxWait=60000這樣就完成了一個簡單的mybatis工程搭建,然后新建一個main方法對數據庫進行操作
package com.zjp;import java.io.IOException; import java.io.Reader;import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder;import com.zjp.bean.UserBean; import com.zjp.mapper.UserMapper;public class Test {public static void main(String[] args) throws IOException {// 使用MyBatis提供的Resources類加載mybatis的配置文件Reader reader = Resources.getResourceAsReader("mybatis-config.xml");// 構建sqlSession的工廠SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);SqlSession session = sqlSessionFactory.openSession();UserMapper mapper = session.getMapper(UserMapper.class);UserBean user = new UserBean("張三", "123456", 7);try {mapper.insertUser(user);System.out.println(user.toString());session.commit();} catch (Exception e) {e.printStackTrace();session.rollback();}} }二、Mybatis配置文件初始化過程
在Main方法中前面兩行代碼就是配置文件的初始化過程
1、創建SqlSessionFactoryBuilder對象
// 使用MyBatis提供的Resources類加載mybatis的配置文件 Reader reader = Resources.getResourceAsReader("mybatis-config.xml"); // 構建sqlSession的工廠 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);第一步是獲取配置文件的reader,然后獲取SqlSessionFactory ,SqlSessionFactory是MyBatis的關鍵對象,它是個單個數據庫映射關系經過編譯后的內存鏡像通過源碼可知SqlSessionFactory 是接口,
public interface SqlSessionFactory {//8個方法可以用來創建SqlSession實例SqlSession openSession();//自動提交SqlSession openSession(boolean autoCommit);//連接SqlSession openSession(Connection connection);//事務隔離級別SqlSession openSession(TransactionIsolationLevel level);//執行器的類型SqlSession openSession(ExecutorType execType);SqlSession openSession(ExecutorType execType, boolean autoCommit);SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);SqlSession openSession(ExecutorType execType, Connection connection);Configuration getConfiguration();}SqlSessionFactoryBuilder就是創建,通過build方法對配置文件進行解析并初始化,通過源碼可知build方法有很多重載,
通過源碼可知,所有的build方法最后都是通過
這方法來執行,所以SqlSessionFactoryBuilder的功能就是對輸入的配置文件流進行解析最后生成SqlSessionFactory 。
2 創建SqlSessionFactory
進入最終都需要進入的build方法
/*** 第4種方法是最常用的,它使用了一個參照了XML文檔或更特定的SqlMapConfig.xml文件的Reader實例。* 可選的參數是environment和properties。Environment決定加載哪種環境(開發環境/生產環境),包括數據源和事務管理器。* 如果使用properties,那么就會加載那些properties(屬性配置文件),那些屬性可以用${propName}語法形式多次用在配置文件中*/public SqlSessionFactory build(Reader reader, String environment, Properties properties) {try {// 委托XMLConfigBuilder來解析xml文件,并構建XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);return build(parser.parse());} catch (Exception e) {// 這里是捕獲異常,包裝成自己的異常并拋出的idiom?,最后還要reset ErrorContextthrow ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {reader.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}build函數首先會構造一個XMLConfigBuilder對象,從名字上可以看出來,該對象是用來解析XML配置文件的。通過XMLConfigBuilder將配置信息轉換成paser
//構造函數,轉換成XPathParser再去調用構造函數public XMLConfigBuilder(Reader reader, String environment, Properties props) {//構造一個需要驗證,XMLMapperEntityResolver的XPathParserthis(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);} //上面6個構造函數最后都合流到這個函數,傳入XPathParserprivate XMLConfigBuilder(XPathParser parser, String environment, Properties props) {//首先調用父類初始化Configurationsuper(new Configuration());//錯誤上下文設置成SQL Mapper Configuration(XML文件配置),以便后面出錯了報錯用吧ErrorContext.instance().resource("SQL Mapper Configuration");//將Properties全部設置到Configuration里面去this.configuration.setVariables(props);this.parsed = false;this.environment = environment;this.parser = parser;}在這里有一個很重要的類Configuration,最后所有的配置信息都會封裝到這個類里面,這個里面擁有很多參數
//環境protected Environment environment;//---------以下都是<settings>節點-------protected boolean safeRowBoundsEnabled = false;protected boolean safeResultHandlerEnabled = true;protected boolean mapUnderscoreToCamelCase = false;protected boolean aggressiveLazyLoading = true;protected boolean multipleResultSetsEnabled = true;protected boolean useGeneratedKeys = false;protected boolean useColumnLabel = true;//默認啟用緩存protected boolean cacheEnabled = true;protected boolean callSettersOnNulls = false;protected String logPrefix;protected Class <? extends Log> logImpl;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 ExecutorType defaultExecutorType = ExecutorType.SIMPLE;protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;//---------以上都是<settings>節點-------protected Properties variables = new Properties();//對象工廠和對象包裝器工廠protected ObjectFactory objectFactory = new DefaultObjectFactory();protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();//映射注冊機protected MapperRegistry mapperRegistry = new MapperRegistry(this);//默認禁用延遲加載protected boolean lazyLoadingEnabled = false;protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNLprotected String databaseId;/*** Configuration factory class.* Used to create Configuration for loading deserialized unread properties.** @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300</a> (google code)*/protected Class<?> configurationFactory;protected final InterceptorChain interceptorChain = new InterceptorChain();//類型處理器注冊機protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry();//類型別名注冊機protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();//映射的語句,存在Map里protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");//緩存,存在Map里protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");//結果映射,存在Map里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>();protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");//不完整的SQL語句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>();public Configuration(Environment environment) {this();this.environment = environment;}3、解析配置文件
獲取到了XMLConfigBuilder之后就可以對配置進行解析了
//解析配置public Configuration parse() {//如果已經解析過了,報錯if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}parsed = true;//根節點是configurationparseConfiguration(parser.evalNode("/configuration"));return configuration;}解析配置文件的重要邏輯
//解析配置private void parseConfiguration(XNode root) {try {//分步驟解析//issue #117 read properties first//1.propertiespropertiesElement(root.evalNode("properties"));//2.類型別名typeAliasesElement(root.evalNode("typeAliases"));//3.插件pluginElement(root.evalNode("plugins"));//4.對象工廠objectFactoryElement(root.evalNode("objectFactory"));//5.對象包裝工廠objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));//6.設置settingsElement(root.evalNode("settings"));// read it after objectFactory and objectWrapperFactory issue #631//7.環境environmentsElement(root.evalNode("environments"));//8.databaseIdProviderdatabaseIdProviderElement(root.evalNode("databaseIdProvider"));//9.類型處理器typeHandlerElement(root.evalNode("typeHandlers"));//10.映射器mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}通過上面的代碼就可以明確的看出來,mybatis對配置文件的每個不同節點的解析過程,由于解析的節點太多,平時在開發接觸較多的properties、typeAliases、environments、mappers
3.1properties的解析
假如有一個這樣的節點需要解析
<properties resource="jdbc.properties"><property name="username" value="root"/><property name="password" value="root"/></properties>解析過程
private void propertiesElement(XNode context) throws Exception {if (context != null) {//傳入方式是調用構造函數時傳入,public XMLConfigBuilder(Reader reader, String environment, Properties props)//1.XNode.getChildrenAsProperties函數方便得到孩子所有PropertiesProperties defaults = context.getChildrenAsProperties();//2.然后查找resource或者url,加入前面的PropertiesString resource = context.getStringAttribute("resource");String url = context.getStringAttribute("url");// resource和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) {// 將所有的配置信息加載到Properties中defaults.putAll(Resources.getResourceAsProperties(resource));} else if (url != null) {defaults.putAll(Resources.getUrlAsProperties(url));}//3.Variables也全部加入PropertiesProperties vars = configuration.getVariables();if (vars != null) {defaults.putAll(vars);}parser.setVariables(defaults);configuration.setVariables(defaults);}}- 獲取到下面的所有子節點信息并將這些信息放到Properties 中;
- 然后讀取resource或者url中的配置加入到Properties 中;
最后將所有的配置信息保存到configuration中
如果在這些地方,屬性多于一個的話,MyBatis 按照如下的順序加載它們:
1.在 properties 元素體內指定的屬性首先被讀取。
2.從類路徑下資源或 properties 元素的 url 屬性中加載的屬性第二被讀取,它會覆蓋已經存在的完全一樣的屬性。
3.作為方法參數傳遞的屬性最后被讀取, 它也會覆蓋任一已經存在的完全一樣的屬性,這些屬性可能是從 properties 元素體內和資源/url 屬性中加載的。
3.2 typeAliases的解析
別名的解析
private void typeAliasesElement(XNode parent) {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {//如果是packageString typeAliasPackage = child.getStringAttribute("name");//(一)調用TypeAliasRegistry.registerAliases,去包下找所有類,然后注冊別名(有@Alias注解則用,沒有則取類的simpleName)configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);} else {//如果是typeAliasString alias = child.getStringAttribute("alias");String type = child.getStringAttribute("type");try {Class<?> clazz = Resources.classForName(type);//根據Class名字來注冊類型別名//(二)調用TypeAliasRegistry.registerAliasif (alias == null) {//alias可以省略typeAliasRegistry.registerAlias(clazz);} else {typeAliasRegistry.registerAlias(alias, clazz);}} catch (ClassNotFoundException e) {throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);}}}}}通過上面的代碼可知,別名的配置方式有package和typeAlias兩種。
<typeAliases><package name="com.zjp.bean"/> </typeAliases> <typeAliases><typeAlias type="com.zjp.bean.UserBean" alias="userBean" /> </typeAliases>通過上面的兩種方式之后就會為所有的類起一個別名,為類首字母小寫。
最終這些別名都會注冊到configuration的typeAliasRegistry中。
3.3 setting的解析
<settings><setting name="logImpl" value="LOG4J" /></settings>通過源碼可知,這個解析較為簡單,就是將讀取到的設置信息設置到configuration中
//顯式定義用什么log框架,不定義則用默認的自動發現jar包機制 configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));3.4 mappers的解析
mappers節點解析是mybatis中比較重要,通過源碼可以知道定義方式有如下四種:
private void mapperElement(XNode parent) throws Exception {if (parent != null) {for (XNode child : parent.getChildren()) {if ("package".equals(child.getName())) {//10.4自動掃描包下所有映射器String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");if (resource != null && url == null && mapperClass == null) {//10.1使用類路徑ErrorContext.instance().resource(resource);InputStream inputStream = Resources.getResourceAsStream(resource);//映射器比較復雜,調用XMLMapperBuilder//注意在for循環里每個mapper都重新new一個XMLMapperBuilder,來解析XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {//10.2使用絕對url路徑ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);//映射器比較復雜,調用XMLMapperBuilderXMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {//10.3使用java類名Class<?> mapperInterface = Resources.classForName(mapperClass);//直接把這個映射加入配置configuration.addMapper(mapperInterface);} else {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");}}}}}通過上面的源碼可以得出這四種分別是package、resource、url和class。
進入Mapper的解析首先會遍歷所有的子節點,然后判斷不同的方式對Mapper進行解析,解析之后的信息也是保存到configuration中。
其中package的解析方式較為簡單,其他的解析方式會使用到一個映射器進行解析
首先在解析之前會創建一個XMLMapperBuilder ,獲取到之后再進行解析
//解析public void parse() {//如果mapper文件沒有加載過再加載,防止重復加載if (!configuration.isResourceLoaded(resource)) {//配置mapperconfigurationElement(parser.evalNode("/mapper"));//標記一下,已經加載過了configuration.addLoadedResource(resource);//綁定映射器到namespacebindMapperForNamespace();} parsePendingResultMaps();parsePendingChacheRefs();parsePendingStatements();}解析mapper
private void configurationElement(XNode context) {try {//1.配置namespaceString namespace = context.getStringAttribute("namespace");if (namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);//2.配置cache-refcacheRefElement(context.evalNode("cache-ref"));//3.配置cachecacheElement(context.evalNode("cache"));//4.配置parameterMap(已經廢棄,老式風格的參數映射)parameterMapElement(context.evalNodes("/mapper/parameterMap"));//5.配置resultMap(高級功能)resultMapElements(context.evalNodes("/mapper/resultMap"));//6.配置sql(定義可重用的 SQL 代碼段)sqlElement(context.evalNodes("/mapper/sql"));//7.配置select|insert|update|delete TODObuildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);}}resultMapElements函數
該函數用于解析映射文件中所有的節點,這些節點會被解析成ResultMap對象,存儲在Configuration對象的resultMaps容器中。
resultMap解析過程:
//5.1 配置resultMapprivate ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {//錯誤上下文 //取得標示符 ("resultMap[userResultMap]") // <resultMap id="userResultMap" type="User"> // <id property="id" column="user_id" /> // <result property="username" column="username"/> // <result property="password" column="password"/> // </resultMap>ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());//一般拿type就可以了,后面3個難道是兼容老的代碼?String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));//高級功能,還支持繼承? // <resultMap id="carResult" type="Car" extends="vehicleResult"> // <result property="doorCount" column="door_count" /> // </resultMap>String extend = resultMapNode.getStringAttribute("extends");//autoMappingBoolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");Class<?> typeClass = resolveClass(type);Discriminator discriminator = null;List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();resultMappings.addAll(additionalResultMappings);List<XNode> resultChildren = resultMapNode.getChildren();for (XNode resultChild : resultChildren) {if ("constructor".equals(resultChild.getName())) {//解析result map的constructorprocessConstructorElement(resultChild, typeClass, resultMappings);} else if ("discriminator".equals(resultChild.getName())) {//解析result map的discriminatordiscriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);} else {List<ResultFlag> flags = new ArrayList<ResultFlag>();if ("id".equals(resultChild.getName())) {flags.add(ResultFlag.ID);}//調5.1.1 buildResultMappingFromContext,得到ResultMappingresultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));}}//最后再調ResultMapResolver得到ResultMapResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);try {return resultMapResolver.resolve();} catch (IncompleteElementException e) {configuration.addIncompleteResultMap(resultMapResolver);throw e;}}解析完成之后ResultMapResolver將resultMap封裝到configuration中,完成解析。
最后buildStatementFromContext構建sql語句
最后將sql語句封裝到configuration中。
4 SQLSessionFactory對象的創建
通過上面的一些列解析以及封裝,把mybatis的配置信息全部都封裝到configuration中了,所以mybatis中configuration是一個很重要的類
// 最后一個build方法使用了一個Configuration作為參數,并返回DefaultSqlSessionFactorypublic SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);}最終就獲取到了SqlSessionFactory了,以上就是mybatis的初始化過程。
總結
以上是生活随笔為你收集整理的Mybatis源码解析之Mybatis初始化过程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 拍拍贷年龄要求
- 下一篇: Mybatis源码解析-sql执行