1.单独使用MyBatis
在了解Spring 继承Mybatis 之前,先来看下如何单独使用MyBatis
数据表结构1
2
3
4
5
6
7
8
9
10CREATE TABLE message (
id bigint unsigned NOT NULL AUTO_INCREMENT,
message_key VARCHAR(255) COMMENT '消息唯一键,用于做回查的标识',
message text COMMENT '消息内容',
message_status INT DEFAULT 1 COMMENT '消息状态 1-prepare 2-commit 3-rollback 4-unknown',
create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE INDEX idx_messageKey(message_key)
) ENGINE=InnoDB;
mybatis-config.xml
1 |
|
mappers/MessageMapper.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<mapper namespace="com.example.codingInAction.mapper.MessageMapper">
<!-- 插入操作 插入数据后获取自动生成的主键 -->
<insert id="insertMessage" parameterType="com.example.codingInAction.model.Message"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO message (
message,
message_key,
message_status
) VALUES (
#{message},
#{messageKey},
#{messageStatus}
)
</insert>
<select id="findByMessageKey" resultType="com.example.codingInAction.model.Message">
SELECT
id,
message AS message,
message_key AS "messageKey",
message_status AS "messageStatus",
create_time AS "createTime",
update_time AS "updateTime"
FROM
message
WHERE
message_key = #{messageKey}
</select>
</mapper>
1 | public class Message { |
1.1 SqlSessionFactory
SqlSessionFactory
是MyBatis的核心, 其作用如下
- 创建
SqlSession
对象:SqlSessionFactory
通过其方法创建SqlSession
实例。SqlSession
用于执行数据库操作,如查询、插入、更新和删除等。每次需要进行数据库操作时,都会从SqlSessionFactory
中获取一个新的SqlSession
对象。 - 管理
SqlSession
生命周期:SqlSessionFactory
不仅负责创建SqlSession
,还负责管理其生命周期,包括资源的分配和释放。SqlSession
对象需要在操作完成后关闭,以释放数据库连接等资源。 - 加载和解析配置:
SqlSessionFactory
在初始化时会加载和解析MyBatis的配置文件(如mybatis-config.xml
)和Mapper XML文件。这些配置文件定义了数据库连接、事务管理、Mapper映射等信息。 - 缓存机制:
SqlSessionFactory
配置中可以包含缓存设置,如一级缓存和二级缓存。缓存机制可以提高数据库访问的性能,减少重复查询的开销。
1.2 SqlSession
1 | SqlSession sqlSession = sqlSessionFactory.openSession(); |
SqlSession
是 MyBatis 与数据库交互的核心。
通过 SqlSession
,开发者可以执行 SQL 语句、获取映射器(Mapper)、管理事务等操作。可以将 SqlSession
看作是 MyBatis 的会话,类似于 JDBC 中的 Connection
对象。
1.3 mapper 动态代理
1 | MessageMapper messageMapper = sqlSession.getMapper(MessageMapper.class) |
以下代码获取到的是MessageMapper
的动态代理对象
1 | public class DefaultSqlSession implements SqlSession { |
Configuration
对象对应的就是mybatis-config.xml
这个配置文件,层层深入源码最后发现是熟悉的JDK 动态代理Proxy.newProxyInstance
, 说明最后生成的mapper 对象是一个JDK 动态代理对象。
1.4 SQL执行
再看MapperProxyFactory.newInstance
源码,
1 | public class MapperProxyFactory<T> { |
Proxy.newProxyInstance
中参数InvocationHandler
传入的是MapperProxy
, 看一下MapperProxy
实现InvocationHandler
的具体逻辑
1 | public class MapperProxy<T> implements InvocationHandler, Serializable { |
2. Spring集成MyBatis
在MyBatis与Spring集成时,可以将MyBatis的配置直接放在Spring的配置文件(如applicationContext.xml
)中,从而简化配置管理。通过这种方式,可以在一个配置文件中集中管理数据源、事务管理、MyBatis的SqlSessionFactory
和Mapper扫描等配置。
applicationContext.xml
1 |
|
Spring整合MyBatis的优势主要在于使用上,我们来看看Spring中使用MyBatis的用法。 测试中我们看到,在Spring中使用MyBatis非常方便,用户甚至无法察觉自己正在使用MyBatis,而这一切相对于独立使用MyBatis时必须要做的各种冗余操作来说无非是大大简化了我们的工作量。
就是从Spring 容器中获取想要的mapper bean, 然后就可以执行对应的SQL 操作了1
2
3
4
5
6
7
8
9
10
11
12class MessageMapperTest {
void messageTest2() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MessageMapper messageMapper = (MessageMapper) context.getBean("messageMapper");
Map<String, Object> params = new HashMap<>();
params.put("messageKey", "test key");
Message result = messageMapper.findByMessageKey(params);
System.out.println(result.getMessage());
}
}
2.1 SqlSessionFactoryBean
从配置文件中可以看到, sqlSessionFactory
是通过SqlSessionFactoryBean
配置的。
通过Spring配置SqlSessionFactoryBean
,可以将SqlSessionFactory
作为Spring的一个Bean进行管理。
1 | public class SqlSessionFactoryBean |
SqlSessionFactoryBean
实现了InitializingBean
和FactoryBean
两个接口
2.1.1 InitializingBean
实现此接口的bean会在初始化时调用重写的InitializingBean.afterPropertiesSet
方法来进行bean的逻辑初始化
获取SqlSessionFactoryBean实例,跳过getBean 的其他代码直接来到init 阶段, InitionlizingBean
相关的逻辑
2.1.2 FactoryBean
在SqlSessionFactoryBean.getObject()
中就是把init 阶段实例化的SqlSessonFactory
实例返回
2.2 MapperFactoryBean
1 | MessageMapper messageMapper = (MessageMapper) context.getBean("messageMapper"); |
从配置文件中可以看到,messageMapper对应的MessageFactoryBean
也是一个FactoryBean
, 重点看下getObject
方法
1 | public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { |
Spring中获取的名为messageMapper的bean,其实是与单独使用MyBatis完成了一样的功能,从源码上看,在bean的创建过程也是使用了MyBatis中的原生方法sqlSession.getMapper(MessageMapper.class)
进行了再一次封装。
和不集成直接使用MyBatis 获取对应mapper实例其实本质上是一样的逻辑,从getMapper
进去 是完全一样的JDK 动态代理逻辑1
MessageMapper messageMapper = sqlSession.getMapper(MessageMapper.class);
2.2 MapperScannerConfigurer
在Spring项目中,通过MapperScannerConfigurer
类自动扫描所有的Mapper接口,并通过Spring的依赖注入机制直接使用这些接口,是一种简化并推荐的方式。这种方法不需要开发者手动实现Mapper接口的具体方法,也不需要手动管理SqlSession
,从而大大简化了代码和配置。
1 | <!-- MyBatis MapperScannerConfigurer配置 --> |
3. Spring Boot集成MyBatis
在Spring Boot项目中集成MyBatis通常使用mybatis-spring-boot-starter
,这个由MyBatis 提供的Starter简化了配置和集成过程。使用Spring Boot集成MyBatis时,你不需要显式地配置SqlSessionFactoryBean
,通过SpringBoot 的自动配置机制, 这些配置均可以自动完成。你只需要
- 添加依赖
- 在application.properties 中配置DataSource 即可
1 | <!-- MyBatis Spring Boot Starter --> |
关于SpringBoot 自动配置机制的实现原理,可以点击Spring boot 自动配置实现原理阅读了解。 下面将忽略MybatisAutoConfiguration配置的加载和BeanDefinition 注册流程,直接进入实例化过程进行分析
3.1 MybatisAutoConfiguration的实例化
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
在Spring boot 自动配置实现原理一文中,已经详细介绍过自动配置类的加载与BeanDefinition 注册过程, 在其中9.5 小节中讲了在ConfigurationClassPostProcessor
中重写的BeanFactoryPostProcessor.postProcessBeanFactory
中,实现了对@Configuration
注解动态代理类的生成。 这里讲以MybatisAutoConfiguration 的实例化为例详细讲解这一过程
来看下拦截逻辑。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49class ConfigurationClassEnhancer {
private static final Callback[] CALLBACKS = new Callback[] {
new BeanMethodInterceptor(),
new BeanFactoryAwareMethodInterceptor(),
NoOp.INSTANCE
};
private static class BeanMethodInterceptor implements MethodInterceptor, ConditionalCallback {
public Object intercept(Object enhancedConfigInstance, Method beanMethod, Object[] beanMethodArgs,
MethodProxy cglibMethodProxy) throws Throwable {
ConfigurableBeanFactory beanFactory = getBeanFactory(enhancedConfigInstance);
String beanName = BeanAnnotationHelper.determineBeanNameFor(beanMethod);
// Determine whether this bean is a scoped-proxy
if (BeanAnnotationHelper.isScopedProxy(beanMethod)) {
String scopedBeanName = ScopedProxyCreator.getTargetBeanName(beanName);
if (beanFactory.isCurrentlyInCreation(scopedBeanName)) {
beanName = scopedBeanName;
}
}
if (factoryContainsBean(beanFactory, BeanFactory.FACTORY_BEAN_PREFIX + beanName) &&
factoryContainsBean(beanFactory, beanName)) {
Object factoryBean = beanFactory.getBean(BeanFactory.FACTORY_BEAN_PREFIX + beanName);
if (factoryBean instanceof ScopedProxyFactoryBean) {
// Scoped proxy factory beans are a special case and should not be further proxied
}
else {
// It is a candidate FactoryBean - go ahead with enhancement
return enhanceFactoryBean(factoryBean, beanMethod.getReturnType(), beanFactory, beanName);
}
}
if (isCurrentlyInvokedFactoryMethod(beanMethod)) {
if (logger.isInfoEnabled() &&
BeanFactoryPostProcessor.class.isAssignableFrom(beanMethod.getReturnType())) {
}
return cglibMethodProxy.invokeSuper(enhancedConfigInstance, beanMethodArgs);
}
return resolveBeanReference(beanMethod, beanMethodArgs, beanFactory, beanName);
}
}
最后生成的代理类重新设置给了BeanDefinition 的一个属性。
3.2 sqlSessionFactory 实例化
3.2.1 instantiateUsingFactoryMethod
在 Spring 中,有几种常见的 Bean 实例化方式:
- 使用构造函数实例化。
- 使用Supplier实例化。
- 使用工厂方法实例化。
针对sqlSessionFactory ,其实例化过程使用的是 instantiateUsingFactoryMethod
工厂方法实例化
最后解析出, 使用instantiateUsingFactoryMethod
实例化sqlSessionFactory 时, 用到的factoryBean 是 MybatisAutoConfiguration
的动态代理对象,用的方法是sqlSessionFactory
3.2.2 动态代理拦截
这里来到经典的反射逻辑来调用实例对象上的方法,此时factoryBean是CGLIB 动态代理对象, 所以在调用方法时会先走在拦截逻辑上