Mybatis-常見面試題

1、什麼是Mybatis?
(1)mybatis是一個實現了JPA(Java-Persistence-API,Java持久化接口)規範的半ORM(Object Relational Mapping,對象關係映射)框架。它的底層就是一個JDBC封裝的組件。
(2)mybatis可以通過接口和XML(或註解)的方式來提供POJO到數據庫的映射。

2、Mybatis的優點?
(1)對JDBC封裝,屏蔽了JDBC繁雜的操作,消除了大量冗餘代碼。幾乎可以代替JDBC,JDBC支持的數據庫MyBatis都支持。
(2)SQL寫在XML中方便統一管理,解除SQL和程序代碼的耦合
(3)提供動態自動映射、動態SQL、級聯、緩存和註解等特性,使用方便。

3、通常一個Xml映射文件,都會寫一個Dao接口與之對應,Dao接口的工作原理是什麼?Dao接口裏的方法,參數不同時能重載嗎?
工作原理:
(1)XML映射文件中,每一個 <select>、<insert>、<update>、<delete>標籤都會被解析爲一個MappedStatement對象,保存到Map<String, MappedStatement> mappedStatements中,其中mappedStatements的鍵值 key=接口權限名+方法名
(2)Dao接口中,就是人們常說的mapper接口,接口的全限名,就是映射文件中的namespace的值,接口的方法名,就是映射文件中MappedStatement的id值,接口方法內的參數,就是傳遞給sql的參數。
(3)Dao接口沒有實現類,他的實現原理是JDK動態代理,Mybatis運行時會使用JDK動態代理爲Dao接口生成代理proxy對象,當調用接口方法時,代理對象proxy會攔截接口方法,轉而執行MappedStatement所代表的sql,然後將sql執行結果返回。

不能重載,因爲是使用 全限名+方法名 的保存和尋找策略。

4、Mybatis是如何進行分頁的?
(1)內存分頁,使用RowBounds對ResultSet結果集執行內存分頁。
(2)通過書寫帶有分頁參數的SQL進行物理分頁,某些插件也可以進行物理分頁。

5、分頁插件的原理?
實現mybatis提供的插件接口,在攔截方法內攔截執行SQL語句,通過dialog方言,添加物理分頁語句和物理分頁參數。

6、在mapper中如何傳遞多個參數

(1)通過順序確定參數,#{index},index從0開始

public student selectByNA(String name,string address)

<select id="selectByNA" resultMap="BaseResultMap">
	select * from student where name=#{0} and address=#{1}
</select>

(2)通過@param("paramName")確定參數

public student selectByNA(@param("name") String name, @param("address") string address)

<select id="selectByNA" resultMap="BaseResultMap">
	select * from student where name=#{name} and address=#{address}
</select>

(3)通過map封裝參數

//設置參數,這裏map中的鍵值對應xml映射文件中sql參數名
Map<String,Object> paramMap=new HashMap<String,Object>();
paramMap,add("name","name111");
paramMap,add("address","address111");
Student stu=studentMapper.selectByNA(paramMap);

//接口
public student selectByNA(Map<String,Object> map)
//xml映射文件
<select id="selectByNA" resultMap="BaseResultMap">
	select * from student where name=#{name} and address=#{address}
</select>

7、Mybati動態SQL是什麼?它的執行原理?有哪些動態SQL?
(1)在XML映射文件中,以標籤的形式編寫動態SQL,完成邏輯判斷和動態拼寫SQL功能。
(2)mybatis使用OGNL(Object Graph Navigation Language,對象導航圖語言)從sql參數對象中計算表達式的值,根據表達式的值動態拼接sql。
(3)<if>、<choose>、<when>、<otherwise>、<trim>、<where>、<set>、<foreach>、<bind>

//test屬性相當於判斷真假
<if test=""></if> 

//相當於switch、case、default
<choose>
	<when test=""></when>
	<when test=""></when>
	<otherwise></otherwise>
</choose>

//prefix代表語句的前綴,prefixOverrides代表要的是需要去掉前綴字符,suffixOverrides表示需要去掉的後綴字符
<trim prefix="" prefixOverrides="" suffixOverrides="">...</trim>

//where元素內部條件成立時纔會加入where這個SQL關鍵字
<select>
	select * from student
	<where>
		<if test="name!=null and name!=''">
			and name=#{name} 
		</if>
		<if test="address!=null and address!=''">
			and address=#{address}
		</if>
	</where>
</select>
//等價於
<trim prefix="where" prefixOverrides="and">...</trim>

//set遇到逗號,會把對應的逗號去掉。常與<if>標籤連用,用語動態更新字段
<update id="updateStudent">
	update student
	<set>
		<if test="name!=null and name!=''">
			name=#{name},
		</if>
		<if test="address!=null and address!=''">
			address=#{address}
		</if>
	</set>
<update>
//等價於
<trim prefix="set" suffixOverrides=",">...</trim>


//collection是傳遞進來的參數名稱,item是循環中的當前元素,index是元素在集合中的下標,open、close、separator是包裝盒分隔符
<select id="getStudentList">
	select * from student where stuId in
	<foreach item="stuid" index="index" collections="stuIdList" open="(" separator="," close=")">
		#{stuid}
	</foreach>
</select>

//bind元素的作用通過OGNL表達式去定義一個上下文變量。address 是傳遞進來的參數名稱
<select>
	<bind name="rangeAddress" value="'%' + address + '%'">
	seletct * from student where address like #{rangeAddress}
</select>
<bind name="rangeAddress" value="'%' + address + '%'">

8、mybatis的一級、二級緩存區別?

所有的緩存對象的操作與維護都是由Executor器執行來完成的,一級緩存由BaseExecutor(包含SimpleExecutor、ReuseExecutor、BatchExecutor三個子類)負責維護,二級緩存由CachingExecutor負責維護。

(1)一級緩存: 基於 PerpetualCache 的 HashMap 本地緩存,其存儲作用域爲 Session,當 Session flush 或 close 之後,該 Session 中的所有 Cache 就將清空,默認打開一級緩存。

一級緩存存儲在SqlSession.Executor.PerpetualCache 中

public class DefaultSqlSession implements SqlSession {
	private final Configuration configuration;
    private final Executor executor;	//執行器
    private final boolean autoCommit;
    private boolean dirty;
    private List<Cursor<?>> cursorList;
    ......
}

public abstract class BaseExecutor implements Executor {
    private static final Log log = LogFactory.getLog(BaseExecutor.class);
    protected Transaction transaction;
    protected Executor wrapper;
    protected ConcurrentLinkedQueue<BaseExecutor.DeferredLoad> deferredLoads;
    protected PerpetualCache localCache;	//一級緩存的對象
    protected PerpetualCache localOutputParameterCache;	//用於緩存存儲過程的一級緩存
    protected Configuration configuration;
    protected int queryStack;
    private boolean closed;
	......
}    

public class PerpetualCache implements Cache {
    private final String id;
    private Map<Object, Object> cache = new HashMap();	//map來記錄緩存
	......
}

(2)二級緩存:
二級緩存默認是不開啓的,需要手動開啓二級緩存。實現二級緩存的時候,MyBatis要求返回的POJO必須是可序列化的。

<settings>
	<setting name = "cacheEnabled" value = "true" />
</settings>

//存放在共享緩存中數據進行序列化操作和反序列化操作
//因此數據對應實體類必須實現【序列化接口】
public class Dept implements Serializable
{
	private String name;
	......
}

當二級緩存開啓後,同一個命名空間(namespace) 所有的操作語句,都影響着一個共同的 cache,也就是二級緩存被多個 SqlSession 共享,是一個全局的變量。當開啓緩存後,會使用 CachingExecutor 裝飾 Executor,進入一級緩存的查詢流程前,先在CachingExecutor 進行二級緩存的查詢(查詢流程: 二級緩存 -> 一級緩存 -> 數據庫)。

public class Configuration {
	......
	protected final Map<String, MappedStatement> mappedStatements;	//
    protected final Map<String, Cache> caches;	//二級緩存存放位置

}

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

(1)實現方法:
實現mybatis的Interceptor接口並複寫intercept()方法,給插件編寫註解,在配置文件中配置你編寫的插件。

public interface Interceptor {
    Object intercept(Invocation var1) throws Throwable;

    Object plugin(Object var1);

    void setProperties(Properties var1);
}

@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args = {Connection.class,Integer.class}))
public class MyPlugin implements Interceptor {
    private Logger log=Logger.getLogger(MyPlugin.class);
    private Properties props=null;
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler=(StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler= SystemMetaObject.forObject(statementHandler);
        while (metaStatementHandler.hasGetter("h")){
            Object object=metaStatementHandler.getValue("h");
            metaStatementHandler=SystemMetaObject.forObject(object);
        }
        String sql=(String)metaStatementHandler.getValue("delegate.boundSql.sql");
        Integer parameterObject =(Integer)metaStatementHandler.getValue("delegate.boundSql.parameterObject");
        log.info("Myplugin 運行!");
        Object obj=invocation.proceed();
        return obj;
    }

    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o,this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}

//mybatis-config.xml
<configuration>
	......
	<plugins>
        <plugin interceptor="plugin.MyPlugin">
            <property name="dbType" value="mysql"/>
        </plugin>
    </plugins>
    ......
</configuration>

(2)mybatis插件的實現原理:Mybatis使用JDK的動態代理,爲需要攔截的接口生成代理對象以實現接口方法攔截功能,每當執行這4種接口對象的方法時,就會執行代理對象執行invoke()方法。

SqlSession執行過程中的四大對象:

Excutor——執行器 :由它來調度StatementHandler、ParameterHandler、ResultHandler等來執行對應的SQL;SIMPLE(簡單執行器)、REUSE(執行重複預處理語句)、BATCH(批量專用執行器)。

public class Configuration {
	......
	public Executor newExecutor(Transaction transaction) {
        return this.newExecutor(transaction, this.defaultExecutorType);
    }

    public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
        executorType = executorType == null ? this.defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Object 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 (this.cacheEnabled) {
            executor = new CachingExecutor((Executor)executor);
        }

        Executor executor = (Executor)this.interceptorChain.pluginAll(executor);
        return executor;
    }
	......
}

StatementHandler——數據庫會話器 :使用數據庫的Statement(PrepareStatement)執行操作。

public class Configuration {
	......
	public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
	        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
	        StatementHandler statementHandler = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
	        return statementHandler;
	}
	......
}

ParameterHandler——參數處理器 :用來處理SQL參數

public class Configuration {
	......
	public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler = (ParameterHandler)this.interceptorChain.pluginAll(parameterHandler);
        return parameterHandler;
    }
	......
}

ResultHandler——結果處理器 :用來進行數據集(ResultSet)的封裝

public class Configuration {
	......
	public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
        ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
        ResultSetHandler resultSetHandler = (ResultSetHandler)this.interceptorChain.pluginAll(resultSetHandler);
        return resultSetHandler;
	}
	......
}

四大對象在Configuration對象創建方法裏Mybatis用責任鏈去封裝他們,換句話說,有機會在四大對象調度時插入我們的代碼去執行一些特殊的事件,這就是mybatis的插件技術。

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