问题来源

  最近在集成spring和mybatis时遇到了很多问题,从网上查了也解决了,但是就是心里有点别扭,想看看到底怎么回事,所以跟了下源码,终于发现了其中的奥妙。

问题分析

首先我们来看看基本的配置。

  spring的配置:

     <!-- 数据库配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${db.driver}"/>
<property name="url" value="${db.url}"/>
<property name="username" value="${db.userName}"/>
<property name="password" value="${db.password}"/>
<property name="maxActive" value="${druid.maxActive}"></property>
<property name="maxWait" value="${druid.maxWait}"></property>
</bean> <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 加载mybatis mapper文件的配置 -->
<property name="mapperLocations" value="classpath*:mapper/*.xml"/>
</bean>
<!-- sqlSession不是必选项 -->
<!-- <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="mSqlSessionFactory"/>
</bean> -->
<!--动态代理实现 不用写dao的实现 -->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zex.dao" />
<property name="sqlSessionFactoryBeanName" value="mSqlSessionFactory"></property>
</bean>
<!-- 事务管理 -->
<bean id="transactionManagermeta"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 事务注解支持 -->
<tx:annotation-driven/>

mapper文件和dao接口

  

controller层代码

源码跟踪

     首先我们分解下spring-mybatis配置信息,数据库配置不说了,我们来看看sqlSessionFactory的配置

 <bean id="mSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 加载mybatis mapper文件的配置 -->
<property name="mapperLocations" value="classpath*:mapper/*.xml"/>
</bean>

    这个配置,主要是把SqlSessionFactoryBean用spring管理起来了,我们一起来看看这个bean的作用

/**
* {@code FactoryBean} that creates an MyBatis {@code SqlSessionFactory}.
* This is the usual way to set up a shared MyBatis {@code SqlSessionFactory} in a Spring application context;
* the SqlSessionFactory can then be passed to MyBatis-based DAOs via dependency injection.
*
* Either {@code DataSourceTransactionManager} or {@code JtaTransactionManager} can be used for transaction
* demarcation in combination with a {@code SqlSessionFactory}. JTA should be used for transactions
* which span multiple databases or when container managed transactions (CMT) are being used.
*/

这是这个类的注释:这个类主要用来创建Mybatis需要的SqlSessionFactory,在spring的上下文共享这个类。这里可以看出这个类用来

管理mybatis的配置信息,讲mybatis的信息管理载spring中,我们看下基本属性。

  

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);

  private Resource configLocation;

  private Configuration configuration;

  private Resource[] mapperLocations;

  private DataSource dataSource;

  private TransactionFactory transactionFactory;

  private Properties configurationProperties;

  private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

  private SqlSessionFactory sqlSessionFactory;

  //EnvironmentAware requires spring 3.1
private String environment = SqlSessionFactoryBean.class.getSimpleName(); private boolean failFast; private Interceptor[] plugins; private TypeHandler<?>[] typeHandlers; private String typeHandlersPackage; private Class<?>[] typeAliases; private String typeAliasesPackage; private Class<?> typeAliasesSuperType; //issue #19. No default provider.
private DatabaseIdProvider databaseIdProvider; private Class<? extends VFS> vfs; private Cache cache; private ObjectFactory objectFactory; private ObjectWrapperFactory objectWrapperFactory;
}

这里我们看到了有个

  private Resource configLocation;

这个属性用来管理mybatis基本配置信息的xml的位置,sqlSessionFactoryBean会根据这个配置加载Configuration,当然我们也可以通过这个类中

其他的参数来配置,例如typeHandler,typeAliasesPackages等等,这些既可以在Configuration的xml中配置,也可以直接配置。所以这个bean主要作用就是生成configuration,

然后通过sqlSessionFactoryBuilder来创建sqlSessionFactory,可以说最重要的就是创建这个sqlSessionFactory。

接下来我们看看SqlSessionTemplate

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="mSqlSessionFactory"/>
</bean>
SqlSessionTemplate这个类实现了mybatis的sqlSession,mybatis的sqlSession主要是执行数据库操作,spring实现了SqlSessionTemplate这个类,主要是讲mybatis对数据库的
操作转嫁到spring中来,让spring来进行数据的操作。我们看看这个类的属性。
public class SqlSessionTemplate implements SqlSession {

  private final SqlSessionFactory sqlSessionFactory;

  private final ExecutorType executorType;

  private final SqlSession sqlSessionProxy;

  private final PersistenceExceptionTranslator exceptionTranslator;

