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 会话可能会导致数据不一致的问题,就是将一个 XML 映射文件中定义的缓存对象放在全局对象中,对于同一个 Mapper 接口都是使用这个 Cache 对象,不管哪个 SqlSession 都是使用该 Cache 对象
执行数据库查询操作前,如果在二级缓存中有对应的缓存数据,则直接返回,没有的话则去一级缓存中获取,如果有对应的缓存数据,则直接返回,不会去访问数据库
默认全局开启,需要在每个 XML 映射文件中定义
缺陷:对于不同的 XML 映射文件,如果某个的 XML 映射文件修改了相应的数据,其他的 XML 映射文件获取到的缓存数据就可能不是最新的,也出现了脏读的问题,当然你可以所有的 XML 映射文件都通过 < cache-ref / > 来使用同一个 Cache 对象,不过这样太局限了,且缓存的数据仅仅是保存在了本地内存中,对于当前高并发的环境下是无法满足要求的,所以我们通常不使用MyBatis的缓存
所以对于 MyBatis 中的缓存,我认为是存在一定的缺陷,无法在正式的生产环境使用。
# {} 和 ${} 的区别是什么?
两者在 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 的初始化过程中,每个一个 XML 映射文件中的、、、标签,会被解析成一个 MappedStatement 对像,对应的 id 就是 XML 映射文件配置的 namespace+’.’+statementId,这个 id 跟 Mapper 接口中的方法进行关联,这里就引申了另外一个问题
同一个 Mapper 接口中为什么不能定义重载方法? 因为 Mapper 接口中的方法是通过 接口名称+’.’+方法名 去找到对应的
MappedStatement 对象,如果方法名相同,则对应的 MappedStatement 对象就是同一个,就存在问题了,所以同一个
Mapper 接口不能定义重载的方法
每个 Mapper 接口都会创建一个动态代理对象(JDK 动态代理),代理类会拦截接口的方法,找到对应的 MappedStatement 对象,然后执行数据库相关操作
执行逻辑如下:
其中 MapperProxy 为 Mapper 接口的动态代理对象的代理类
在Spring中Mapper接口是如何被注入的?
通过 SqlSession 的 getMapper(Class 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 接口会对应一个