介绍一下 MyBatis
MyBatis 是一款优秀的持久层框架。
- 支持自定义 SQL、存储过程以及高级映射
- 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
- 通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录
Mybaits 的优缺点
优点:
- 消除 JDBC 中的重复代码
- 可以在 XML 或注解中直接编写 SQL 语句,比较灵活,方便对 SQL 的优化与调整
- SQL 写在 XML 中,与代码解耦,按照对应关系方便管理
- XML 中提供了动态 SQL 的标签,方便根据条件拼接 SQL
- 提供了 XML、注解与 Java 对象的映射机制
- 与 Spring 集成比较方便
缺点:
- 字段较多、关联表多时,编写 SQL 工作量较大
- SQL 语句依赖了数据库特性,会导致程序的移植性较差,切换数据库困难
MyBatis主要执行流程
1. mybatis配置文件,包括Mybatis全局配置文件和Mybatis映射文件,
其中全局配置文件配置了数据源、事务等信息;映射文件配置了SQL执行相关的 信息。
2. mybatis通过读取配置文件信息(全局配置文件和映射文件),构造出SqlSessionFactory,即会话工厂。
3. 通过SqlSessionFactory,可以创建SqlSession即会话。Mybatis是通过SqlSession来操作数据库的。
4. SqlSession本身不能直接操作数据库,它是通过底层的Executor执行器接口来操作数据库的。Executor接口有两个实现类,一个是普通执行器,一个是缓存执行器(默认)。
5. Executor执行器要处理的SQL信息是封装到一个底层对象MappedStatement中。
该对象包括:SQL语句、输入参数映射信息、输出结果集映射信息。其中输入参数和输出结果的映射类型包括java的简单类型、HashMap集合对象、POJO对象类型。
更多细节详见:Mybatis之工作原理 MyBatis原理分析(通俗易懂)
MyBatis 与 Hibernate 的区别
- MyBatis 不完全是一个 ORM 框架,它需要程序员自己编写 SQL;Hibernate 可以做到无 SQL 对数据库进行操作
- MyBatis 直接编写原生 SQL,可以严格控制 SQL 执行性能,灵活度高,快速响应需求变化;Hibernate 会根据模型配置自动生成和执行 SQL 语句,面对多变的需求,灵活度没那么高
- MyBatis 书写 SQL 可能依赖数据库特性,导致应用程序数据库一致性差;Hibernate 可以屏蔽掉数据库差异,数据库一致性好
- MyBatis 考验程序编写 SQL 的功底,编写大量 SQL,效率可能不高;Hibernate 对象关系映射能力强,可以节省很多代码,提高开发效率
- MyBatis 没法根据模型自动初始化数据库中的表;Hibernate 是根据模型的配置生成 DDL 语句在数据库中自动初始化对应表、索引、序列等
#{} 和 ${} 的区别
- MyBatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ?,预编译 SQL,通过 PreparedStatement 的 setXxxx 的方法进行参数赋值。使用 #{} 可以有效地防止 SQL 注入。
- MyBatis 在处理 ${} 时,会直接把 ${} 替换为参数值,存在 SQL 注入的风险。
- #{} 比 ${} 安全,但还是提供了 ${} 这种动态替换参数的方式,是因为有些复杂的 SQL 使用场景通过预编译的方式比较麻烦,且在代码中完全可以做到控制非法参数,有些参数可能是一些常量或字段值。
MyBatis 中实体类的属性名与表中的字段名不一致怎么处理?
1、修改 SQL,给查询字段重命名,如 将 user_id 重命名为 userId
select user_id as userId from table
2、MyBatis 的 XML 映射文件中,使用 <resultMap> 标签,定义数据库字段名与实体 bean 的属性字段名的映射关系
<select id="getUser" parameterType="int" resultMap="”UserMap”">
select * from user where user_id=#{id}
</select>
<resultMap type=”User” id=”UserMap”>
<!–- id 标签映射主键字段 -–>
<id property=”id” column=user_id>
<!–- result 标签映射非主键字段,property 为实体 bean 属性名,column 为数据库表中字段名 -–>
<result property=“userName” column =”user_name”/>
</reslutMap>
MyBatis 中的本地缓存和二级缓存的作用是什么?怎么实现的?
本地缓存
作用:
SqlSession 级别的缓存,默认开启,在 MyBatis 配置文件中可以修改 MyBatis 文件中 <setting> 标签 localCacheScope 参数值改变缓存的作用域。statementId、boundSql.getSql() 执行 sql、查询参数、RowBounds 都相同,即认为是同一次查询,返回缓存值。
实现原理:
每个 SqlSession 对象包含一个 Executor 对象,Executor 对象中 localCache 属性使用 PerpetualCache 对象缓存查询数据;从源码中看 DefaultSqlSession 的 close、commit、rollback、insert、delete、update 相关的方法都会触发 BaseExecutor 对象清掉缓存。
二级缓存
作用:
MappedStatement 级别的缓存,默认不开启,可以在 Mapper xml 中通过 <cache> 标签开启 或者 MyBatis 文件中 <setting> 标签设置 cacheEnabled 参数为 true 全局开启 或者 mapper xml 配置文件中的 select 节点需要加上属性 useCache,在 SqlSession 关闭或提交之后才会生效。
开启二级缓存的默认作用摘自官网
-
映射语句文件中的所有 select 语句的结果将会被缓存。
-
映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
-
缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
-
缓存不会定时进行刷新(也就是说,没有刷新间隔)。
-
缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
-
缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
实现原理:
-
XMLMappedBuilder 解析 Mapper xml 中的 <cache>、<cache-ref> 标签
-
通过 builderAssistant 对象 addMappedStatement 方法,设置 cache 信息到 MappedStatement 对象内
-
CachingExecutor 对象的 query 方法先 MappedStatement 对象中 getCache() 获取缓存 Cache 对象,如果没有查到则到 BaseExecutor 中查询,走本地缓存逻辑