在上一篇文章 https://blog.csdn.net/qq_26323323/article/details/81335058 中,我们说了使用mybatis有三种方式, 而上篇文章介绍了关于使用原生Mybatis的源码解析。
实际在常规项目开发中,大部分都会使用mybatis与Spring结合起来使用,毕竟现在不用Spring开发的项目实在太少了。
那么该篇文章便来介绍下Mybatis如何与Spring结合起来使用,并介绍下其源码是如何实现的。建议读者先看下上篇文章,对mybatis原生情况下的使用有一定的了解
org.mybatis
mybatis
3.2.8
org.projectlombok
lombok
1.18.2
provided
mysql
mysql-connector-java
5.1.32
org.mybatis
mybatis-spring
1.3.2
org.springframework
spring-jdbc
4.3.8.RELEASE
在src/main/resources/mapper路径下添加User.xml
在src/main/resources/路径下添加beans.xml
package cn.itcast.springmvc.bean;
import org.springframework.stereotype.Component;
import lombok.Data;
@Data
@Component
public class User {
private int id;
private String name;
private String dept;
private String phone;
private String website;
}
public interface IUser {
public User getUser(int id);
}
public class SpringMybatisTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
IUser userDao = (IUser)ac.getBean("userDao");
User user = userDao.getUser(3);
System.out.println(user);//User(id=3, name=jack, dept=devp, phone=xxx, website=www.baidu.com)
}
}
测试结果正确。
通过以上的过程可知,Mybatis与Spring结合的最重要的配置就是beans.xml。
beans.xml将dataSource、SQLSessionFactory及IUser注入到Spring容器中,然后再使用的时候获取到对应的IUser,然后进行CRUD操作
* 解析spring-mybatis-config.xml,生成Resource
* 使用SqlSessionFactoryBuilder创建SqlSessionFactory
* 使用SqlSessionFactory创建SqlSession
* 从SqlSession中获取Mapper接口(也就是本例中的IUser接口)
* 使用Mapper接口进行CRUD操作
注意:上篇文章中讲的示例是直接使用SqlSession.selectOne()来进行查询操作的;
实际还有另一种更面向对象的查询方式,就是从SqlSession.getMapper()中获取Mapper接口,然后通过Mapper接口来进行查询操作
Spring-Mybatis使用的就是这种方式
就对应着SqlSessionFactory的生成,类似于原生Mybatis使用时的以下代码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
而以下userDao的获取
就对应着Mapper接口的获取,类似于原生Mybatis使用时的以下代码:
SqlSession session = sqlSessionFactory.openSession();
IUser mapper = session.getMapper(IUser.class);
总结:所以我们现在就主要分析下在Spring中是如何生成SqlSessionFactory和Mapper接口的
由beans.xml中的配置可知,其实现类为org.mybatis.spring.SqlSessionFactoryBean ,并定义了属性configLocation和dataSource,下面我们就来看下
SqlSessionFactoryBean 这个类
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
private Resource configLocation;
private Resource[] mapperLocations;
private DataSource dataSource;
private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
private SqlSessionFactory sqlSessionFactory;
...
}
SqlSessionFactoryBean实现了FactoryBean接口,有关于FactoryBean的使用,读者可自行查询下,笔者不再赘述
那我们从容器中获取SqlSessionFactoryBean的时候,实际是调用其getObject()方法,该方法的返回值才是容器真正给我们生成的对象,下面我们来看下该方法
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
// sqlSessionFactory默认为空,直接走afterPropertiesSet()方法
// 实际不是这样的,由于SqlSessionFactoryBean实现了InitializingBean,则再该bean生成之后,
// 会直接调用afterPropertiesSet()方法,来创建sqlSessionFactory,故sqlSessionFactory应该
// 是已经被创建好的
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
// afterPropertiesSet()
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
this.sqlSessionFactory = buildSqlSessionFactory();//关键方法
}
// buildSqlSessionFactory()
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configLocation != null) {
// 1.解析spring-mybatis-config.xml配置
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
// 2.获取配置中心Configuration
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (logger.isDebugEnabled()) {
logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
configuration.setVariables(this.configurationProperties);
}
// 3.以下的操作就是对Configuration中的各种属性进行set操作
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (logger.isDebugEnabled()) {
logger.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
...
Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
configuration.setEnvironment(environment);
...
// 4.最终还是通过sqlSessionFactoryBuilder来生成sqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
总结:通过以上代码的分析,可知,SqlSessionFactoryBean在容器中最终返回的就是通过sqlSessionFactoryBuilder.build(configuration)返回的SqlSessionFactory,与我们使用原生方式获取SqlSessionFactory没有什么差别
由beans.xml中的配置可知,其实现类为org.mybatis.spring.mapper.MapperFactoryBean,并且定义了属性mapperInterface和sqlSessionFactory。
注意:mapperInterface需要指定我们的IUser接口全路径
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
}
类似于SqlSessionFactoryBean,MapperFactoryBean也实现了FactoryBean接口,那容器真正返回给我们的同样也是getObject()返回的对象。
public T getObject() throws Exception {
// getSqlSession()返回的是SqlSessionDaoSupport的sqlSession属性
// 那么这个属性是什么时候被赋值的呢?
// 读者可自行思考下
return getSqlSession().getMapper(this.mapperInterface);
}
注意:MapperFactoryBean在Spring中真正返回的也就是SqlSession.getMapper()方法中返回的Mapper接口
问题:那么Mapper接口是什么时候被放入Configuration的呢?
通过分析MapperFactoryBean的结构可知,其也实现了InitializingBean接口,那么在该bean加载进Spring的时候,也会在初始化的时候自动调用其afterPropertiesSet()方法,我们来看下这个方法
具体实现在DaoSupport类中
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// 该方法在MapperFactoryBean中有具体实现
checkDaoConfig();
// Spring没有关于其的实现,作者可自行实现
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
MapperFactoryBean.checkDaoConfig()
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 核心代码在这里,主要是将该Mapper接口添加进Configuration,以便后面使用
configuration.addMapper(this.mapperInterface);
} catch (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
总结:
MapperFactoryBean初始化的时候就通过afterPropertiesSet()方法把Mapper接口添加进Configuration;
MapperFactoryBean.getObject()返回的也就是从Configuration中获取的Mapper接口。
整个MapperFactoryBean.getObject()方法类似于原生Mybatis中
SqlSession session = sqlSessionFactory.openSession();
IUser mapper = session.getMapper(IUser.class);
通过以上对Spring-Mybatis的源码分析,可知,其本质还是Mybatis。
只不过,容器帮我们生成了SqlSessionFactory和SqlSession,同时把Mapper加载进Configuration,可以让我们直接以getBean()的方式来获取Mapper