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文件等內容。
有不正之處,歡迎指正。