mybatis原始碼分析:啟動過程
mybatis在開發中作為一個ORM框架使用的比較多,所謂ORM指的是Object Relation Mapping,直譯過來就是物件關係對映,這個對映指的是java中的物件和資料庫中的記錄的對映,也就是一個java物件對映資料庫中的一條記錄。瞭解了mybatis的背景及作用下面看mybatis的使用及從原始碼分析啟動過程。
一、概述
要使用mybatis必須要引入mybatis的jar包,由於我這裡需要檢視原始碼,使用的mybatis原始碼作為依賴。首先需要下載原始碼,可執行從github上下載,mybatis下載下來是maven工程,按照maven匯入的方式進行匯入即可,詳細的步驟在這裡不在贅述。
引入了mybatis的依賴便可以開發mybatis的程式,我這裡使用的原始碼版本為:3-3.4.x版本。
1、核心配置檔案
mybatis核心配置檔案,一般命名為mybatis-config.xml,說是核心配置檔案一點也不錯,這個檔案包含了使用mybatis的時候的所有配置,只有正確載入了此檔案,mybatis才可正常工作。下面是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> <settings> <!-- 設定日誌輸出為LOG4J --> <setting name="logImpl" value="LOG4J" /> <!--將以下畫線方式命名的資料庫列對映到 Java 物件的駝峰式命名屬性中--> <setting name= "mapUnderscoreToCamelCase" value="true" /> </settings> <!--簡化類名稱空間 --> <typeAliases> </typeAliases> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="UNPOOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://127.0.0.1:3306/test" /> <property name="username" value="user" /> <property name="password" value="user" /> </dataSource> </environment> </environments> <mappers> <!--常規做法--> <mapper resource="cn/com/mybatis/dao/UserMapper.xml"/> <mapper resource="cn/com/mybatis/dao/MenuMapper.xml"/> <!--第二種做法--> <!-- <package name="cn.com.mybatis.dao"/> --> </mappers> </configuration>
上面是一個mybatis-config.xml檔案的例項,在configuration標籤中配置了mappers、settings、environments等標籤,這些標籤代表的意思及如何解析在後面會詳細分析。
這裡sql的配置方式有註解和對映檔案兩種方式,這裡採用對映檔案的方式,所以在mybatis-config.xml檔案中配置了Mapper檔案,下面看UserMapper.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.mybatis.dao.UserMapper"> <select id="selectUser" resultType="hashmap"> select * from e_user </select> </mapper>
上面的UserMapper.xml只有一個select標籤,另外在mapper標籤中配置了namespace屬性,這個屬性很關鍵,代表的是一個應對映檔案對應的介面。下面看UserMapper介面,
package cn.com.mybatis.dao; import java.util.HashMap; import java.util.List; public interface UserMapper { public List<HashMap> selectUser(); }
細心的讀者會發現介面中的方法名和對映檔案中的select標籤的id是一樣的,沒錯這裡必須是一致,必須一一對應,至於為什麼要保持一致,後面會通過原始碼分析,並且在一同一個namespace中不能包含同名的方法,也就是對映檔案中的id不允許重複。
有了上面的這些配置,便可以開始mybatis之旅了,下面看下每個檔案的位置,
二、詳述
上面已經把mybatis的環境及程式碼已經分析了,下面看測試程式碼,
package cn.com.mybatis.test; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; 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 cn.com.mybatis.dao.UserMapper; public class TestMybatis { public static void main(String[] args) throws IOException { // TODO Auto-generated method stub //載入核心配置檔案 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//生成一個SqlSessionFactoryBuilder物件 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
//建立一個SqlSessionFactory物件 SqlSessionFactory factory = builder.build(inputStream);
//獲得一個SqlSession物件 SqlSession session=factory.openSession();
//獲得一個UserMapper UserMapper userMapper=session.getMapper(UserMapper.class); List<HashMap> users=userMapper.selectUser(); System.out.println(users.size()); } }
上面的程式碼,即使用mybatis的過程,下面來分析。
1、讀取配置檔案
下面看讀取mybatis核心檔案的程式碼,
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
這裡就一句話,把mybatis-config.xml轉化為InputStream物件,這裡mybatis-config.xml檔案是在類路徑下(WEB-INF/classes)下,這裡Resources類是如何讀取檔案,後續詳細分析,只要明白這裡會獲得InputStream就好。
2、建立SqlSessionFactoryBuilder
下面需要建立一個SqlSessionFactoryBuilder物件,看這個類名可以猜到應該使用的是建造者模式,
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
看下具體的SqlSessionFactoryBuilder類,下面是其所有的方法,
可以看到都是build方法,那麼其構造方法也就是預設的。在SqlSessionFactoryBuilder類中所有的方法都是build方法,這是標準的建造者模式,可以看到返回值都是SqlSessionFactory。在mybatis中很多地方都使用了建造者模式,後邊會進行專門的分析。
下面看生成SqlSessionFactory,
SqlSessionFactory factory = builder.build(inputStream);
呼叫的SqlSessionFactoryBuilder的build(InputStream)方法,
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. } } }
在上面的程式碼中,使用inputStream生成一個XMLConfigBuilder,這裡又是一個建造者模式,看XMLConfigBuilder的構造方法,
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
//呼叫下面的構造方法 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
//初始化父類BaseBuilder類的configuration super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
下面看其parse()方法,此方法構造的是在這裡建造的物件是Configuration物件,
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true;
//解析mybatis-config.xml檔案中的<configuration>標籤,把該標籤中的內容 parseConfiguration(parser.evalNode("/configuration")); return configuration; }
從上面的程式碼中,可以看出呼叫parseConfiguration方法,這個方法就是解析<configuration>標籤,如下,
private void parseConfiguration(XNode root) { try { //issue #117 read properties first //解析properties標籤 propertiesElement(root.evalNode("properties")); //解析settings標籤 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); //解析別名標籤,例<typeAlias alias="user" type="cn.com.bean.User"/> typeAliasesElement(root.evalNode("typeAliases")); //解析外掛標籤 pluginElement(root.evalNode("plugins")); //解析objectFactory標籤,此標籤的作用是mybatis每次建立結果物件的新例項時都會使用ObjectFactory,如果不設定 //則預設使用DefaultObjectFactory來建立,設定之後使用設定的 objectFactoryElement(root.evalNode("objectFactory")); //解析objectWrapperFactory標籤 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); //解析reflectorFactory標籤 reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 //解析environments標籤 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); //解析<mappers>標籤 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
上面的方法後面會逐一進行分析,主要就是解析核心配置檔案mybatis-config.xml中的配置,並放到Configuration物件中。
再回到上面的build(parse.parse())方法,其定義如下,
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
從上面的程式碼,可以看到使用configuration生成一個DefaultSqlSessionFactory物件。
3、建立SqlSessionFactory
上面分析到SqlSessionFactoryBuilder最後會返回一個DefaultSqlSessionFactory物件,
public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; }
可以看到,把Configuration物件直接賦值給了DefaultSqlSessionFactory物件的configuration屬性。
4、建立SqlSession
SqlSession session=factory.openSession();
上面的程式碼呼叫factory的openSession()方法,也就是DefaultSqlSesssionFactory的openSession()方法,
@Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); }
呼叫了下面的方法,
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //獲得mybatis核心配置檔案中的environment資訊,包括dataSource id trasacationFactory final Environment environment = configuration.getEnvironment(); //獲得transactionFactory,如果environment中沒有則使用ManagedTransactionFactory final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); final Executor executor = configuration.newExecutor(tx, execType); return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
上面的方法返回了一個DefaultSqlSession物件,具體的過程就是使用上面的引數構造一個DefaultSqlSession物件。
5、獲取Mapper物件
使用下面的程式碼獲取一個Mapper物件,有了Mapper物件便可以呼叫方法,進行資料庫操作,
UserMapper userMapper=session.getMapper(UserMapper.class);
上面的程式碼從DefaultSqlSession中呼叫getMapper返回一個UserMapper物件,這裡UserMapper是一個代理物件,至於為什麼是代理物件,先不分析,先了解其過程,
@Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
可以看出是從DefaultSqlSession的Configuration中獲得該Mapper,下面繼續看,
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//返回的是下面的方法 return mapperRegistry.getMapper(type, sqlSession); } //呼叫此方法 @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
從上面中可以看到從knownMappers中根據type,這裡就是UserMapper.class返回一個MapperProxyFactory,最後返回MapperProxyFactory的一個例項,
public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
下面看newInstance方法,
@SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) {
//JDK動態代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); }
到這裡我們可以看到最終返回的是一個代理物件,而且是JDK動態代理的一個物件,從我們只編寫了介面也可以猜出這裡返回的應該是一個JDK動態代理的類,因為JDK動態代理要求必須有介面。
6、執行操作
執行操作,則直接呼叫其方法即可,
List<HashMap> users=userMapper.selectUser();
從上面的分析制定useMapper是代理物件,那麼代理類便是上面的MapperProxy類,那麼執行selectUser方法,便會執行MapperProxy的invoke方法,那麼該類肯定也會實現InvocationHandler介面,下面,
在看其invoke方法,
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method);
//呼叫的是execute方法 return mapperMethod.execute(sqlSession, args); }
從上面可以看到呼叫的是mapperMethod.execute方法,並且把sqlSession方法作為引數傳進去。那也就是說最後呼叫的sqlSession的方法,下面看,
可以看到呼叫的sqlSession的方法,從這裡大體可以看出sqlSession是個重要的類。
三、總結
上面分析了mybatis的啟動過程,包括載入核心配置檔案(mybatis-config.xml)、SqlSessionFactory、SqlSession、執行操作資料庫方法。這裡僅僅分析了其執行過程,很多細節後續會一一分析,像載入配置檔案、Configuration類、DefaultSqlSession以及如何通過介面找到對應的Mapper檔案等內容。
有不正之處,歡迎指