先腐一下
1 MyBatis 是啥
MyBatis 是一款优秀的基于 ORM(Object Relation Mapping) 的半自动轻量级持久层框架,它支持定制化 SQL,存储过程以及高级映射。MyBatis 底层封装了 JDBC,开发人员避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 xml 或注解来配置和映射原生类型,接口和 Java 的 pojo 为数据库中的记录。
不同于 Hibernate:Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。而 Mybatis 在查询关联对象或关联集合对象时,需要手动编写 sql 来完成,所以,称之为半自动 ORM 映射工具。
2 MyBatis 的优缺点
2.1 优势
MyBatis 是一个半自动的持久层框架。对开发人员而言,核心 sql 还是需要自己优化,sql 和 java 编码分开,功能边界清晰,一个专注业务,一个专注数据。
2.2 缺点
- SQL 语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写 SQL 语句的功底有一定要求;
- JDBC 方式可以用打断点的方式调试,但是 MyBatis 不能,需要通过 Log4j 日志信息帮助调试,然后在配置文件中修改。
3 适用场合
- 对性能的要求很高的项目,做 SQL 的性能优化相对简单、直接;
- 需求变化较多的项目,直接编写或修改 SQL。
4 配置文件相关问题
4.1 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?
我觉得要尽量避免这种情况
(1)第 1 种:通过在查询的 sql 语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
<select id=”selectOrder” parametertype=”int” resultetype=”com.order”>
select order_id id, order_no orderno ,order_price price form
orders where order_id=#{id};
</select>
<resultMap id="userMap" type="com.User">
<!–用 result 属性来映射非主键字段,property 为实体类属性名,column 为数据表中的属性–>
<result column="id" property="id"></result>
<result column="username" property="username"></result>
<collection property="orderList" ofType="com.Order">
<result column="oid" property="id"></result>
<result column="ordertime" property="ordertime"></result>
<result column="total" property="total"></result>
</collection>
</resultMap>
<select id="findAll" resultMap="userMap">
select *,o.id oid from user u left join orders o on u.id=o.uid
</select>
4.2 通常一个 xml 映射文件,都会写一个 Dao 接口与之对应,请问,这个 Dao 接口的工作原理是什么?Dao 接口里的方法,参数不同时,方法能重载吗?
Dao 接口即 Mapper 接口。接口的全限定名对应于映射文件中 namespace 的值,接口的方法名对应于映射文件中每条 sql 的 id 值;接口方法内的参数,就是传递给 sql 的参数值。
(1)传统开发方式
public interface UserDao {
List<User> findAll() throws IOException;
}
public class UserDaoImpl implements IUserDao {
public void delete() throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new
SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 命名空间.id
sqlSession.selectList("userDao.delete");
sqlSession.close();
return userList;
}
}
<mapper namespace="userDao">
<delete id="delete" parameterType="java.lang.Integer">
delete from user where id=#{id}
</delete>
</mapper>
(2)代理开发方式,将由 Mybatis 框架根据接口定义创建接口的动态代理对象
- mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
- Mapper 接口方法名和 mapper.xml 文件中的每个 statement 的 id 相同
- Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
- Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同
public interface IUserDao {
//查询所有用户
public List<User> findAll() throws IOException;
}
<mapper namespace="com.lagou.dao.IUserDao">
<!--namespace: 名称空间:与id组成sql的唯一标识
resultType:表明返回值类型-->
<!--查询用户-->
<select id="findAll" resultType="user">
<include refid="selectUser"></include>
</select>
</mapper>
@Test
public void test6() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// 获取动态代理对象
IUserDao mapper = sqlSession.getMapper(IUserDao.class);
List<User> all = mapper.findAll();
}
4.3 Mybatis 是如何将 sql 执行结果封装为目标对象并返回的? 都有哪些映射形式?
(1)使用<resultMap>标签,逐一定义数据库列名和对象属性名之间的映射关系。
<resultMap id="ApprovalInfoResult" type="ApprovalInfoBean">
<result property="id" column="id"/>
<result property="userId" column="user_id"/>
</resultMap>
<select id="getList" resultMap="ApprovalInfoResult" parameterType="java.util.Map">
</select>
(2)使用 sql 列的别名功能,将列的别名书写为对象属性名。
public class ApprovalBookOnlineInfoDTO {
/** 版权书籍ID */
private Long copyrightBookId;
}
<select id="getApprovalOnlineInfoDTOList" resultType="ApprovalBookOnlineInfoDTO" parameterType="java.util.Map">
SELECT
id AS copyrightBookId from a_approval;
</select>
4.4 自动生成主键
<insert id="insert" parameterType="ApprovalInfoBean">
<selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER">
SELECT LAST_INSERT_ID()
</selectKey>
insert into a_approval_info(id,user_id)
values(#{id},#{userId}
</insert>
4.5 Mybatis动态sql是做什么的?都有哪些动态sql?简述一下动态sql的执行原理?
(1)动态sql:进行sql操作时,动态的根据属性值来拼接数据库执行的sql语句,也就是根据传入的值不同,动态拼接出不同的可执行sql。包含判断为空、循环等;
(2)动态 sql 标签
- 标签 if - 用于判断传入的值是否符合某种条件;
- where - 用来动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1;
- foreach - 用于构建 in 条件,可在 sql 中对集合进行迭代。也常用到批量删除、添加等操作;
- choose when otherwise - 按顺序判断 when 中的条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满则时,则执行 otherwise 中的 sql。类似于 Java 的 switch 语句,choose 为 switch,when 为 case,otherwise 则为 default;
- include - 提取重复的sql,达到 sql 重用的目的。
(3)动态 sql 的执行原理
① 解析 Mapper 配置文件 mapper.xml,文件中的一个 select/update/delete/insert 节点对应于一个 MappedStatement 对象,然后存储在 Configuration 对象的 mappedStatements 属性中,mappedStatements 是一个 HashMap,存储时 key = 全限定类名+方法名,value = 对应的MappedStatement 对象;
② 获取用于和数据库交互的 SqlSession,传递的参数为 Configuration 对象和 Executor 执行器,然后根据传入的全限定名和方法名从 map 中获取 MappedStatement 对象;
③ 执行 executor.query(),根据传入的参数动态获得 SQL 语句,最后用 BoundSql 对象表示;
④ 创建 statementHanlder 对象执行查询,为 sql 中的占位符设值,最终返回 List 结果集。
4.6 Mybatis 的 Xml 映射文件中,不同的 Xml 映射文件,id 是否可以重复?
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复;
原因: namespace+id 是作为 Map<String, MapperStatement>的 key 使用的,如果没有 namespace,就剩下 id,那么,id 重复会导致数据互相覆盖。 有了 namespace,自然 id 就可以重复,namespace 不同,namespace + id 自然也就不同。
4.7 Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?
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()方法的调用。这就是延迟加载的基本原理。
4.8 Mybatis都有哪些Executor执行器?它们之间的区别是什么?
(1)SimpleExecutor:默认的普通执行器
(2)BatchExecutor:重用语句并执行批量更新
(3)ReuseExecutor:重用预处理语句 prepared statements
4.9 简述下Mybatis的一级、二级缓存(分别从存储结构、范围、失效场景。三个方面来作答)?
(1)一级缓存:一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,SqlSession 用一个 HashMap 来存储缓存数据。不同 SqlSession 之间的缓存数据区域(HashMap)是互相不影响的。一级缓存默认开启。
(2)二级缓存:二级缓存是 mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。二级缓存需要手动开启。二级缓存中的pojo需要实现 Serializable 接口,因为二级缓存数据存储介质多种多样,再去取pojo 时就需要反序列化了。
4.10 简述Mybatis的插件运行原理,以及如何编写一个插件?
(1)MyBatis 插件的运行原理 MyBatis 的插件实质上是拦截器,用于增强 MyBatis 的四大核心对象。增强功能本质上是借助底层的动态代理实现的。使用插件为四大对象创建出代理对象,拦截四大对象的每一个执行。interceptorChain保存了所有的拦截器(interceptors)。依次调用拦截器链中的拦截器依次对目标进行拦截或增加。
(2)如何编写一个插件 ① 创建一个插件类实现 interceptor 接口,使用注解定义要拦截哪个接口,接口里的哪个方法; ② 重写拦截方法,对原有方法进行增强 ③ 为自定义拦截器生成代理对象,放入拦截器链中 ④ 在sqlMapConfig.xml 配置自定义插件的全限定名