溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Mybatis工作原理

發(fā)布時(shí)間:2020-08-09 22:05:05 來源:ITPUB博客 閱讀:146 作者:高級(jí)java架構(gòu)師 欄目:軟件技術(shù)

作者:wuxinliulei
鏈接:https://www.zhihu.com/question/25007334/answer/266187562
來源:知乎
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。

Mybatis原名Ibatis,在2011年從Ibatis2.x升級(jí)到Mybatis 3.X,并將項(xiàng)目地址從Apache遷移到了Google code,事實(shí)上我們看MyBatis的類全路徑名,還是保留了Apache和Ibatis的的包前綴

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

不過MyBatis的配置文件以及操作類和實(shí)現(xiàn)方式都有了很大變化,這里我們重點(diǎn)講述的是Mybatis,不是Ibatis;


Mybatis的配置文件一共由兩類:

一類用于指定數(shù)據(jù)源、事務(wù)屬性以及其他一些參數(shù)配置信息(通常是一個(gè)獨(dú)立的文件,可以稱之為全局配置文件);

另一類則用于 指定數(shù)據(jù)庫表和程序之間的映射信息(可能不止一個(gè)文件,我們稱之為映射文件)

這些文件的名字并沒有確定的要求;只是要最從特定的dtd的xml文件約束,即xml標(biāo)簽需要符合要求;

<?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" />
            <!-- 配置數(shù)據(jù)庫連接信息 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 注冊userMapper.xml文件, 
        userMapper.xml位于com.test.mapping這個(gè)包下,所以resource寫成com/test/mapping/userMapper.xml-->
        <mapper resource="com/test/mapping/userMapper.xml"/>
    </mappers>
</configuration>

上述就是MyBatis的數(shù)據(jù)源,事務(wù)屬性,以及映射文件的索引;

<?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">
<!-- 為這個(gè)mapper指定一個(gè)唯一的namespace,namespace的值習(xí)慣上設(shè)置成包名+sql映射文件名,這樣就能夠保證namespace的值是唯一的
例如namespace="com.test.mapping.userMapper"就是com.test.mapping(包名)+userMapper(userMapper.xml文件去除后綴)
 -->
<mapper namespace="com.test.mapping.userMapper">
    <!-- 
        根據(jù)id查詢得到一個(gè)user對象
     -->
    <select id="getUser" parameterType="int" 
        resultType="com.test.domain.User">
        select * from users where id=#{id}
    </select>
</mapper>

上面是數(shù)據(jù)庫表與程序之間的映射文件,定義了一個(gè)根據(jù)id來獲取User對象的sql

package com.test.domain;
/**
 * users表所對應(yīng)的實(shí)體類
 */
public class User {
    //實(shí)體類的屬性和表的字段名稱一一對應(yīng)
    private int id;
    private String name;
    private int age;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

問題:

mybatis是怎么在程序中順利的找到sqlmapper的,這個(gè)的流程是怎么樣??

// mybatis的配置文件
String resource = "conf.xml";
// 使用類加載器加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件)
InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
// 構(gòu)建sqlSession的工廠
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);

題主問的sqlmapper可以理解為兩種組件,一種是mapping映射文件,通過id名來獲取相應(yīng)的sql語句,操作數(shù)據(jù)庫;一種是sql的返回對象,

resultType="com.test.domain.User"

這個(gè)就是返回的sql結(jié)果映射成為具體的POJO(Plain Ordinary Java Object)對象;

兩個(gè)重要的類即:

org.apache.ibatis.session.SqlSessionFactory;

org.apache.ibatis.session.SqlSession;

package org.apache.ibatis.session;
import java.sql.Connection;
public interface SqlSessionFactory {
  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();
}

在構(gòu)建SqlSessionFactory類的時(shí)候,將會(huì)對數(shù)據(jù)源及事務(wù)配置進(jìn)行解析,具體在

org.apache.ibatis.builder.xml.XMLConfigBuilder類

