- MyBatis的实现逻辑
- MyBatis的缓存实现逻辑
- #{} 和 ${} 的区别是什么?
- MyBatis中自定义标签的执行原理
- 简述Mapper接口的工作原理
- 在Spring中Mapper接口是如何被注入的?
- 在Mapper接口中是否可以有重载方法?
- 当实体类中的属性名和表中的字段名不一样 ,怎么办?
- 如何获取自动生成的键值?
- Mybatis有哪些Executor执行器?
- MyBatis的延迟加载原理
- MyBatis的插件运行原理
- Mybatis是如何进行分页的?
- Mybatis如何处理include标签的?
- MyBatis与Hibernate有哪些不同?
- JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
- Mybatis比IBatis比较大的几个改进是什么?
MyBatis的实现逻辑
- 在 MyBatis 的初始化过程中,会生成一个 Configuration 全局配置对象,里面包含了所有初始化过程中生成对象
- 根据 Configuration 创建一个 SqlSessionFactory 对象,用于创建 SqlSession "会话"
- 通过 SqlSession 可以获取到 Mapper 接口对应的动态代理对象,去执行数据库的相关操作
- 动态代理对象执行数据库的操作,由 SqlSession 执行相应的方法,在他的内部调用 Executor 执行器去执行数据库的相关操作
- 在 Executor 执行器中,会进行相应的处理,将数据库执行结果返回
MyBatis的缓存实现逻辑
MyBatis 提供了一级缓存和二级缓存
在 MyBatis 的开启一个 SqlSession 会话时,都会创建一个 Executor 执行器对象
一级缓存
在 Executor 执行器(SimpleExecutor)中有一个 Cache 对象中,默认就是一个 HashMap 存储缓存数据,执行数据库查询操作前,如果在一级缓存中有对应的缓存数据,则直接返回,不会去访问数据库
默认的缓存区域为SESSION,表示开启一级缓存,可以设置为STATEMENT,执行完查询后会清空一级缓存,所有的数据库更新操作也会清空一级缓存
缺陷:在多个 SqlSession 会话时,可能导致数据的不一致性,某一个 SqlSession 更新了数据而其他 SqlSession 无法获取到更新后的数据,出现数据不一致性,这种情况是不允许出现了,所以我们通常选择"关闭"一级缓存

二级缓存
在 Executor 执行器(CachingExecutor)中有一个 TransactionalCacheManager 对象中,可以在一定程度上解决的一级缓存中多个 SqlSession 会话可能会导致数据不一致的问题,就是将一个
执行数据库查询操作前,如果在二级缓存中有对应的缓存数据,则直接返回,没有的话则去一级缓存中获取,如果有对应的缓存数据,则直接返回,不会去访问数据库
默认全局开启,需要在每个
缺陷:对于不同的 <cache-ref />来使用同一个 Cache 对象,不过这样太局限了,且缓存的数据仅仅是保存在了本地内存中,对于当前高并发的环境下是无法满足要求的,所以我们通常不使用MyBatis的缓存

所以对于 MyBatis 中的缓存,我认为是存在一定的缺陷,无法在正式的生产环境使用,如果需要使用缓存,可以参考我的另一篇文档《JetCache源码分析》
#{} 和 ${} 的区别是什么?
两者在 MyBatis 中都可以作为 SQL 的参数占位符,在处理方式上不同
#{}:在解析 SQL 的时候会将其替换成?占位符,然后通过 JDBC 的 PreparedStatement 对象添加参数值,这里会进行预编译处理,可以有效的防止 SQL 注入,提高系统的安全性${}:在 MyBatis 中带有该占位符的 SQL 片段会被解析成动态 SQL 语句,根据入参直接替换掉这个值,然后执行数据库相关操作,存在 SQL注入 的安全性问题
MyBatis中自定义标签的执行原理
MyBatis 提供了以下几种动态 SQL 的标签:<if />、<choose />、<when />、<otherwise />、<trim />、<where />、<set />、<foreach />、<bind />
在 MyBatis 的初始化过程中的解析 SQL 过程中,会将定义的一个 SQL 解析成一个个的 SqlNode 对象,当需要执行数据库查询前,需要根据入参对这些 SqlNode 对象进行解析,使用OGNL表达式计算出结果,然后根据结果拼接对应的 SQL 片段,以此完成动态 SQL 的功能
如何使用可以参考MyBatis官方文档
简述Mapper接口的工作原理
在 MyBatis 的初始化过程中,每个一个 <select />、<insert />、<update />、<delete />标签,会被解析成一个 MappedStatement 对像,对应的 id 就是 namespace+'.'+statementId,这个 id 跟 Mapper 接口中的方法进行关联,这里就引申了另外一个问题
同一个 Mapper 接口中为什么不能定义重载方法?
因为 Mapper 接口中的方法是通过
接口名称+'.'+方法名去找到对应的 MappedStatement 对象,如果方法名相同,则对应的 MappedStatement 对象就是同一个,就存在问题了,所以同一个 Mapper 接口不能定义重载的方法
每个 Mapper 接口都会创建一个动态代理对象(JDK 动态代理),代理类会拦截接口的方法,找到对应的 MappedStatement 对象,然后执行数据库相关操作
执行逻辑如下:

