从源码分析spring整合mybatis
Posted 编程之艺术
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从源码分析spring整合mybatis相关的知识,希望对你有一定的参考价值。
回顾上一篇文章中,spring整合mybatis的三种配置mapper接口bean的方式:
mapper接口有实现类
-
mapper接口无实现类
-
配置mapper接口的扫描
首先来回顾一下spring整合mybatis的配置:
-
第一步:配置数据源dataSource,class为c3p0的ComboPooledDataSource类。
<!-- 使用c3p0连接池配置数据源,需要导入c3p0的jar包 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
<!-- 是否开启自动提交,默认为false -->
<property name="autoCommitOnClose" value="false"/>
<property name="driverClass" value="${dataSource.driver}"/>
<property name="jdbcUrl" value="${dataSource.url}"/>
<property name="user" value="${dataSource.username}"/>
<property name="password" value="${dataSource.password}"/>
<!-- 池的配置 -->
<!--初始化时获取多少个连接,取值应在minPoolSize与maxPoolSize之间。默认值: 3 -->
<property name="initialPoolSize" value="${c3p0.initialPoolSize}"/>
<property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
<property name="minPoolSize" value="${c3p0.minPoolSize}"/>
<property name="maxStatements" value="${c3p0.maxStatements}"/>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。默认值: 3 -->
<property name="acquireIncrement" value="${c3p0.acquireIncrement}"/>
<!--最大空闲时间,指定多少秒内未使用则连接被丢弃。若为0则永不丢弃。默认值: 0 -->
<property name="maxIdleTime" value="${c3p0.maxIdleTime}"/>
<!--指定多少秒检查一次所有连接池中的空闲连接。默认值: 0 -->
<property name="idleConnectionTestPeriod" value="${c3p0.idleConnectionTestPeriod}"/>
</bean> -
第二步:配置sqlSessionFactory,class是mybatis-spring提供的SqlSessionFactoryBean类,该类实现了FactoryBean接口。
<!-- sqlsessionfactory,mybatis-spring提供的SqlSessionFactoryBean类,该类实现了FactoryBean接口 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- mybatis的核心配置文件路径,该文件中只保留mybatis的一些设置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 接口映射文件路径,*匹配任意字符 -->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean> -
第三步:1).mapper接口有实现类
<bean id="userDao" class="wjy.sm.dao.impl.UseDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!--<property name="sqlSessionTemplate" ref=""/>-->
</bean>2).mapper接口无实现类 <bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 注入Mapper接口,也可以构造方法注入 -->
<!--<property name="mapperInterface" value="wjy.sm.dao.UserDao"/>-->
<constructor-arg index="0" value="wjy.sm.dao.UserDao"/>
<!-- 注入SqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>3).配置mapper接口的扫描 <!-- 配置mapper接口的扫描 -->
<!-- bean的名称为接口的类名,首字母小写 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="wjy.sm.dao"/>
</bean>
通过这三步配置我们就可以以spring的方法获取到mapper接口的实例对象来操作了。
从mapper接口有实现类入手
我们从第一种有接口实现类的方法进行深入源码。
就以例子中的userDao这个bean为例,看下UserDao接口的实现类UseDaoImpl。要使用mapper接口的实现类来配置bean,那么就需要这个实现类不仅需要实现该mapper接口,还需要继承自SqlSessionDaoSupport这个类,我们知道spring整合mybatis需要一个额外jar包,这个jar包是mybatis-spring.*.jar,而SqlSessionDaoSupport是mybatis-spring.*.jar包提供的。
[UseDaoImpl.java]
package wjy.sm.dao.impl;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import wjy.sm.bean.User;
import wjy.sm.dao.UserDao;
public class UseDaoImpl extends SqlSessionDaoSupport implements UserDao {
public User getUserByUsername(String username) {
UserDao userDao = this.getSqlSession().getMapper(UserDao.class);
return userDao.getUserByUsername(username);
}
public User findUserById(int id) {
UserDao userDao = this.getSqlSession().getMapper(UserDao.class);
return userDao.findUserById(id);
}
public int savaUserGetId(User user) {
UserDao userDao = this.getSqlSession().getMapper(UserDao.class);
return userDao.savaUserGetId(user);
}
}
在实现类中,我们可以使用this.getSqlSession()获取到一个SqlSession。还记得mybatis单独使用的过程吗?先来回顾一下吧,看下面代码,首先通过mybatis的核心配置文件获取数据库连接配置信息,将配置信息作为参数调用SqlSessionFactoryBuilder实例的build方法构建一个SqlSessionFactory实例,再通过SqlSessionFactory实例的openSession方法获取到SqlSession对象,拿到SqlSession实例就可以通过getMapper方法获取实现指定mapper接口的一个实例来操作数据了。
try {
//通过配置文件获取数据库连接配置信息
//通过配置信息构建一个SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
//通过SqlSessionFactory打开一个数据库会话
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过sqlSession的getMapper方法获取实现了指定接口的一个实例对象,至于底层是怎么实现的就需要自己去研究mybatis的源码了,这也不是本篇文章讨论的话题
UserDao userDao = sqlSession.getMapper(UserDao.class);
userDao.savaUserGetId(new User());
}catch (IOException e){
e.printStackTrace();
}
回顾mybatis的单独使用之后我们回到主题来,接着看下SqlSessionDaoSupport这个类的源码,看看this.getSqlSession()是如何获取到一个SqlSession实例的。
[SqlSessionDaoSupport源码]
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public SqlSessionDaoSupport() {
}
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
public SqlSession getSqlSession() {
return this.sqlSession;
}
protected void checkDaoConfig() {
Assert.notNull(this.sqlSession, "Property 'sqlSessionFactory' or 'sqlSessionTemplate' are required");
}
}
SqlSessionDaoSupport为spring注入提供了两个setter方法,一个是注入SqlSessionFactory实例的,另一个是注入SqlSessionTemplate实例的。SqlSessionFactory我们都很熟悉了,因为只有通过SqlSessionFactory我们才能获取到SqlSession,相对来说SqlSessionTemplate就显得很陌生,先暂时忽略SqlSessionTemplate这个东东吧。
SqlSessionDaoSupport类定义了一个私有的类型为SqlSession的属性sqlSession,还实现了getter方法返回sqlSession属性的值。我们来看看sqlSession被实例化的过程,先看setSqlSessionFactory方法体内的这句代码
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
哎,想避都避不开这个SqlSessionTemplate类,这么陌生,感觉很讨厌。既然避不开,那就客服它吧。
package org.mybatis.spring;
import ....
public class SqlSessionTemplate implements SqlSession, DisposableBean {
好,看到SqlSessionTemplate是实现SqlSession接口的就行了,其它的暂时就先不管了。现在回到前面提的问题SqlSessionDaoSupport实例的this.getSqlSession()是如何获取到一个SqlSession实例的?那么你明白了吗?就是new一个SqlSessionTemplate对象啊,SqlSessionTemplate是实现SqlSession接口的。
我们在配置userDao的时候注入了sqlSessionFactory,所以当执行到setSqlSessionFactory这个方法的时候就实例化了sqlSession属性,当我们在UseDaoImpl中调用this.getSession的时候就可以获取到Session的实例了。
<bean id="userDao" class="wjy.sm.dao.impl.UseDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<!--<property name="sqlSessionTemplate" ref=""/>-->
</bean>
我们就拿UserDaoImpl的一个方法来说
public User getUserByUsername(String username) {
UserDao userDao = this.getSqlSession().getMapper(UserDao.class);
return userDao.getUserByUsername(username);
}
通过this.getSqlSession()获取到SqlSession实例,当然,这就是一个SqlSessionTemplate实例,所以当我们继续调用其getMapper方法的实现,走的就是SqlSessionTemplate的方法。
[SqlSessionTemplate类的getMapper方法源码]
public <T> T getMapper(Class<T> type) {
return this.getConfiguration().getMapper(type, this);
}
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
到这里我们已经获取到UserDao的实例了。
现在把UserDaoImpl的getUserByUsername改为如下
public User getUserByUsername(String username) {
return this.getSqlSession().selectOne("wjy.sm.dao.UserDao.getUserByUsername",username);
}
直接调用的this.getSqlSession()方法获取到的SqlSession实例(即SqlSessionTemplate实例)的selectOne方法,这是实现SqlSession接口的方法。
先来看下SqlSession的源码吧:
package org.apache.ibatis.session;
import ......
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1);
<E> List<E> selectList(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
<K, V> Map<K, V> selectMap(String var1, String var2);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
跟进selectOne方法,也就是SqlSessionTemplate的selectOne方法。
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
最终调用的是sqlSessionProxy的selectOne方法,这里出现了一个sqlSessionProxy属性,那么就有必要先找到这个属性是何时被实例化的,以及它的类型。
[SqlSessionTemplate源码]
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.mybatis.spring;
import ......
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType, new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
Assert.notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
}
...................................
}
跟着构造函数走,最终调用Proxy.newProxyInstance实例化sqlSessionProxy的。
this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
这句代码应该很熟悉了吧?不就是jdk提供的动态代理吗,既然有Proxy.newProxyInstance的出现就必然会有InvocationHandler的实现。
private class SqlSessionInterceptor implements InvocationHandler {
private SqlSessionInterceptor() {
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
Object unwrapped;
try {
Object result = method.invoke(sqlSession, args);
if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
unwrapped = result;
} catch (Throwable var11) {
......
throw (Throwable)unwrapped;
} finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
return unwrapped;
}
}
SqlSessionInterceptor为了容易看我去掉了一些源码,也就是说SqlSessionTemplate类中定义的类型为SqlSession的属性sqlSessionProxy是通过动态代理来创建的,而每次调用sqlSessionProxy这个实例实现SqlSession接口中的方法都会被SqlSessionInterceptor的invoke方法拦截下来。再通过SqlSessionUtils的getSqlSession获取一个SqlSession来执行目标方法,最终将执行结果返回。
SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
当函数调用完成时,最终走SqlSessionUtils.closeSqlSession方法将sqlSession关闭掉。
finally {
if (sqlSession != null) {
SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
再看mapper接口无实现类
这需要从无实现类的配置中入手。
<!-- 配置userDao,底层使用动态代理创建注入的接口类型的实现类 -->
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!-- 注入Mapper接口,也可以构造方法注入 -->
<!--<property name="mapperInterface" value="wjy.sm.dao.UserDao"/>-->
<constructor-arg index="0" value="wjy.sm.dao.UserDao"/>
<!-- 注入SqlSessionFactory -->
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
我们来回顾一下上一篇文章的知识点,因为可能很多人没看过。
这里有必要介绍一个知识点,即FactoryBean。
[FactoryBean源码]
package org.springframework.beans.factory;
public interface FactoryBean<T> {
T getObject() throws Exception;
Class<?> getObjectType();
boolean isSingleton();
}
现在回到主题来,看看userDao这个bean的配置。这个配置可以简单理解为:配置了一个类型为MapperFactoryBean的bean,id为userDao,因为MapperFactoryBean是实现FactoryBean接口的,所以取id为userDao的bean的时候[比如getBean("userDao")],取得的不是这个MapperFactoryBean实例,而是其getObject()方法返回的实例。
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
public Class<T> getObjectType() {
return this.mapperInterface;
}
public boolean isSingleton() {
return true;
}
MapperFactoryBean实现FactoryBean接口的三个接口源码。
那么我们是不是只需要关注MapperFactoryBean的getObject这个方法就行了呢?肯定是的。
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
我们需要先从MapperFactoryBean源码入手,首先是这个类是否继承了什么类,实现了什么接口。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
}
从源码看出,MapperFactoryBean不仅是实现了FactoryBean接口,还是继承自SqlSessionDaoSupport的,这不就是跟前面的内容接轨了嘛,this.getSqlSession()就是SqlSessionDaoSupport的方法,获取到SqlSession之后,调用getMapper方法获取到注入的接口(mapperInterface)的实例并返回,这是mybatis完成的工作。
好了,spring整合mybatis就分析到这里了,如有不明白的地方欢迎留言探讨,如果有说错的地方还望指正。
这是上一篇《Spring与Mybatis整合事务管理》就是事务管理配置的一点小补充。
<tx:annotation-driven/>如果不指定transaction-manager,默认会使用id设为transactionManager的事务管理器。如果不指定transaction-manager,且事务管理器的id也不为transactionManager就需要在@Transactional注解中配置transactionManager = "事务管理器的id",否则就会抛出异常。如
@Transactional(transactionManager="dataSourceTransactionManager");
<!-- 开启spring事务,依赖spring-jdbc的jar包 -->
<!-- 事务管理器 -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据源 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 开启事务注解功能 -->
<!--
如果不指定transaction-manager,默认会使用id设为transactionManager的事务管理器,
如果不指定transaction-manager,且事务管理器的id也不为transactionManager就,
就需要在@Transactional注解中配置transactionManager = "事务管理器的id",否则就会抛出异常。
@Transactional(transactionManager="dataSourceTransactionManager");
-->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
上一篇《Spring与Mybatis整合事务管理》文中的例子源码已经上传到Github了,想参考的可以复制链接到浏览器下载源码:https://github.com/wujiuye/SpringMybatisDemo
以上是关于从源码分析spring整合mybatis的主要内容,如果未能解决你的问题,请参考以下文章