Mybatis 介紹以及面試問題

(一)Mybatis介紹
MyBatis 本是apache的一個開源項目iBatis, 2010年這個項目由apache software foundation 遷移到了google code,並且改名爲MyBatis 。2013年11月遷移到Github。
iBATIS一詞來源於“internet”和“abatis”的組合,是一個基於Java的持久層框架。iBATIS提供的持久層框架包括SQL Maps和Data Access Objects(DAOs)
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。MyBatis 可以使用簡單的 XML 或註解來配置和映射原生信息,將接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java對象)
映射成數據庫中的記錄。

    Mybatis通過xml或註解的方式將要執行的statement配置起來,並通過java對象和statement中的sql進行映射生成最終執行的sql語句,最後由mybatis框架執行sql並將結果映射成java對象並返回。  

   (二)框架流程:

  1.加載配置:配置來源於兩個地方,一處是配置文件,一處是Java代碼的註解,將SQL的配置信息加載成爲一個個MappedStatement對象(包括了傳入參數映射配置、執行的SQL語句、結果映射配置),存儲在內存中。

  2.SQL解析:當API接口層接收到調用請求時,會接收到傳入SQL的ID和傳入對象(可以是Map、JavaBean或者基本數據類型),Mybatis會根據SQL的ID找到對應的MappedStatement,然後根據傳入參數對象對MappedStatement進行解析,解析後可以得到最終要執行的SQL語句和參數。

   3.SQL執行:將最終得到的SQL和參數拿到數據庫進行執行,得到操作數據庫的結果。

   4.結果映射:將操作數據庫的結果按照映射的配置進行轉換,可以轉換成HashMap、JavaBean或者基本數據類型,並將最終結果返回。

 

    (三)MyBatis優缺點

優點:

1、簡單易學

       mybatis本身就很小且簡單。沒有任何第三方依賴,最簡單安裝只要兩個jar文件+配置幾個sql映射文件易於學習,易於使用,通過文檔和源代碼,可以比較完全的掌握它的設計思路和實現。

2、靈活

       mybatis不會對應用程序或者數據庫的現有設計強加任何影響。 sql寫在xml裏,便於統一管理和優化。通過sql基本上可以實現我們不使用數據訪問框架可以實現的所有功能,或許更多。

3、解除sql與程序代碼的耦合

       通過提供DAL層,將業務邏輯和數據訪問邏輯分離,使系統的設計更清晰,更易維護,更易單元測試。sql和代碼的分離,提高了可維護性。

4、提供xml標籤,支持編寫動態sql。

    缺點:

1、編寫SQL語句時工作量很大,尤其是字段多、關聯表多時,更是如此。

2、SQL語句依賴於數據庫,導致數據庫移植性差,不能更換數據庫。

3、二級緩存機制不佳

 (四)功能架構

    我們把Mybatis的功能架構分爲三層:
(1)API接口層:提供給外部使用的接口API,開發人員通過這些本地API來操縱數據庫。接口層一接收到調用請求就會調用數據處理層來完成具體的數據處理。
(2)數據處理層:負責具體的SQL查找、SQL解析、SQL執行和執行結果映射處理等。它主要的目的是根據調用的請求完成一次數據庫操作。

(3)基礎支撐層:負責最基礎的功能支撐,包括連接管理、事務管理、配置加載和緩存處理,這些都是共用的東西,將他們抽取出來作爲最基礎的組件。爲上層的數據處理層提供最基礎的支撐。


(五)Mybatis工作原理

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

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

不過MyBatis的配置文件以及操作類和實現方式都有了很大變化,這裏我們重點講述的是Mybatis,不是Ibatis;


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

一類用於指定數據源、事務屬性以及其他一些參數配置信息(通常是一個獨立的文件,可以稱之爲全局配置文件);

另一類則用於 指定數據庫表和程序之間的映射信息(可能不止一個文件,我們稱之爲映射文件)

這些文件的名字並沒有確定的要求;只是要最從特定的dtd的xml文件約束,即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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <!-- 配置數據庫連接信息 -->
            <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這個包下,所以resource寫成com/test/mapping/userMapper.xml-->
        <mapper resource="com/test/mapping/userMapper.xml"/>
    </mappers>

</configuration>

上述就是MyBatis的數據源,事務屬性,以及映射文件的索引;

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

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

package com.test.domain;

/**
 * users表所對應的實體類
 */
public class User {

    //實體類的屬性和表的字段名稱一一對應
    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的,這個的流程是怎麼樣??

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

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

resultType="com.test.domain.User"

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

兩個重要的類即:

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();

}

在構建SqlSessionFactory類的時候,將會對數據源及事務配置進行解析,具體在

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

org.apache.ibatis.builder.BaseBuilder類

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

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

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

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

zhuanlan.zhihu.com/p/31

