目录
1、前言
spring整合mybatis几乎是每个Java开发学习过程中都会接触到的,相信很多人开始学习的时候都和博主一样,跟着视频或者博客把一大堆配置复制粘贴下来,然后运行发现没问题,于是就觉得自己掌握了。
但实际上真要细究spring和mybatis之间的关系时,大多数人都说不出来个所以然。
今天我们就从源码的角度来了解一下,从mybatis到mybatis-spring。
2、mybatis
作为一个半ORM框架,mybatis的流行度已经远超hibernate,毕竟hibernate的HQL学起来真是太伤神了。我们先从一个简单的例子来使用一下mybatis:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="app.properties"/>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springtrans"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper class="com.zhengfa.mybatis.dao.UserMapper"></mapper>
</mappers>
</configuration>
main:
public static void main(String[] args) throws IOException {
InputStream fileInputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(fileInputStream);
SqlSession sqlSession = factory.openSession(true);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.selectByid(1);
System.out.println(user);
sqlSession.close();
}
service,mapper什么的这里就不贴了,小白请自行去看入门博客~
2.1 mybatis使用流程
从上面的xml配置文件到主方法使用mapper调用查询方法,我们主要做了以下几件事:
- 配置DataSource,也就是我们要连接的数据库
- 配置mapper位置,因为mybatis需要将mapper.xml与class联系起来
- 读取主配置文件mybatis-config.xml
- 通过SqlSessionFactoryBuilder构建SqlSessionFactory
- 通过SqlSessionFactory打开一个SqlSession
- 通过SqlSession获取Mapper实例
得到mapper实例对象之后,我们就可以进行相关操作了。
2.2 mybatis三个组件
从上述流程我们可以发现,使用mybatis涉及到了三个组件:
SqlSessionFactoryBuilder:
用于构建会话工厂,基于config.xml构建会话工厂,构建完成后即可丢弃。
SqlSessionFactory:
用于生成会话的工厂,作用于整个应用运行期间,一般不需要构造多个工厂对象
SqlSession:
作用于单次会话,如WEB一次请求期间,不能用作于某个对象属性,也不能在多个线程间共享,因为它是线程不安全的。
2.3 SqlSession
这里单独把SqlSession列出来,是因为这个接口实在是太重要了。它为我们提供了大量操作数据库的接口。
而在Mybatis中,SqlSession的实现类是DefaultSqlSession
在实际应用中,我们可以通过openSession方法拿到session之后,像JDBC那样去操作数据库:
但是这也太笨重了,如果Mybatis就这的话,还不如使用JDBC。
2.4 Mapper接口式编程
Mybatis通过Mapper接口式编程的方式,巧妙的让SqlSession实现类消失了,取而代之的是通过mapper.xml与mapper接口通过反射建立映射生成代理类的方法,用面向对象的方法来编程。
在DefaultSqlSession中,通过getMapper方法,我们可以得到Mapper的代理类实例:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
3、spring整合mybatis
我们都知道spring可以抽象为IOC容器,先不说具体流程,我们来思考一下,刚才mybatis的流程中,有哪些是可以交给spring管理的:
- 数据源DataSource
- SqlSessionFactory
- SqlSessionTemplate
- Mapper
我们通过引入org.mybatis.mybatis-spring包和spring之后,重新来配置一次简单的实例:
spring.xml:
<?xml version="1.0" encoding="GBK"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/springtrans"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<property name="dataSource" ref="dataSource"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperFactoryBean" id="userFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="mapperInterface" value="com.zhengfa.mybatis.dao.UserMapper"/>
</bean>
</beans>
main:
public static void main(String[] args) {
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("spring.xml");
UserMapper userMapper = context.getBean(UserMapper.class);
System.out.println(userMapper.selectByid(1));
}
可以看到,spring整合mybatis之后的代码变得清爽了很多。
但是这中间出现了一个我们在之前没见过的东西:SqlSessionTemplate,同时更加令人困惑的是,不仅在配置中没有看到它,就连使用时也没有它,那么它到底是干嘛的呢?
在了解它之前,我们先介绍一下其它的组件。
3.1 SqlSessionFactoryBean
在MyBatis中要构建SqlsessionFactory对象,让它来产生 Sqlsession。在Mybatis-Spring中也不例外。
在一开始的数据源配置肯定是必不可少的,DriverManagerDataSource是spring自带的数据源,当然我们也可以选择配置Druid数据源。
而我们之前的SqlSessionFactory也被SqlSessionFactoryBean所替换,这也很好理解,spring把mybatis的SqlSessionFactory作为一个bean加入到了容器中。
3.3 SqlsessionTemplate
SqlsessionTemplate( org.mybatis.spring.SqlsessionTemplate)是一个模板类,它是SqlSession的一个实现类,通过Sqlsession来完成工作。
在Mybatis中,对应的就是DefaultSqlSession。而在MyBatis-Spring中,实现类就是我们的SqlsessionTemplate。
它为我们提供了大量方法以及对SqlSession生命周期的操作,我们可以通过添加如下配置来使用它:
<bean class="org.mybatis.spring.SqlSessionTemplate" id="sessionTemplate">
<constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
public static void main(String[] args) {
ClassPathXmlApplicationContext context
= new ClassPathXmlApplicationContext("spring.xml");
SqlSessionTemplate sqlSessionTemplate = context.getBean(SqlSessionTemplate.class);
sqlSessionTemplate.selectList("……");
}
这个使用过程和DefaultSqlSession的使用没有什么差别?直接使用SqlsessionTemplate显得极为笨重,我们一般不会在代码中使用。
但是我们仍然需要操作SqlSession,在Mybatis中通过DefaultSqlSession的getMapper方法完成了对代理类的实例化,但是Spring明显不会每次还要显示调用getMapper方法。于是添加了MapperFactoryBean组件。
3.3 MapperFactoryBean
熟悉Spring的同学就应该能了解FactoryBean和BeanFactory的区别,这里简要提一下:
一般情况下,Spring通过反射机制利用的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂。
如果按照传统的方式,则需要在中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。
Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。
从宏观来看,MapperFactoryBean接口的逻辑就是为我们完成了从mapper的接口类到mapper的代理类创建,然后加入到Spring容器中。
3.4 配置mapperScan
假如我们需要第二个mapper时,我们就需要在xml中添加如下配置:
<bean class="org.mybatis.spring.mapper.MapperFactoryBean" id="testFactoryBean">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="mapperInterface" value="com.zhengfa.mybatis.dao.Test"/>
</bean>
这从使用者的角度来说,简直就是噩梦。还好在mybatis.xml配置mapper时,就已经有包扫描的配置,我们只需要简单的进行如下配置即可:
<mybatis:scan base-package="com.zhengfa.mybatis.dao"/>
那么接下来,我先要问你几个问题:
- 假如指定的包中,存在大量接口类,mybatis会把它们全部扫描进去并创建代理类吗?
- 假如我创建了一个TestMapper接口,但是没有写对应的xml,会报错吗?如果会报错,是在什么时候报错呢?
这两个问题大家可以自行实践去查证,答案将在结尾给出。
4、mybatis与spring流程小结
我们再来做一次小结,对比一下mybatis与spring的差异:
mybatis | mybatis-spring | |
---|---|---|
SqlSessionFactory | 通过SqlSessionFactoryBuilder读取配置文件构建 | 通过SqlSessionFactoryBean的getObject方法获取 |
SqlSession实现类 | DefaultSqlSession | SqlSessionTemplate |
Mapper实例 | DefaultSqlSession.getMapper方法 | 通过MapperFactoryBean的getObject方法返回实例,实际上是调用SqlSessionTemplate.getMapper方法 |
其它宏观或者微观细节大致上没有差异,比如xml解析流程,比如映射器和核心类Configuration,这些我虽然没有去求证过,但是想来不会有多少出入。
不过有需要注意的点是,在Spring中,Mapper是单例对象,但是Mapper与SqlSession是绑定在一起的,那这样一来,岂不是SqlSession也是单例的?为什么我们在实际使用过程中,不会出现线程安全问题呢?关于这个问题,博主会另写一篇博客介绍~
最后回答一下之前的两个问题:
- 会扫描进去并创建代理类,从而产生大量的无用代理对象,因此我们可以通过配置MapperSacnnerConfigurer来优化
- 启动过程并不会报错,但是使用时会报错,有兴趣的读者可以去了解一下XML解析以及代理方法调用流程