Java面试题精选:MyBatis(一)
一、MyBatis的工作原理
MyBatis是一个ORM(对象关系映射)框架,用于实现面向对象编程语言中不同类型系统的数据之间的转换。
MyBatis的源码结构
MyBatis的原理:
- MyBatis框架的初始化操作
- 处理SQL请求的流程
系统启动的时候会加载解析全局配置文件和对应映射文件。加载解析的相关信息存储在 Configuration 对象
@Test
public void test1() throws Exception{
// 1.获取配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
// 2.加载解析配置文件并获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
// 3.根据SqlSessionFactory对象获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 4.通过SqlSession中提供的 API方法来操作数据库
List<User> list = sqlSession.selectList("com.boge.mapper.UserMapper.selectUserList");
// 获取接口的代码对象 得到的其实是 通过JDBC代理模式获取的一个代理对象
// UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//List<User> list = mapper.selectUserList();
System.out.println("list.size() = " + list.size());
// 5.关闭会话
sqlSession.close(); // 关闭session 清空一级缓存
}
MyBatis的主要工作流程图
- SqlSessionFactoryBuiler:是用来构建SqlSessionFactory的,而SqlSessionFactory只需要一个,所以只要构建了这一个SqlSessionFactory,它的使命就完成了,也就没有存在的意义了。所以它的生命周期只存在于方法的局部。
- SqlSessionFactory:是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。因为我们一直有创建会话的需要,所以SqlSessionFactory应该存在于应用的整个生命周期中(作用域是应用作用域)。创建SqlSession只需要一个实例来做这件事就行了,否则会产生很多的混乱,和浪费资源。所以我们要采用单例模式。
- SqlSession:是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们在请求开始的时候创建一个SqlSession对象,在请求结束或者说方法执行完毕的时候要及时关闭它(一次请求或者操作中)。
二、介绍下MyBatis中的缓存设计
- 首先了解缓存的作用:
减低数据源的访问频率,从而提高数据源的处理能力,或提高服务器的响应速度。 - MyBatis中的缓存设计:
结构设计:装饰器模式
缓存级别:先二级缓存再一级缓存
为什么会先走二级缓存再走一级缓存?
二级缓存的作用域是SqlSessionFactory级别,90%找到
一级缓存是SqlSession级别,5%找到
三、聊下MyBatis中如何实现缓存的扩展
首先可以回答下MyBatis中的缓存机构
其次回答MyBatis缓存的扩展
- 创建Cache接口的实现。重写getObject和putObject方法
- 如何让自定义的缓存实现:在cache标签中通过type属性关联我们自定义的Cache接口的实现
import org.apache.ibatis.cache.Cache;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class CustomCache implements Cache {
private final String id;
private final ConcurrentMap<Object, Object> cache = new ConcurrentHashMap<>();
public CustomCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public int getSize() {
return cache.size();
}
// 其他必要的方法可以根据需要实现
}
要使用自定义缓存,需要在MyBatis的配置文件中注册缓存类型,并指定要使用的自定义缓存实现:
<cache type="com.example.CustomCache"/>
或者在Mapper文件中使用元素来为特定的Mapper启用缓存:
<cache type="com.example.CustomCache"/>
在Mapper接口或者语句中使用该缓存:
<select ... useCache="true">
<!-- 查询语句 -->
</select>
这样就通过实现Cache接口并在配置中指定,为MyBatis引入了自定义的缓存机制。
四、MyBatis中涉及到的设计模式
(1)建造者模式
- 在Mybatis环境的初始化过程中,
SqlSessionFactoryBuilder
会调用XMLConfigBuilder
读取所有的MybatisMapConfig.xml
和所有的*Mapper.xml
文件,构建Mybatis运行的核心对象Configuration
对象,然后将该Configuration
对象作为参数构建一个SqlSessionFactory
对象。
(2)工厂模式
- 在Mybatis中比如
SqlSessionFactory
使用的是工厂模式,该工厂没有那么复杂的逻辑,是一个简单工厂模式。
SqlSession
可以认为是一个Mybatis工作的核心的接口,通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的Connection
对象。
(3)单例模式
在Mybatis中有两个地方用到单例模式,ErrorContext
和 LogFactory
,其中 ErrorContext
是用在每个线程范围内的单例,用于记录该线程的执行环境错误信息,而 LogFactory
则是提供给整个Mybatis使用的日志工厂,用于获得针对项目配置好的日志对象。
public class ErrorContext {
private static final ThreadLocal<ErrorContext> LOCAL = new ThreadLocal<>();
private ErrorContext() {
}
public static ErrorContext instance() {
ErrorContext context = LOCAL.get();
if (context == null) {
context = new ErrorContext();
LOCAL.set(context);
}
return context;
}
}
(4)代理模式
- 代理模式可以认为是Mybatis的核心使用的模式,正是由于这个模式,我们只需要编写
Mapper.java
接口,不需要实现,由Mybatis后台帮我们完成具体SQL的执行。
(5)适配器模式
在Mybatsi的logging包中,有一个Log接口,该接口定义了Mybatis直接使用的日志方法,而Log接口具体由谁来实现呢?Mybatis提供了多种日志框架的实现,这些实现都匹配这个Log接口所定义的接口方法,最终实现了所有外部日志框架到Mybatis日志包的适配。
五、谈谈你对SqlSessionFactory的理解
SqlSessionFactory是MyBatis中非常核心的一个API。
目的是创建SqlSession对象。
SqlSessionFactory应该是单例。
SqlSessionFactory对象的创建是通过SqlSessionFactoryBuilder
来实现。
在SqlSessionFactoryBuilder即完成了SqlSessionFactory
对象的创建。也完成了全局配置文件和相关的映射文件的加载和解析操作。相关的加载解析的信息会被保存在Configuration
对象中。
而且涉及到了两种涉及模式:工厂模式(SqlSessionFactory),建造者模式(SqlSessionFactoryBuilder)
六、谈谈你对SqlSession的理解
SqlSession是MyBatis中非常核心的一个API:通过这个接口可以执行执行SQL语句、获取Mappers、管理事务。类似于连接MySQL的 Connection对象。
SqlSession对象的获取需要通过SqlSessionFactory
来实现。是一个会话级别的。当一个新的会话到来的时候。我们需要新建一个SqlSession对象来处理。当一个会话结束后我们需要关闭相关的会话资源。
处理请求的方式:
- 通过相关的增删改查的API直接处理
- 可以通过getMapper(xxx.class) 来获取相关的mapper接口的代理对象来处理
七、谈谈你对MyBatis的理解
MyBatis应该是我们在工作中使用频率最高的一个ORM框架。持久层框架
- 提供非常方便的API来实现增删改查操作
- 支持灵活的缓存处理方案,一级缓存、二级缓存,三级缓存
- 还支持相关的延迟数据加载的处理
- 还提供了非常多的灵活标签来实现复杂的业务处理,if forech where trim set bind …
- 相比于Hibernate会更加的灵活
八、谈谈MyBatis中的分页原理
SQL语句中的分页查询:
(1)MySQL:limit
(2)Oracle:rowid
在MyBatis中实现分页有两种实现
- 逻辑分页:RowBounds
○ 原理:执行完整的SQL查询,将结果集全部加载到内存中,然后根据RowBounds指定的偏移量offset
和限制数limit
进行分页处理。
○ 优点:减少IO次数,对于频繁访问且数据量较小的情况较为适合。
○ 缺点:当数据量非常大时,容易造成内存溢出,性能下降。
int offset = 10; // 偏移量
int limit = 5; // 每页数据条数
RowBounds rowBounds = new RowBounds(offset, limit);
List<User> userList = sqlSession.selectList("getUsers", null, rowBounds);
- 逻辑分页:基于插件的分页(
PageHelper
)
○ 原理:MyBatis提供了插件机制,通过自定义插件来拦截SQL语句的执行,并在查询结果返回之前添加分页逻辑。
○ 优点:插件封装了分页的具体实现细节,使用起来简单方便,适用于多种分页需求。
○ 缺点:可能需要一定的开发成本来编写和维护插件。
// Java代码中使用 PageHelper
PageHelper.startPage(1, 10);
List<User> userList = userMapper.getUsers();
PageInfo<User> pageInfo = new PageInfo<>(userList);
- 基于MyBatis-Plus实现分页
MyBatis-Plus提供了分页插件,可实现简单易用的分页功能,可以根据传入的分页参数自动计算出分页信息,无需手动编写分页SQL语句。
public interface UserMapper extends BaseMapper<User> {
List<User> selectUserPage(@Param("page") Page<User> page, @Param("name") String name);
}
九、Spring中是如何解决DefaultSqlSession的数据安全问题的
DefaultSqlSession
是线程非安全的。也就意味着我们不能够把DefaultSqlSession声明在成员变量中。
在Spring中提供了一个SqlSessionTemplate
来实现SqlSession
的相关的定义。
然后在SqlSessionTemplate中的每个方法都通过SqlSessionProxy
来操作。这个是一个动态代理对象。然后在动态代理对象中通过方法级别的DefaultSqlSession
来实现相关的数据库的操作
十、谈谈你对MyBatis中的延迟加载的理解
延迟加载:等一会加载。
在多表关联查询操作的时候可以使用到的一种方案。
如果是单表操作就完全没有延迟加载的概念。
MyBatis的懒加载是指在需要数据时才进行数据加载,而不是在加载实体对象时就立即加载其关联的所有数据。这样可以提高数据加载的效率,因为只有当确实需要时才会进行数据加载。
- 需要开启延迟加载
- 需要配置多表关联
- association 一对一的关联配置
- collection 一对多的关联配置
在MyBatis中,可以通过配置或标签的lazy属性来控制是否懒加载。当lazy属性设置为true时,表示启用懒加载。
<mapper namespace="com.example.mapper.UserMapper">
<!-- 配置懒加载的关联查询 -->
<collection property="orders" ofType="com.example.entity.Order"
column="user_id" select="selectOrdersByUserId" lazy="true"/>
</mapper>
- 需要全局配置
全局懒加载设置:在mybatis-config.xml配置文件中,可以通过标签的lazyLoadingEnabled属性全局设置懒加载。
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
延迟加载的原理:代理对象