其實一般各種輪子都會有一個解析XML後信息的專用存儲類,比如Config.Java,xxxConf.java,都是在啓動組件時解析XML配置以用作程序中使用的。


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

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);
}

其實就是根據一個map映射,key就是定義mapping時候的id來拿到的;

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

在默認情況下,即沒有設置settings的cache和executor屬性時,默認使用的

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;
}


所以調用到了

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);
}

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

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並且關閉Session;

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

事實上如果我們使用SqlSessionManager來管理,那麼開啓和關閉Session操作都不用我們來處理了。

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

下面是Interceptor類實現,開啓和關閉操作都交由了

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,就是利用動態代理,可以避免我們手寫mapper的id字符串,將查找sql過程和執行sql過程放到了代理處理中,更優雅些,不過大體流程就是這些,改變了查找sql的步驟,通過Mapper的方法名來查找對應的sql的,

 

(五)常見的Mybatis面試問題:


1#{}${}的區別是什麼?

答:${}Properties文件中的變量佔位符,它可以用於標籤屬性值和sql內部,屬於靜態文本替換,比如${driver}會被靜態替換爲com.mysql.jdbc.Driver#{}sql的參數佔位符,Mybatis會將sql中的#{}替換爲?號,在sql執行前會使用PreparedStatement的參數設置方法,按序給sql?號佔位符設置參數值,比如ps.setInt(0,parameterValue)#{item.name}的取值方式爲使用反射從參數對象中獲取item對象的name屬性值,相當於param.getItem().getName()

2Xml映射文件中,除了常見的select|insert|updae|delete標籤之外,還有哪些標籤?

答:還有很多其他的標籤,<resultMap><parameterMap><sql><include><selectKey>,加上動態sql9個標籤,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中<sql>sql片段標籤,通過<include>標籤引入sql片段,<selectKey>爲不支持自增的主鍵生成策略標籤。

3、最佳實踐中,通常一個Xml映射文件,都會寫一個Dao接口與之對應,請問,這個Dao接口的工作原理是什麼?Dao接口裏的方法,參數不同時,方法能重載嗎?

答:Dao接口,就是人們常說的Mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatementid值,接口方法內的參數,就是傳遞給sql的參數。Mapper接口是沒有實現類的,當調用接口方法時,接口全限名+方法名拼接字符串作爲key值,可唯一定位一個MappedStatement,舉例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到namespacecom.mybatis3.mappers.StudentDao下面id= findStudentByIdMappedStatement。在Mybatis中,每一個<select><insert><update><delete>標籤,都會被解析爲一個MappedStatement對象。

Dao接口裏的方法,是不能重載的,因爲是全限名+方法名的保存和尋找策略。

Dao接口的工作原理是JDK動態代理,Mybatis運行時會使用JDK動態代理爲Dao接口生成代理proxy對象,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然後將sql執行結果返回。

4Mybatis是如何進行分頁的?分頁插件的原理是什麼?

答:Mybatis使用RowBounds對象進行分頁,它是針對ResultSet結果集執行的內存分頁,而非物理分頁,可以在sql內直接書寫帶有物理分頁的參數來完成物理分頁功能,也可以使用分頁插件來完成物理分頁。

分頁插件的基本原理是使用Mybatis提供的插件接口,實現自定義插件,在插件的攔截方法內攔截待執行的sql,然後重寫sql,根據dialect方言,添加對應的物理分頁語句和物理分頁參數。

舉例:select * from student,攔截sql後重寫爲:selectt.* from select * from studentt limit010

5、簡述Mybatis的插件運行原理,以及如何編寫一個插件。

答:Mybatis僅可以編寫針對ParameterHandlerResultSetHandlerStatementHandlerExecutor4種接口的插件,Mybatis使用JDK的動態代理,爲需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會進入攔截方法,具體就是InvocationHandlerinvoke()方法,當然,只會攔截那些你指定需要攔截的方法。

實現MybatisInterceptor接口並複寫intercept()方法,然後在給插件編寫註解,指定要攔截哪一個接口的哪些方法即可,記住,別忘了在配置文件中配置你編寫的插件。

6Mybatis執行批量插入,能返回數據庫主鍵列表嗎?

答:能,JDBC都能,Mybatis當然也能。

7Mybatis動態sql是做什麼的?都有哪些動態sql?能簡述一下動態sql的執行原理不?

答:Mybatis動態sql可以讓我們在Xml映射文件內,以標籤的形式編寫動態sql,完成邏輯判斷和動態拼接sql的功能,Mybatis提供了9種動態sql標籤trim|where|set|foreach|if|choose|when|otherwise|bind

其執行原理爲,使用OGNLsql參數對象中計算表達式的值,根據表達式的值動態拼接sql,以此來完成動態sql的功能。

