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