org.apache.ibatis.builder.BaseBuilder類

XMLConfigBuilder類是解析產(chǎn)生org.apache.ibatis.Session.Configuration類的的具體類,Configuration類中將保存中所有的配置;

mybatis的源代碼解析(1)--xml文件解析 - 王久勇 - 博客園

這篇博客介紹了一些xml文件解析的基本;

具體mybatis的xml解析使用到了XPath方式,具體解析過程參看

zhuanlan.zhihu.com/p/31

其實(shí)一般各種輪子都會(huì)有一個(gè)解析XML后信息的專用存儲(chǔ)類,比如Config.Java,xxxConf.java,都是在啟動(dòng)組件時(shí)解析XML配置以用作程序中使用的。

引用網(wǎng)絡(luò)上的一段源代碼

public class Test1 {
    public static void main(String[] args) throws IOException {
        //mybatis的配置文件
        String resource = "conf.xml";
        //使用類加載器加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件)
        InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
        //構(gòu)建sqlSession的工廠
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
        //使用MyBatis提供的Resources類加載mybatis的配置文件(它也加載關(guān)聯(lián)的映射文件)
        //Reader reader = Resources.getResourceAsReader(resource); 
        //構(gòu)建sqlSession的工廠
        //SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        //創(chuàng)建能執(zhí)行映射文件中sql的sqlSession
        SqlSession session = sessionFactory.openSession();
        /**         * 映射sql的標(biāo)識(shí)字符串,         * me.gacl.mapping.userMapper是userMapper.xml文件中mapper標(biāo)簽的namespace屬性的值,         * getUser是select標(biāo)簽的id屬性值,通過select標(biāo)簽的id屬性值就可以找到要執(zhí)行的SQL         */
        String statement = "me.gacl.mapping.userMapper.getUser";//映射sql的標(biāo)識(shí)字符串
        //執(zhí)行查詢返回一個(gè)唯一user對象的sql
        User user = session.selectOne(statement, 1);
        System.out.println(user);
    }}

通過跟蹤源代碼可以看到SqlSession通過mapper映射的id來查找數(shù)據(jù)的方法;

org.apache.ibatis.session.defaults.DefaultSqlSession類

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
{
     try
     {
	 MappedStatement ms = configuration.getMappedStatement(statement);
	 List<E> result = executor.<E> query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
	 return result;
     }
     catch (Exception e)
     {
	throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
     }
     finally
     {
	ErrorContext.instance().reset();  
     }
}

org.apache.ibatis.session.Configuration類

public MappedStatement getMappedStatement(String id)
{
	return this.getMappedStatement(id, true);
}


protected final Map<String, MappedStatement> mappedStatements = 
new StrictMap<MappedStatement>("Mapped Statements collection");


public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements)
{
	if (validateIncompleteStatements)
	{
		buildAllStatements();
	}
	return mappedStatements.get(id);
}

其實(shí)就是根據(jù)一個(gè)map映射,key就是定義mapping時(shí)候的id來拿到的;

至此,

------------------------------



上述org.apache.ibatis.session.defaults.DefaultSqlSession類對象中的 selectList方法中的executor對象,

在默認(rèn)情況下,即沒有設(shè)置settings的cache和executor屬性時(shí),默認(rèn)使用的

org.apache.ibatis.executor.CachingExecutor類

public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit)
{
	        executorType = executorType == null ? defaultExecutorType : executorType;
	        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
		Executor executor;
		if (ExecutorType.BATCH == executorType)
		{
			executor = new BatchExecutor(this, transaction);
		}
		else if (ExecutorType.REUSE == executorType)
		{
			executor = new ReuseExecutor(this, transaction);
		}
		else
		{
			executor = new SimpleExecutor(this, transaction);
		}
		if (cacheEnabled)
		{
			executor = new CachingExecutor(executor, autoCommit);
		}
		executor = (Executor) interceptorChain.pluginAll(executor);
		return executor;
}