8Mybatis是如何將sql執行結果封裝爲目標對象並返回的?都有哪些映射形式?

答:第一種是使用<resultMap>標籤,逐一定義列名和對象屬性名之間的映射關係。第二種是使用sql列的別名功能,將列別名書寫爲對象屬性名,比如T_NAME AS NAME,對象屬性名一般是name,小寫,但是列名不區分大小寫,Mybatis會忽略列名大小寫,智能找到與之對應對象屬性名,你甚至可以寫成T_NAME AS NaMeMybatis一樣可以正常工作。

有了列名與屬性名的映射關係後,Mybatis通過反射創建對象,同時使用反射給對象的屬性逐一賦值並返回,那些找不到映射關係的屬性,是無法完成賦值的。

9Mybatis能執行一對一、一對多的關聯查詢嗎?都有哪些實現方式,以及它們之間的區別。

答:能,Mybatis不僅可以執行一對一、一對多的關聯查詢,還可以執行多對一,多對多的關聯查詢,多對一查詢,其實就是一對一查詢,只需要把selectOne()修改爲selectList()即可;多對多查詢,其實就是一對多查詢,只需要把selectOne()修改爲selectList()即可。

關聯對象查詢,有兩種實現方式,一種是單獨發送一個sql去查詢關聯對象,賦給主對象,然後返回主對象。另一種是使用嵌套查詢,嵌套查詢的含義爲使用join查詢,一部分列是A對象的屬性值,另外一部分列是關聯對象B的屬性值,好處是隻發一個sql查詢,就可以把主對象和其關聯對象查出來。

那麼問題來了,join查詢出來100條記錄,如何確定主對象是5個,而不是100個?其去重複的原理是<resultMap>標籤內的<id>子標籤,指定了唯一確定一條記錄的id列,Mybatis根據<id>列值來完成100條記錄的去重複功能,<id>可以有多個,代表了聯合主鍵的語意。

同樣主對象的關聯對象,也是根據這個原理去重複的,儘管一般情況下,只有主對象會有重複記錄,關聯對象一般不會重複。

舉例:下面join查詢出來6條記錄,一、二列是Teacher對象列,第三列爲Student對象列,Mybatis去重複處理後,結果爲1個老師6個學生,而不是6個老師6個學生。

      t_id    t_name          s_id

|         1 | teacher      |      38 |

|         1 | teacher      |      39 |

|         1 | teacher      |      40 |

|         1 | teacher      |      41 |

|         1 | teacher      |      42 |

|         1 | teacher      |      43 |

10Mybatis是否支持延遲加載?如果支持,它的實現原理是什麼?

答:Mybatis僅支持association關聯對象和collection關聯集合對象的延遲加載,association指的就是一對一,collection指的就是一對多查詢。在Mybatis配置文件中,可以配置是否啓用延遲加載lazyLoadingEnabled=true|false

它的原理是,使用CGLIB創建目標對象的代理對象,當調用目標方法時,進入攔截器方法,比如調用a.getB().getName(),攔截器invoke()方法發現a.getB()null值,那麼就會單獨發送事先保存好的查詢關聯B對象的sql,把B查詢上來,然後調用a.setB(b),於是a的對象b屬性就有值了,接着完成a.getB().getName()方法的調用。這就是延遲加載的基本原理。

當然了,不光是Mybatis,幾乎所有的包括Hibernate,支持延遲加載的原理都是一樣的。

11MybatisXml映射文件中,不同的Xml映射文件,id是否可以重複?

答:不同的Xml映射文件,如果配置了namespace,那麼id可以重複;如果沒有配置namespace,那麼id不能重複;畢竟namespace不是必須的,只是最佳實踐而已。

原因就是namespace+id是作爲Map<String, MappedStatement>key使用的,如果沒有namespace,就剩下id,那麼,id重複會導致數據互相覆蓋。有了namespace,自然id就可以重複,namespace不同,namespace+id自然也就不同。

12Mybatis中如何執行批處理?

答:使用BatchExecutor完成批處理。

13Mybatis都有哪些Executor執行器?它們之間的區別是什麼?

答:Mybatis有三種基本的Executor執行器,SimpleExecutorReuseExecutorBatchExecutor

SimpleExecutor每執行一次updateselect,就開啓一個Statement對象,用完立刻關閉Statement對象。

ReuseExecutor執行updateselect,以sql作爲key查找Statement對象,存在就使用,不存在就創建,用完後,不關閉Statement對象,而是放置於Map<String,Statement>內,供下一次使用。簡言之,就是重複使用Statement對象。