  /**
* Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
* provided as an argument.
*
* @param sqlSessionFactory
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
} /**
* Constructs a Spring managed SqlSession with the {@code SqlSessionFactory}
* provided as an argument and the given {@code ExecutorType}
* {@code ExecutorType} cannot be changed once the {@code SqlSessionTemplate}
* is constructed.
*
* @param sqlSessionFactory
* @param executorType
*/
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
this(sqlSessionFactory, executorType,
new MyBatisExceptionTranslator(
sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
} /**
* Constructs a Spring managed {@code SqlSession} with the given
* {@code SqlSessionFactory} and {@code ExecutorType}.
* A custom {@code SQLExceptionTranslator} can be provided as an
* argument so any {@code PersistenceException} thrown by MyBatis
* can be custom translated to a {@code RuntimeException}
* The {@code SQLExceptionTranslator} can also be null and thus no
* exception translation will be done and MyBatis exceptions will be
* thrown
*
* @param sqlSessionFactory
* @param executorType
* @param exceptionTranslator
*/
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}

这个类包含有四个基本的属性,其中的SqlSessionFactory就是我们之前通过SqlSessionFactoryBean生成的那个SqlSessionFactory,他的作用是提供Configation,

另一个重要的属性就是SqlSessionProxy这个类其实是个代理类,代理的Mybatis的sqlSession接口,这样他就可以拥有MyBatis的sqlSession的所有方法了。这个代理类在执行的时候

其实是走的

SqlSessionInterceptor的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}

这个方法就是获取真实的sqlSession然后调用数据库的操作。

ok,以上就是spring和mybatis的整合点,接下来我们看看是如何只通过一个接口就能操作数据库的,肯定用的是代理模式,只是mybatis用的太好了。

首先我们来看个类,MapperFactoryBean

/**
* BeanFactory that enables injection of MyBatis mapper interfaces. It can be set up with a
* SqlSessionFactory or a pre-configured SqlSessionTemplate.
* <p>
* Sample configuration:
*
* <pre class="code">
* {@code
* <bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
* <property name="sqlSessionFactory" ref="sqlSessionFactory" />
* </bean>
*
* <bean id="oneMapper" parent="baseMapper">
* <property name="mapperInterface" value="my.package.MyMapperInterface" />
* </bean>
*
* <bean id="anotherMapper" parent="baseMapper">
* <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
* </bean>
* }
* </pre>
* <p>
* Note that this factory can only inject <em>interfaces</em>, not concrete classes.
*
* @author Eduardo Macarron
*
* @see SqlSessionTemplate
*/
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { private Class<T> mapperInterface; private boolean addToConfig = true; public MapperFactoryBean() {
//intentionally empty
}/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
} }

这个类就是可以注入mapper接口的工厂类,可以理解为他可以通过接口生产一个代理类用来调用接口的工作,首先他是个FactoryBean可以通过getObject(),获取到他管理的bean,

这个类最主要的就是传入一个sqlSessionFactory。

我们先来看看一般的用法

<bean id="baseMapper" class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" lazy-init="true">
* <property name="sqlSessionFactory" ref="sqlSessionFactory" />
* </bean>
*
* <bean id="oneMapper" parent="baseMapper">
* <property name="mapperInterface" value="my.package.MyMapperInterface" />
* </bean>
*
* <bean id="anotherMapper" parent="baseMapper">
* <property name="mapperInterface" value="my.package.MyAnotherMapperInterface" />
* </bean>

我们看到将这个类由spring管理,然后注入sqlSessionFactory,然后又用其他的类去继承它并注入接口,这样这个接口就被管理起来了,生成了代理类。我们在获取这个接口的时候得到的其实就是代理类。不过这样子有点麻烦,我们每次都要进行接口的配置,所以spring提供了org.mybatis.spring.mapper.MapperScannerConfigurer这个类来管理所有的接口了,这个类会所有所有的配置的包中的接口,然后将每个接口的定义设置好生成代理相应的信息。

 @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 {
for (BeanDefinitionHolder holder : beanDefinitions) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
} if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
} return beanDefinitions;
}

我们重点看下

 definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);
definition.getPropertyValues().add("addToConfig", this.addToConfig);

这三行代码,指定了mapper接口的类型--MapperFactoryBean,以及相应的接口信息,这样bean的定义就指定了必要的信息,当spring创建这个mapper接口对应的bean的时候就会生成相应的MapperFactoryBean类,当需要接口实例时就会调用MapperFactoryBean的getObject()方法获取相应的bean。

  @Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

这个方法就是获取mapper接口对应的代理类,这个方法给我们上了一堂如何完美利用jdk代理类的课。建议大家可以研究下。这里不是我们的重点,就不带大家研读了。