所以調(diào)用到了

public <E> List<E> query(MappedStatement ms, Object parameterObject, 
                        RowBounds rowBounds, ResultHandler resultHandler)
			throws SQLException
{
      BoundSql boundSql = ms.getBoundSql(parameterObject);
      CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
      return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

在真正查詢時(shí)先查詢cache,可以看到這個(gè)cache層級(jí)在MappedStatement上,也就是在單個(gè)Sql上;若查到,則直接返回,無則通過jdbc查詢,且返回結(jié)果

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler,
		CacheKey key, BoundSql boundSql) throws SQLException
{
	Cache cache = ms.getCache();
	if (cache != null)
	{
		flushCacheIfRequired(ms);
		if (ms.isUseCache() && resultHandler == null)
		{
			ensureNoOutParams(ms, key, parameterObject, boundSql);
			if (!dirty)
			{
				cache.getReadWriteLock().readLock().lock();
				try
				{
					@SuppressWarnings("unchecked")
					List<E> cachedList = (List<E>) cache.getObject(key);
					if (cachedList != null)
						return cachedList;
				}
				finally
				{
					cache.getReadWriteLock().readLock().unlock();
				}
			}
			List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
			tcm.putObject(cache, key, list); // issue #578. Query must be
												// not synchronized to
												// prevent deadlocks
			return list;
		}
	}
	return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

上述的使用方式是未使用代理的方式,這樣需要我們自行openSession并且關(guān)閉Session;

SqlSession session = null;
try
{
	session = sessionFactory.openSession();
	/**
	 * 映射sql的標(biāo)識(shí)字符串, com.test.mapping.userMapper是userMapper.
	 * xml文件中mapper標(biāo)簽的namespace屬性的值,
	 * getUser是select標(biāo)簽的id屬性值,通過select標(biāo)簽的id屬性值就可以找到要執(zhí)行的SQL
	 */
	String statement = "com.test.mapping.userMapper.getUser";// 映射sql的標(biāo)識(shí)字符串
	// 執(zhí)行查詢返回一個(gè)唯一user對象的sql
	User user = session.selectOne(statement, 1);
	System.out.println(user);
}
catch (Exception e)
{
	// TODO: handle exception
}
finally
{
	if (session != null)
	{
		session.close();
	}
}

事實(shí)上如果我們使用SqlSessionManager來管理,那么開啟和關(guān)閉Session操作都不用我們來處理了。

final SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(sessionFactory);
String statement = "com.test.mapping.userMapper.getUser";// 映射sql的標(biāo)識(shí)字符串
User user = sqlSessionManager.selectOne(statement, 1);
System.out.println(user);

下面是Interceptor類實(shí)現(xiàn),開啟和關(guān)閉操作都交由了

private class SqlSessionInterceptor implements InvocationHandler
{
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
	{
		final SqlSession sqlSession = SqlSessionManager.this.localSqlSession.get();
		if (sqlSession != null)
		{
			try
			{
				return method.invoke(sqlSession, args);
			}
			catch (Throwable t)
			{
				throw ExceptionUtil.unwrapThrowable(t);
			}
		}
		else
		{
			final SqlSession autoSqlSession = openSession();
			try
			{
				final Object result = method.invoke(autoSqlSession, args);
				autoSqlSession.commit();
				return result;
			}
			catch (Throwable t)
			{
				autoSqlSession.rollback();
				throw ExceptionUtil.unwrapThrowable(t);
			}
			finally
			{
				autoSqlSession.close();
			}
		}
	}
}

如果使用Mapper方式來操作SQL,就是利用動(dòng)態(tài)代理,可以避免我們手寫mapper的id字符串,將查找sql過程和執(zhí)行sql過程放到了代理處理中,更優(yōu)雅些,不過大體流程就是這些,改變了查找sql的步驟,通過Mapper的方法名來查找對應(yīng)的sql的,


向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI