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的插件技術。