问题总结

  解决这个问题对我们有什么好处呢,首先我们可以在这个过程中更加熟悉spring的bean的创建过程,以及mybatis的代理的生成过程,以及spring-mybatis集成的相关了解,了解这个我们可以更好的写一些类去设计和mybatis更好地连接。

spring如何管理mybatis(一) ----- 动态代理接口的更多相关文章

  1. Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法

    为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者 ...

  2. Spring学习笔记之aop动态代理(3)

    Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...

  3. MyBatis学习(三)MyBatis基于动态代理方式的增删改查

    1.前言 上一期讲到MyBatis-Statement版本的增删改查.可以发现.这种代码写下来冗余的地方特别多.写一套没啥.如果涉及到多表多查询的时候就容易出现问题.故.官方推荐了一种方法.即MyBa ...

  4. Spring @Trasactionl 失效, JDK,CGLIB动态代理

    @Transaction:  http://blog.csdn.net/bao19901210/article/details/41724355 Spring上下文:  http://blog.csd ...

  5. (十二)mybatis之动态代理

    mybatis之动态代理的应用 在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我们知道了,Mybatis的使用需要用到Mapper映射文件, ...

  6. Mybatis mapper动态代理的原理详解

    在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出my ...

  7. JAVA框架 Spring 和Mybatis整合(动态代理)

    一.使用传统方式的dao的书写方式,不建议.目前采用的是动态代理的方式交给mybatis进行处理. 首先回顾下动态代理要求: 1)子配置文件的中,namespace需要是接口的全路径,id是接口的方法 ...

  8. Spring 整合Mybatis Mapper动态代理方法

    先看项目目录结构 很清爽了 最重要的Spring的核心配置文件,看一下 <?xml version="1.0" encoding="UTF-8"?> ...

  9. 新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理

    之前写了一篇文章IOC该博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到 ...

随机推荐

  1. 20135234mqy-——信息安全系统设计基础第十二周学习总结

    process environ.c environvar.c consumer.c 管道写端 producer.c 管道读端 testmf.c listargs.c pipedemo.c 管道 pip ...

  2. 20135337朱荟潼 Linux第六周学习总结——进程的描述和进程的创建

    朱荟潼 + 原创作品转载请注明出处 + <Linux内核分析>MOOC课http://mooc.study.163.com/course/USTC 1000029000 第六周 进程的描述 ...

  3. wordpress学习三:wordpress自带的模板学习

    在<学习二>里,大概说了下怎么去查找模板,本节我们以一个简单的模板为例子,继续说说wordpress的模板机制,看看做一个自己的模板需要哪些知识点. 页面模板渲染 wordpress的模板 ...

  4. Day Seven

    站立式会议 站立式会议内容总结 331 今天完成: 1.主页面 toolbar 菜单修改为点击弹出两个选项:新增计划和书籍 2.点击新增书籍跳转到文件管理器 home按钮为回退至上级目录,后退按钮为c ...

  5. 第十一周(11.24-12.01)----final评论II

    1.  Nice 项目:约跑软件 这款app非常实用.从性能上讲,这款软件基于Android开发.使用者只要注册就能实用,操作简便.在功能上,这款软件不仅为两个有意愿同时跑步的人牵线,为跑步的人提供跑 ...

  6. TestNG简单介绍以及安装—学习笔记1

    TestNG是什么 到这里,大家肯定已经运行了一些关于Selenium的测试用例了(这里主要是站着一个自动化测试工程师的角度来看待TestNG的,所以这里所说的也都是基于Web测试的验证点来说的,而不 ...

  7. PHP后台支付的开发:微信支付和支付宝支付

    关于支付的流程之类的就不做解释,大家可以自行搜索! 微信支付 项目前提:本人用的是tp框架,PHP语言下载到微信平台提供的微信支付接口文件,放在了tp第三方类库vendor,命名为WxpayAPI, ...

  8. phaser3 微信小游戏入门

    phaser与eget, laya, pixi.js本质上没什么区别. 都是渲染引擎.  其它的都是配角.  phaser的特点是.代码容易理解 功能比较全面. 个人比较喜欢phaser的地方 twe ...

  9. bzoj 2243: [SDOI2011]染色 (树链剖分+线段树 区间合并)

    2243: [SDOI2011]染色 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 9854  Solved: 3725[Submit][Status ...

  10. MySQL中使用Like模糊查询太慢

    问题:明明建立了索引,为何Like模糊查询速度还是特别慢? Like是否使用索引? 1.like %keyword    索引失效,使用全表扫描.但可以通过翻转函数+like前模糊查询+建立翻转函数索 ...