BatchExecutor執行update(沒有selectJDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統一執行(executeBatch()),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同。

作用範圍:Executor的這些特點,都嚴格限制在SqlSession生命週期範圍內。

14Mybatis中如何指定使用哪一種Executor執行器?

答:在Mybatis配置文件中,可以指定默認的ExecutorType執行器類型,也可以手動給DefaultSqlSessionFactory的創建SqlSession的方法傳遞ExecutorType類型參數。

15Mybatis是否可以映射Enum枚舉類?

答:Mybatis可以映射枚舉類,不單可以映射枚舉類,Mybatis可以映射任何對象到表的一列上。映射方式爲自定義一個TypeHandler,實現TypeHandlersetParameter()getResult()接口方法。TypeHandler有兩個作用,一是完成從javaTypejdbcType的轉換,二是完成jdbcTypejavaType的轉換,體現爲setParameter()getResult()兩個方法,分別代表設置sql問號佔位符參數和獲取列查詢結果。

16Mybatis映射文件中,如果A標籤通過include引用了B標籤的內容,請問,B標籤能否定義在A標籤的後面,還是說必須定義在A標籤的前面?

答:雖然Mybatis解析Xml映射文件是按照順序解析的,但是,被引用的B標籤依然可以定義在任何地方,Mybatis都可以正確識別。

原理是,Mybatis解析A標籤,發現A標籤引用了B標籤,但是B標籤尚未解析到,尚不存在,此時,Mybatis會將A標籤標記爲未解析狀態,然後繼續解析餘下的標籤,包含B標籤,待所有標籤解析完畢,Mybatis會重新解析那些被標記爲未解析的標籤,此時再解析A標籤時,B標籤已經存在,A標籤也就可以正常解析完成了。

17、簡述MybatisXml映射文件和Mybatis內部數據結構之間的映射關係?

答:Mybatis將所有Xml配置信息都封裝到All-In-One重量級對象Configuration內部。在Xml映射文件中,<parameterMap>標籤會被解析爲ParameterMap對象,其每個子元素會被解析爲ParameterMapping對象。<resultMap>標籤會被解析爲ResultMap對象,其每個子元素會被解析爲ResultMapping對象。每一個<select><insert><update><delete>標籤均會被解析爲MappedStatement對象,標籤內的sql會被解析爲BoundSql對象。

18、爲什麼說Mybatis是半自動ORM映射工具?它與全自動的區別在哪裏?

答:Hibernate屬於全自動ORM映射工具,使用Hibernate查詢關聯對象或者關聯集合對象時,可以根據對象關係模型直接獲取,所以它是全自動的。而Mybatis在查詢關聯對象或關聯集合對象時,需要手動編寫sql來完成,所以,稱之爲半自動ORM映射工具。

面試題看似都很簡單,但是想要能正確回答上來,必定是研究過源碼且深入的人,而不是僅會使用的人或者用的很熟的人,以上所有面試題及其答案所涉及的內容,在我的Mybatis系列博客中都有詳細講解和原理分析

19 Mybatis對象關聯實例

    在單表查詢中,屬性名和數據庫相同的字段可以省略,多表不可省略,省略則爲空

    • // 一對一
      <resultMap type="Orders" id="orders">
          <result column="id" property="id"/>
      
          <association property="user" javaType="User">
          //關聯另一張表
              <id column="id" property="id"/>   // id
              <result column="name" property="name"></result>  // 屬性 即查詢出來顯示的名字
          </association>
      </resultMap>
      
      <select id="onemany" resultMap="orders">
          select u.id ,o.number,o.dic,u.name from orders o left JOIN user u on o.user_id=u.id
      </select>       
      // 一對多
      <resultMap type="User" id="user">
          <id column="id" property="id"/>
          <result column="name" property="name"/>
          <!-- 一對多 -->
          <collection property="list2" ofType="Orders">   // ofType : 每個屬性的類型
              <id column="oid" property="id"/>   //需要設置id,如果兩個對象屬性相同,則會視爲一條記錄
              <result column="number" property="number"/>
          </collection>
      </resultMap>
      <select id="selectUserList" resultMap="user">
          select u.id,o.id as oid,o.number,o.dic,u.name from user u left JOIN orders o on o.user_id=u.id
      </select>
      
  1. resultType resultMap的區別

    • 類的名字和數據庫相同時,可以直接設置resultType參數爲Pojo類

    • 若不同,需要設置resultMap 將結果名字和Pojo名字進行轉換,

  2. Map 直接#{key}就可以取得對應的值

(20)  在mapper中如何傳遞多個參數
  直接在方法中傳遞參數,xml文件用#{0} #{1}來獲取
  使用 @param 註解:這樣可以直接在xml文件中通過#{name}來獲取

(21)如何獲取自動生成的(主)鍵值
配置文件設置usegeneratedkeys 爲true



參考:(1)百度百科

         (2)https://www.zhihu.com/question/25007334/answer/266187562

         (3)https://www.cnblogs.com/huajiezh/p/6415388.html

         (4)https://www.cnblogs.com/liyuhui-Z/p/7832866.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章