本文目录:
在读本文之前,默认你已经了解 MyBatis 编程的基本使用,熟悉 MyBatis 的使用流程,了解了SqlSessionFactoryBuilder
、SqlSessionFactory
、SqlSession
、Mapper
这四大对象以及getMapper()
方法的用法。如果你对这些还不是太了解,建议你先翻回去了解一下:MyBatis基础使用,这样会加深你对 MyBatis 使用的了解。
本文接下来重点分析 Spring 整合 MyBatis 的流程,请继续往下看 →Go 皮卡丘。在了解 Spring 整合 MyBatis 之前,我们先来了解一下这个问题:
1.为什么要将 MyBatis 整合到 Spring?
- Spring会帮助我们管理对象。
省去我们对 SqlSessionFactory、SqlSession、Mapper 这些核心对象的创建,不需要我们手工去创建了,Spring 会通过 IOC 机制来帮我们管理这些对象。 - Spring 使用 xxxTemplate 封装了方法
Spring整合MyBatis,它为我们提供了一个xxxTemplate方法,方便我们去调用。
2. Spring 整合 MyBatis 原理
将 MyBatis 集成到 Spring中,首先我们需要使用到一个"桥梁",即:MyBatis 官方为我们提供的 mybatis-spring
jar 包。它会依赖 Spring 中为我们预留的扩展点,来完成 Spring 与 MyBatis 的整合。
2.1 Spring整合MyBatis流程
Spring 整合 MyBatis 过程,共以下两步:
2.1.1 项目中添加 Maven 依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
2.1.2 Spring核心配置applicationContext.xml添加如下配置
<!-- spring和MyBatis完美整合,不需要mybatis的配置映射文件 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- dataSource 数据源,使用的是Spring的数据源 -->
<property name="dataSource" ref="mysqlDatasource" />
<!-- 指定全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>
<!-- DAO接口所在包名,Spring会自动查找其下的类 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mvc.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
2.2 Spring整合 MyBatis 过程源码分析
我们已经了解到,MyBatis的使用离不开SqlSessionFactory
、SqlSession
、Mapper
这三大对象。这三大对象是如何被创建的呢?我们接下了进一步分析
第一步:SqlSessionFactory对象的创建
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- dataSource 数据源,使用的是Spring的数据源 -->
<property name="dataSource" ref="mysqlDatasource" />
<!-- 指定全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<!-- 自动扫描mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>
我们已经在 applicationContext.xml 中有配置以上 sqlSessionFactoryBean
,通过名称我们便可以大概了解到该 Bean 就是用来为我们创建 sqlSessionFactory 对象的类。(我们继续往下分析 → Go → Go)
1.1 我们来分析sqlSessionFactoryBean 类
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
......
}
我们看到该类实现了一个 FactoryBean
、InitializingBean
的接口。
我们先来分析一下 InitializingBean
接口,在源码中,我们能够看到该接口有一个 afterPropertiesSet() 方法
需要实现。如下所示,那么该方法是用来干嘛的呢?
public interface InitializingBean {
/**
* 用于对象实例化设置bean属性完成后,执行的一些操作,
* 该方法由 BeanFactory调用。即创建工厂类的时候,一定会调用这个方法
*/
void afterPropertiesSet() throws Exception;
}
我们已经知道了 afterPropertiesSet() 方法的意思。接下来我们再来分析FactoryBean
接口,我们看到该接口共有三个方法需要实现,此处我们重点来关注一下 getObject() 方法。
public interface FactoryBean<T> {
/**
* 该方法用来获取对象。
* 即:从Spring IOC容器中获取创建的对象,会调用到该方法
*/
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
介绍完这两个接口,我们再来看看 getObject()
方法和 afterPropertiesSet()
方法在 sqlSessionFactoryBean 类中的具体实现过程。
1.2 分析getObject() 、afterPropertiesSet() 方法在类中的实现
源码显示,实际调用流程如下:
1.getObject() → 2.afterPropertiesSet() → 3.buildSqlSessionFactory()
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
/**
* 其他属性、方法 省略,仅展示要分析的源码
*/
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
//1.第一步
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
//2.第二步
this.sqlSessionFactory = buildSqlSessionFactory();
}
/**
* buildSqlSessionFactory()方法
*/
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
//3.第三步:XMLConfigBuilder 用来读取配置的 xmlConfig
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
//省略部分判断代码
}
if (xmlConfigBuilder != null) {
try {
//4.执行 xmlConfigBuilder.parse()方法,此处已经走入到 mybatis 源码部分,已经是MyBatis的执行流程了。
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//5.解析XMLMapper映射器
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//6.调用 xmlMapperBuilder.parse()方法,跳转到MyBatis源码去解析 XMLMapper 配置
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
//7.通过sqlSessionFactoryBuilder.build()方法,创建一个sqlSessionFactory
return this.sqlSessionFactoryBuilder.build(configuration);
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
通过如上代码分析,此处我们整理了一下,执行流程如下图所示:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- dataSource 数据源,使用的是Spring的数据源 -->
<property name="dataSource" ref="mysqlDatasource" />
<!-- 指定全局配置文件 -->
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<!-- 自动扫描 mapping.xml文件 -->
<property name="mapperLocations" value="classpath:com/mvc/mapper/*.xml"></property>
</bean>
执行到此处,配置完上面这个<bean id=“sqlSessionFactory”>
,它便会在 Spring 启动创建 sqlSessionFactoryBean 时,调用 getObject() 等一系列方法,将全局配置文件
和mapper配置文件
等一些列操作在上面源码中都已经完成。最后调用了一个 return this.sqlSessionFactoryBuilder.build(configuration);
返回了一个DefaultSqlSessionFactory() 对象。
到达此处---->创建 sqlSessionFactory 对象就 OK 了。接下来继续了解 sqlSession 对象的创建流程。皮卡丘,继续向下看↓↓↓↓↓
第二步:SqlSession对象的创建
在 MyBatis 中,我们创建 sqlSession 使用的是 new DefaultSqlSession()
的方式,代码如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
//MyBatis源码:使用的是 new DefaultSqlSession()的方式,返回一个 SqlSession 对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.1 我们来分析整合Spring时,如何创建SqlSession类
2.1.1 为什么不能使用 DefaultSqlSession 来创建 SqlSession?
在整合 Spring 的过程中,我们是不能使用new DefaultSqlSession()
的方式来创建 SqlSession,这是因为 DefaultSqlSession 类在 MyBatis 中是线程不安全的。 如下图所示:
在与 Spring 整合的过程中,Spring 为了解决 DefaultSqlSession 线程不安全的问题,为我们提供了一个线程安全的 SqlSessionTemplate
类。如下图所示:
2.1.2 SqlSessionTemplate为什么是线程安全的?
我们从SqlSessionTemplate
类开始分析。该类同我们在 MyBatais 中使用的 DefaultSqlSession 类相同,也都为我们提供了直接操作数据库的一些方法,比如:selectOne(),selectList() 等。
我们从 selectOne() 方法入手来分析,发现此处使用的是 sqlSessionProxy 对象,这个对象我们通过名称可以看出它是一个代理类。
public class SqlSessionTemplate implements SqlSession, DisposableBean {
//部分代码省略
private final SqlSession sqlSessionProxy;
@Override
public <T> T selectOne(String statement) {
//使用 sqlSessionProxy 来调用 selectOne() 方法
return this.sqlSessionProxy.<T> selectOne(statement);
}
}
通过属性定义private final SqlSession sqlSessionProxy;
发现,它还是一个 SqlSession 类。最终这个 SqlSession 还是指代的是 DefaultSqlSession类。
我们来了解一下 sqlSessionProxy 的实例化过程,我们发现它是在构造器中进行实例化的,代码如下:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
//发现:sqlSessionProxy其实是 SqlSessionInterceptor 的代理类
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
我们继续对 SqlSessionInterceptor 类进行分析,代理类肯定会执行它的 invoke() 方法,接下来我们对 invoke()
方法进行了解,代码如下:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//这个方法,此处这个 getSqlSession() 方法就是一个重点了
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
//此处是通过 Transactional 事务的方式
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
//sqlSession提交
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
//省略
}
throw unwrapped;
} finally {
if (sqlSession != null) {
//关闭sqlSession
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
我们继续对 getSqlSession() 方法进行分析,代码如下:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//此处,通过事务管理器的 getResource()方法,将我们的sqlSessionFactory工厂类传入
//返回的是一个 SqlSessionHolder持久器类
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Creating a new SqlSession");
}
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
我们继续对 getResource() 方法进行分析,代码如下:
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");
public static Object getResource(Object key) {
Object actualKey = TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
//执行 doGetResource()方法
Object value = doGetResource(actualKey);
if (value != null && logger.isTraceEnabled()) {
logger.trace("Retrieved value [" + value + "] for key [" + actualKey + "] bound to thread [" + Thread.currentThread().getName() + "]");
}
return value;
}
private static Object doGetResource(Object actualKey) {
//我们通过 resources.get() 的方式来获得,我们对这个 resources 属性进行分析
//发现它返回的 SqlSession的持久器 SqlSessionHolder 也是一个ThreadLocal对象。
//即:我们每一个获取到的 SqlSession都是有自己的SqlSessionHolder对象的
//此处,一个事务一个SqlSession。本身ThreadLocal 就是线程安全的,所以每一个SqlSession 也都是线程安全的
Map<Object, Object> map = (Map)resources.get();
if (map == null) {
return null;
} else {
Object value = map.get(actualKey);
if (value instanceof ResourceHolder && ((ResourceHolder)value).isVoid()) {
map.remove(actualKey);
if (map.isEmpty()) {
resources.remove();
}
value = null;
}
return value;
}
}
}
如果你还不了解ThreadLocal
的使用,请到:ThreadLocal,你想了解的都在这里(进来瞧瞧不后悔)充充电。源码分析到此处---->通过 SqlSessionTemplate
就可以创建 线程安全的 SqlSession 对象了。接下来继续了解 getMapper 的流程。皮卡丘,继续向下看↓↓↓↓↓
第三步:在 DAO 层如何获取 SqlSessionTemplate 来调用数据库
这个过程,即 MyBatis 中的 getMapper() 的过程。我们在 DAO 层是怎么能够拿到 SqlSessionTemplate 的呢?(实际开发中我们使用的是注解的方式,此处不通过注解的形式,我们来分析一下如何拿到这个 SqlSessionTemplate)
3.1.1 我们通过继承 SqlSessionDaoSupport 的方式来获取SqlSessionTemplate
在 mybatis-spring 整合包中,它为我们提供了一个SqlSessionDaoSupport 类。我们只需要继承这个类,通过getSqlSession()
方法,便能够获取到一个 sqlSessionTemplate。
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
/**
* 省略部分代码
*/
/**
* Users should use this method to get a SqlSession to call its statement methods
* This SqlSession is managed by spring. Users should not commit/rollback/close it
* because it will be automatically done.
*
* @return Spring managed thread safe SqlSession
*/
public SqlSession getSqlSession() {
return this.sqlSession;
}
}
3.1.2 【过渡阶段】Dao层实现类继承自SqlSessionDaoSupport,完成对数据库的操作
此部分仅是一个过渡阶段,在我们的项目中不会使用这种方式开发,此处只是用来介绍一下(但是,有的公司还是使用的这种方式,我公司有的项目就是这样子开发。也是What…我佛了)
我们定义一个父类 BaseDao,让其继承 SqlSessionDaoSupport ,然后使用我们的 Mapper 接口的实现类来继承这个 BaseDao,我们便能够拿到 sqlSessionTemplate,完成对数据库的操作。
public class BaseDao<T> extends SqlSessionDaoSupport {
private static final Log log = LogFactory.getLog(BaseDao.class);
public List<T> query(String sql, Object obj) throws DaoException{
List<T> lists = null;
try {
lists = super.getSqlSession().selectList(sql, obj);
} catch (Exception e) {
String info = String.format("qury list for db, sql is [%s], param is [%s] failed.", sql,
null != obj ? obj.toString() : null);
log.error(info , e);
throw new DaoException(ReportConstant.DB_ACTION_EXCEPTION, info);
}
return lists;
}
public int save(String sql, Object obj) throws DaoException{
//省略
}
public boolean delete(String sql,Object obj) throws DaoException {
//省略
}
public boolean update(String sql, Object obj) throws DaoException{
//省略
}
}
@Repository
public class ColumnMgrDaoImpl extends BaseDao<ColumnMgr> {
public ColumnMgr query(int cid){
ColumnMgr columnMgr = (ColumnMgr)this.selectOne("com.springboot.dao.UserMapper.selectAllUser",cid);
return columnMgr;
}
}
public class DaoSupportTest{
@Autowired
ColumnMgrDaoImpl columnMgrDao;
@Test
public void test{
ColumnMgr columnMgr = columnMgrDao.query(1);
System.out.println(columnMgr);
}
}
这样子虽然可以查询数据库,但是此处会有很多实现类,显然实际开发中不是这样开发,我们只使用了一个注解@Autowired即可。那这又是为什么呢?(
)皮卡丘,继续向下看↓↓↓↓↓
3.1.3 既然不想写那么多Dao的实现类,那么此处来了解一下接口扫描注册的过程
当使用 @Autowired 注入一个接口的时候,首先这个接口肯定是从Spring IOC 容器中获得的,那么就说明在之前我们肯定已经把它注册到 IOC 容器了。那我们是在什么时候注册的?此处就涉及到了一个接口扫描的配置,如下配置部分:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入要扫描的包名 -->
<property name="basePackage" value="com.mvc.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
我们先来分析一下 MapperScannerConfigurer
这个类,我们发现 它有实现一个 BeanDefinitionRegistryPostProcessor
这个接口,该接口有一个postProcessBeanDefinitionRegistry()
方法需要被实现,接下来我们就分析一下这个方法。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
//省略部分代码
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
//1.调用 scan() 方法
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
//2.调用 doScan()方法
doScan(basePackages);
// Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}
}
doScan()方法,实际上调用的是ClassPathMapperScanner 类中的 doScan() 方法,如下所示:
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//3.继续调用此处的方法
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
//4.此处,拿到所有beanDefinition(即接口)
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
//5.(重点)注意此处:我们向IOC容器中注册的对象,本来应该是Mapper接口本身
//但是,此处我们真正注册到IOC容器的是一个 MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
//6.将接口的 beanClass 替换成 MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
//部分代码省略
}
}
我们再来分析一下这个 MapperFactoryBean,我们发现它竟然继承了 SqlSessionDaoSupport,其实就是为了通过 getSqlSession()方法来返回一个 sqlSession,解决我们在3.1.2 步骤中的繁琐操作的。现在就省去了3.1.2步骤中实现类的编写了。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
......
}
第四步:我们继续分析getMapper()的过程
我们看到 MapperFactoryBean 对象还有实现一个 FactoryBean
接口,在本文开始我们有介绍它有一个 getObject() 方法需要被实现,那我们就看一下 MapperFactoryBean 类中对 getObject() 方法时如何实现的。(
)前方高能,重点代码出现了↓↓↓
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
//部分代码省略
@Override
public T getObject() throws Exception {
//1.通过 SqlSessionDaoSupport 类中的 getSqlSession()方法,可以获取一个SqlSession对象
//通过 getMapper()方法,来获取一个接口实现类
return getSqlSession().getMapper(this.mapperInterface);
}
}
那我们继续对 getMapper()方法进行分析,跳转到 SqlSessionTemplate 类中去分析它的实现过程。Go Go Go,皮卡丘,快要见太阳了
public class SqlSessionTemplate implements SqlSession, DisposableBean {
@Override
public <T> T getMapper(Class<T> type) {
//2.通过getConfiguration()方法,来获取MyBatis的全局配置对象
//通过getMapper()方法,继续向下分析就进入到 MyBatis包中的代码了
return getConfiguration().getMapper(type, this);
}
}
继续向下分析,进入MyBatis 源码包
/**
* 进入到 MyBatis 的 Configuration 类
*/
public class Configuration {
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//3.此处返回的就是一个MapperProxy的代理类对象了
return this.mapperRegistry.getMapper(type, sqlSession);
}
}
到此处,我们得出结果,最终通过@Autowired 注解的 Mapper类,返回来的其实就是一个 MapperProxy 代理类的对象。(此处我们以 UserService 为例分析)
如下所示:我们最终自己编写的 UserService 中,返回来的这个 UserMapper 就是一个 MapperProxy 代理类。
@Service("userService")
public class UserService implements IUserService {
@Autowired
public UserMapper userDao;//4.此处就是一个 MapperProxy 代理类
/**
* 获取用户信息
* @return
*/
@Override
public List<User> getUser() {
List<User> users = userDao.selectAllUser();
return users;
}
}
最后一步,通过userDao.selectAllUser()
的调用,最后执行的其实还是 MapperProxy 代理类中的 invoke()方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
//省略部分代码
//5.最终执行的还是 MapperProxy 代理类中的 invoke()方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//6.在此处,它会通过反射的方式,获取到使用注解类的 class
//传递给上两步,MyBatis源码中的getMapper(type,sqlSession)方法中的type参数
//从而实现 MyBatis 中,通过getMapper()的方式获得当前类。
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
至此,MyBatis中通过 getMapper() 获得 Mapper 类,在Spring 中的实现方式也就介绍完了。
这就是我们在 Spring 中,只需要在 UserMapper 类上添加一个 @Autowired 注解,便能够实现 MyBatis 中的 getMapper()的过程。
恭喜您,看到这里,说明你是最棒的 Yeah、Yeah、Yeah
Spring 整合 MyBatis 源码分析,介绍到此为止
博主写作不易,来个关注呗
求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙
博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