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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值