spring如何管理mybatis(一) ----- 动态代理接口
问题来源
最近在集成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(一) ----- 动态代理接口的更多相关文章
- Java EE开发平台随手记5——Mybatis动态代理接口方式的原生用法
为了说明后续的Mybatis扩展,插播一篇广告,先来简要说明一下Mybatis的一种原生用法,不过先声明:下面说的只是Mybatis的其中一种用法,如需要更深入了解Mybatis,请参考官方文档,或者 ...
- Spring学习笔记之aop动态代理(3)
Spring学习笔记之aop动态代理(3) 1.0 静态代理模式的缺点: 1.在该系统中有多少的dao就的写多少的proxy,麻烦 2.如果目标接口有方法的改动,则proxy也需要改动. Person ...
- MyBatis学习(三)MyBatis基于动态代理方式的增删改查
1.前言 上一期讲到MyBatis-Statement版本的增删改查.可以发现.这种代码写下来冗余的地方特别多.写一套没啥.如果涉及到多表多查询的时候就容易出现问题.故.官方推荐了一种方法.即MyBa ...
- Spring @Trasactionl 失效, JDK,CGLIB动态代理
@Transaction: http://blog.csdn.net/bao19901210/article/details/41724355 Spring上下文: http://blog.csd ...
- (十二)mybatis之动态代理
mybatis之动态代理的应用 在前文(https://www.cnblogs.com/NYfor2018/p/9093472.html)我们知道了,Mybatis的使用需要用到Mapper映射文件, ...
- Mybatis mapper动态代理的原理详解
在开始动态代理的原理讲解以前,我们先看一下集成mybatis以后dao层不使用动态代理以及使用动态代理的两种实现方式,通过对比我们自己实现dao层接口以及mybatis动态代理可以更加直观的展现出my ...
- JAVA框架 Spring 和Mybatis整合(动态代理)
一.使用传统方式的dao的书写方式,不建议.目前采用的是动态代理的方式交给mybatis进行处理. 首先回顾下动态代理要求: 1)子配置文件的中,namespace需要是接口的全路径,id是接口的方法 ...
- Spring 整合Mybatis Mapper动态代理方法
先看项目目录结构 很清爽了 最重要的Spring的核心配置文件,看一下 <?xml version="1.0" encoding="UTF-8"?> ...
- 新秀学习SSH(十四)——Spring集装箱AOP其原理——动态代理
之前写了一篇文章IOC该博客--<Spring容器IOC解析及简单实现>,今天再来聊聊AOP.大家都知道Spring的两大特性是IOC和AOP. IOC负责将对象动态的注入到容器,从而达到 ...
随机推荐
- wpf-典型的mvvm模式通用中小型管理系统框架0
之前就一直在想着写这么一系列博客,将前段时间(也算有点久了)自己编写的一套框架分享下,和园子里的诸位大牛交流交流,奈何文思枯竭,提键盘而无从敲起,看来只有coding时才不会有这种裤子都脱了,才发现对 ...
- Backbone.js源码浅介
终于看到一个只有一千多行的js框架了,于是抱着一定可以看懂他的逻辑的心态,查看了他的整个源码,进去之后才发现看明白怎么用容易,看懂怎么写的就难了,于是乎有了这篇博客的标题:浅介,只能粗浅的介绍下Bac ...
- OpenFlow PacketOut消息机制
OpenFlow PacketOut消息机制 前言 由于最近实验的进行,遇到一个比较棘手的问题,就是利用控制器主动发送packet消息的问题,期间遇到一些问题,后来在RYU群中得到群友左木的帮助成功解 ...
- iOS 内存管理-copy、 retain、 assign 、readonly 、 readwrite、nonatomic、@property、@synthesize、@dynamic、IB_DESIGNABLE 、 IBInspectable、IBOutletCollection
浅谈iOS内存管理机制 alloc,retain,copy,release,autorelease 1)使用@property配合@synthesize可以让编译器自动实现getter/setter方 ...
- ElasticSearch 2 (15) - 深入搜索系列之多字段搜索
ElasticSearch 2 (15) - 深入搜索系列之多字段搜索 摘要 查询很少是简单的一句话匹配(one-clause match)查询.很多时候,我们需要用相同或不同的字符串查询1个或多个字 ...
- mysql & java & spring transaction isolation level
mysql /*SESSION LEVEL*/ select @@tx_isolation; /*GLOBAL LEVEL*/ select @@global.tx_isolation; select ...
- pgm8
前面的近似策略是寻找了 energy functional 的近似,该近似导致了 LBP,这使得 message passing 的算法不变.近似使用 I-projection,尽管这个一般说来并不容 ...
- 51Nod 1199 Money out of Thin Air (树链剖分+线段树)
1199 Money out of Thin Air 题目来源: Ural 基准时间限制:1 秒 空间限制:131072 KB 分值: 80 难度:5级算法题 收藏 关注 一棵有N个节点的树,每 ...
- 前端学习 -- Css -- 盒子模式
框模型: CSS处理网页时,它认为每个元素都包含在一个不可见的盒子里. 为什么要想象成盒子呢?因为如果把所有的元素都想象成盒子,那么我们对网页的布局就相当于是摆放盒子.我们只需要将相应的盒子摆放到网页 ...
- 用rem来做响应式开发(转)
由于最近在做公司移动项目的重构,因为要实现响应式的开发,所以大量使用到了rem的单位,觉得这个单位有点意思.但是现在貌似用他的人很少.上一篇文章我分享了淘宝写的一篇rem的介绍,介绍的非常全面,但是他 ...