其中 MapperProxy 为 Mapper 接口的动态代理对象的代理类
在Spring中Mapper接口是如何被注入的?
通过 SqlSession 的 getMapper(Class<T> type) 方法,可以获取到 Mapper 接口的动态代理对象,那么在 Spring 中是如何将 Mapper 接口注入到其他 Spring Bean 中的呢?
在 MyBatis 的 MyBatis-Spring 集成 Spring 子项目中,通过实现 Spring 的 BeanDefinitionRegistryPostProcessor 接口,实现它的 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) 方法,也就是在 Spring 完成 BeanDefinition 的初始化工作后,会将 Mapper 接口也解析成 BeanDefinition 对象注册到 registry 注册表中,并且会修改其 beanClass 为 MapperFactoryBean 类型,还添加了一个入参为 Mapper 接口的 Class 对象的名称
这样 Mapper 接口会对应一个 MapperFactoryBean 对象,由于这个对象实现了 FactoryBean 接口,实现它的 getObject() 方法,该方法会通过 SqlSession 的 getMapper(Class<T> type) 方法,返回该 Mapper 接口的动态代理对象,所以在 Spring Bean 中注入的 Mapper 接口时,调用其 getObeject() 方法,拿到的是 Mapper 接口的动态代理对象
在Mapper接口中是否可以有重载方法?
不可以,参考Mapper接口的工作原理这个问题
当实体类中的属性名和表中的字段名不一样 ,怎么办?
通过
AS关键字为字段名设置一个别名与属性名对应通过
<resultMap>来映射字段名和实体类属性名的一一对应的关系,通过配置 property 和 column 属性,如下:<resultMap id="StudentResult" type="tk.mybatis.simple.Student"> <id column="id" javaType="INTEGER" property="id" /> <result column="student_name" jdbcType="VARCHAR" property="name" /> <result column="student_age" jdbcType="INTEGER" property="age" /></resultMap>是第一种的特殊情况。大多数场景下,数据库字段名和实体类中的属性名差,主要是前者为下划线风格,后者为驼峰风格。在这种情况下,可以开启 MyBatis 的
mapUnderscoreToCamelCase配置,实现自动的下划线转驼峰的功能,如下:<!-- mybatis-config.
如何获取自动生成的键值?
不同的数据库,获取自动生成的(主)键值的方式是不同的
MySQL 有两种方式获取自动生成的键值,如下:
在
<insert />标签中添加useGeneratedKeys="true"等属性<insert id="insert" useGeneratedKeys="true" keyProperty="id" keyColumn="id" parameterType="person" > INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})</insert>在
<insert />标签内添加<selectKey />标签<insert id="insert" parameterType="person"> <selectKey keyProperty="id" keyColumn="id" resultType="long" order="AFTER"> SELECT LAST_INSERT_ID() </selectKey> INSERT INTO person(name, pswd) VALUE (#{name}, #{pswd})</insert>
Oracle 也有两种方式,序列和触发器,基于序列,根据 <selectKey /> 执行的时机,也有两种方式,如下:
// 这个是创建表的自增序列CREATE SEQUENCE student_sequenceINCREMENT BY 1NOMAXVALUENOCYCLECACHE 10;// 方式一,使用 `<selectKey />` 标签 + BEFORE<insert id="add" parameterType="Student"> <selectKey keyProperty="student_id" resultType="int" order="BEFORE"> select student_sequence.nextval FROM dual </selectKey> INSERT INTO student(student_id, student_name, student_age) VALUES (#{student_id},#{student_name},#{student_age})</insert>// 方式二,使用 `<selectKey />` 标签 + AFTER<insert id="save" parameterType="com.threeti.to.ZoneTO" > <selectKey resultType="java.lang.Long" keyProperty="id" order="AFTER" > SELECT SEQ_ZONE.CURRVAL AS id FROM dual </selectKey> INSERT INTO TBL_ZONE (ID, NAME ) VALUES (SEQ_ZONE.NEXTVAL, #{name,jdbcType=VARCHAR})</insert>
Mybatis有哪些Executor执行器?
- SimpleExecutor(默认):每执行一次数据库的操作,就创建一个 Statement 对象,用完立刻关闭 Statement 对象。
- ReuseExecutor:执行数据库的操作,以 SQL 作为 key 查找缓存的 Statement 对象,存在就使用,不存在就创建;用完后,不关闭 Statement 对象,而是放置于缓存
Map<String, Statement>内,供下一次使用,就是重复使用 Statement 对象 - BatchExecutor:执行数据库更新操作(没有查询操作,因为 JDBC 批处理不支持查询操作),将所有 SQL 都添加到批处理中(通过 addBatch 方法),等待统一执行(使用 executeBatch 方法)。它缓存了多个 Statement 对象,每个 Statement 对象都是调用 addBatch 方法完毕后,等待一次执行 executeBatch 批处理。实际上,整个过程与 JDBC 批处理是相同。
- CachingExecutor:在上述的三个执行器之上,增加二级缓存的功能
MyBatis的延迟加载原理
MyBatis 获取到查询结果需要进行结果集映射,也就是将 JDBC 返回的结果集转换成我们需要的 Java 对象
在映射的过程中,如果存在嵌套子查询且需要延迟加载,则会为该返回结果的实例对象创建一个动态代理对象(Javassist),也就是说我们拿到的返回结果实际上是一个动态代理对象
这个动态代理对象中包含一个 ResultLoaderMap 对象,保存了需要延迟加载的属性和嵌套子查询的映射关系
当你调用了需要延迟加载的属性的 getter 方法时,会执行嵌套子查询,将结果设置到该对象中,然后将该延迟加载的属性从 ResultLoaderMap 中移除
如果你调用了该对象
equals、clone、hashCode、toString某个方法(默认),会触发所有的延迟加载,然后全部移除如果你调用了延迟加载的属性的 setter 方法,也会将将
延迟加载的属性从 ResultLoaderMap 中移除
MyBatis的插件运行原理
MyBatis 的插件机制仅针对 ParameterHandler、ResultSetHandler、StatementHandler、Executor 这 4 种接口中的方法进行增强处理
Mybatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 #invoke(...)方法。当然,只会拦截那些你指定需要拦截的方法
为什么仅支持这 4 种接口呢?
因为 MyBatis 仅在创建上述接口对象后,将所有的插件应用在该对象上面,也就是为该对象创建一个动态代理对象,然后返回
如何编写一个 MyBatis 插件?
让插件类实现 org.apache.ibatis.plugin.Interceptor 接口,还需要通过注解标注该插件的拦截点,然后在 MyBatis 的配置文件中添加的插件,例如:
@Intercepts({ @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} )})public class ExamplePlugin implements Interceptor { // Executor的查询方法: // public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = invocation.getArgs(); RowBounds rowBounds = (RowBounds) args[2]; if (rowBounds == RowBounds.DEFAULT) { // 无需分页 return invocation.proceed(); } /* * 将query方法的 RowBounds 入参设置为空对象 * 也就是关闭 MyBatis 内部实现的分页(逻辑分页,在拿到查询结果后再进行分页的,而不是物理分页) */ args[2] = RowBounds.DEFAULT; MappedStatement mappedStatement = (MappedStatement) args[0]; BoundSql boundSql = mappedStatement.getBoundSql(args[1]); // 获取 SQL 语句,拼接 limit 语句 String sql = boundSql.getSql(); String limit = String.format("LIMIT %d,%d", rowBounds.getOffset(), rowBounds.getLimit()); sql = sql + " " + limit; // 创建一个 StaticSqlSource 对象 SqlSource sqlSource = new StaticSqlSource(mappedStatement.getConfiguration(), sql, boundSql.getParameterMappings()); // 通过反射获取并设置 MappedStatement 的 sqlSource 字段 Field field = MappedStatement.class.getDeclaredField("sqlSource"); field.setAccessible(true); field.set(mappedStatement, sqlSource); // 执行被拦截方法 return invocation.proceed(); } @Override public Object plugin(Object target) { // default impl return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // default nop }}Mybatis是如何进行分页的?
Mybatis 使用 RowBounds 对象进行分页,它是针对 ResultSet 结果集执行的内存分页,而非数据库分页
所以,实际场景下,不适合直接使用 MyBatis 原有的 RowBounds 对象进行分页。而是使用如下两种方案:
- 在 SQL 内直接书写带有数据库分页的参数来完成数据库分页功能
- 也可以使用分页插件来完成数据库分页。
这两者都是基于数据库分页,差别在于前者是工程师手动编写分页条件,后者是插件自动添加分页条件。
分页插件的基本原理是使用 Mybatis 提供的插件接口,实现自定义分页插件。在插件的拦截方法内,拦截待执行的 SQL ,然后重写 SQL ,根据 dialect 方言,添加对应的物理分页语句和物理分页参数。
举例:SELECT * FROM student ,拦截 SQL 后重写为:select * FROM student LIMI 0,10
目前市面上目前使用比较广泛的 MyBatis 分页插件有:
- Mybatis-PageHelper
- MyBatis-Plus
Mybatis如何处理include标签的?
例如:如果 A 标签通过 include 引用了 B 标签的内容,请问,B 标签能否定义在 A 标签的后面,还是说必须定义在A标签的前面
虽然 Mybatis 解析 按照顺序解析的。但是,被引用的 B 标签依然可以定义在同一个 也就是说,无需按照顺序,进行定义。
原理是,Mybatis 解析 A 标签,发现 A 标签引用了 B 标签,但是 B 标签尚未解析到,尚不存在,此时,Mybatis 会将 A 标签标记为未解析状态。然后,继续解析余下的标签,包含 B 标签,待所有标签解析完毕,Mybatis 会重新解析那些被标记为未解析的标签,此时再解析A标签时,B 标签已经存在,A 标签也就可以正常解析完成了。
MyBatis与Hibernate有哪些不同?
Mybatis 和 Hibernate 不同,它不完全是一个 ORM 框架,因为MyBatis 需要程序员自己编写 SQL 语句。不过 MyBatis 可以通过
Mybatis 学习门槛低,简单易学,程序员直接编写原生态 SQL ,可严格控制 SQL 执行性能,灵活度高。但是灵活的前提是 MyBatis 无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套 SQL 映射文件,工作量大。
Hibernate 对象/关系映射能力强,数据库无关性好。如果用 Hibernate 开发可以节省很多代码,提高效率。但是 Hibernate 的缺点是学习门槛高,要精通门槛更高,而且怎么设计 O/R 映射,在性能和对象模型之间如何权衡,以及怎样用好 Hibernate 需要具有很强的经验和能力才行。
总之,按照用户的需求在有限的资源环境下只要能做出维护性、扩展性良好的软件架构都是好架构,所以框架只有适合才是最好。简单总结如下:
- Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取。
- Mybatis 属于半自动 ORM 映射工具,在查询关联对象或关联集合对象时,需要手动编写 SQL 来完成。
当然,实际上,MyBatis 也可以搭配自动生成代码的工具,提升开发效率,还可以使用 MyBatis-Plus 框架,已经内置常用的 SQL 操作,也是非常不错的。
JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?
问题一:SQL 语句写在代码中造成代码不易维护,且代码会比较混乱。
解决方式:将 SQL 语句配置在 Mapper
问题二:根据参数不同,拼接不同的 SQL 语句非常麻烦。例如 SQL 语句的 WHERE 条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决方式:MyBatis 提供 <where />、<if /> 等等动态语句所需要的标签,并支持 OGNL 表达式,简化了动态 SQL 拼接的代码,提升了开发效率。
问题三,对结果集解析麻烦,SQL 变化可能导致解析代码变化,且解析前需要遍历。
解决方式:Mybatis 自动将 SQL 执行结果映射成 Java 对象。
Mybatis比IBatis比较大的几个改进是什么?
这是一个选择性了解的问题,因为可能现在很多面试官,都没用过 IBatis 框架
- 有接口绑定,包括注解绑定 SQL 和
- 动态 SQL 由原来的节点配置变成 OGNL 表达式。
- 在一对一或一对多的时候,引进了
<association />标签,在一对多的时候,引入了<collection />标签,不过都是在<resultMap />里面配置
原文转载:http://www.shaoqun.com/a/492716.html
c-tick认证:https://www.ikjzd.com/w/2074
square:https://www.ikjzd.com/w/2106
跨境通网站:https://www.ikjzd.com/w/1329
目录MyBatis的实现逻辑MyBatis的缓存实现逻辑#{}和${}的区别是什么?MyBatis中自定义标签的执行原理简述Mapper接口的工作原理在Spring中Mapper接口是如何被注入的?在Mapper接口中是否可以有重载方法?当实体类中的属性名和表中的字段名不一样,怎么办?如何获取自动生成的键值?Mybatis有哪些Executor执行器?MyBatis的延迟加载原理MyBatis的插
急速:急速
imgur:imgur
德国黑森林 在温泉中尽情遗忘 - :德国黑森林 在温泉中尽情遗忘 -
5月份情侣适合去哪里玩 五月适合两人度假的最佳去处:5月份情侣适合去哪里玩 五月适合两人度假的最佳去处
【捷克旅游签证办理流程】--捷克旅游签证怎么办:【捷克旅游签证办理流程】--捷克旅游签证怎么办
No comments:
Post a Comment