mybatis源码学习--spring+mybatis注解方式为什么mybatis的dao接口不需要实现类
相信大家在刚开始学习mybatis注解方式,或者spring+mybatis注解方式的时候,一定会有一个疑问,为什么mybatis的dao接口只需要一个接口,不需要实现类,就可以正常使用,笔者最开始的时候也会有这种疑问,当时在网上查了很多资料,也问过公司比较年长的同事,但是并没有得到答案,后来通过自己看mybatis的源码的方式才明白其中道理,接下来我就对大家分享,为什么dao接口不需要实现类的原理,这篇文章的讲解主要分为两部分:
1.mybatis注解方式是怎样通过没有实现类的dao接口进行数据库操作
2.spring+mybatis注解方式是怎样在没有实现类的dao接口的情况下结合的
文章的结构是通过 总结+详细讲解 的方式来进行说明的,希望大家能和我一同进步,例子程序放在github上了,mybatis-demo。
环境:
mybatis 3.2.7
mybatis-spring 1.2.2
spring 4.1.6
总结:
1.mybatis注解方式通过没有实现类的dao接口进行数据库操作的原理,一句话概括,就是jdk proxy,就是jdk代理
2)功能上:可以看出mybatis中的接口就是XML文件的描述,一方面这样做的目的是和spring集成,将接口交给spring管理;另一方面是为了更加方便的管理XML文件(使用接口的package+interface作为namespace,method作为ID)
2.spring+mybatis注解方式,也是没有实现类的,但是spring会默认返回MapperFactoryBean对象作为实现类的替换,但是这个只是被spring使用的,mybatis本身还是通过jdk代理来运行的。
详细讲解:
1.mybatis注解方式是怎样通过没有实现类的dao接口进行数据库操作
/**
*
* 类UserMapper.java的实现描述:TODO 类实现描述
* @author yuezhihua 2015年7月9日 上午11:18:30
*/
public interface UserMapper { /**
* 根据用户id查询用户角色
* @param userId
* @return
*/
@Select("select * from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")
public List<RolePO> getRolesByUserId(@Param("userId")Integer userId); /**
* 根据用户id查询用户角色名
* @param userId
* @return
*/
@Select("select a.role_name from role_main a INNER JOIN user_role b ON a.id = b.role_id WHERE b.user_id=#{userId}")
public Set<String> getRoleNamesByUserId(@Param("userId")Integer userId); /**
* 根据userid查询用户的所有权限
* @param userId
* @return
*/
@Select("SELECT a.permission_name FROM permission_main a INNER JOIN role_permission b ON a.id=b.permission_id WHERE b.role_id IN (SELECT d.role_id from user_main c INNER JOIN user_role d ON c.id = d.user_id WHERE c.id=#{userId})")
public Set<String> getPermissionsByUserId(@Param("userId")Integer userId); /**
* 通过用户名查询用户信息
* @param username
* @return
*/
@Select("select * from user_main where username=#{username}")
@Results({
@Result(property = "roleNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getRoleNamesByUserId")),
@Result(property = "permissionNames", column = "id", many = @Many(fetchType=FetchType.LAZY,select = "getPermissionsByUserId"))
})
public UserPO getUserByUsername(@Param("username")String username); @Select("select username from user_main")
public List<String> getRoleMain(); }
测试用例:
/**
*
* 类SqlTemplateTest.java的实现描述:TODO 类实现描述
* @author yuezhihua 2015年7月29日 下午2:07:44
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:spring/demo-locator.xml"
})
public class SqlTemplateTest { @Autowired
private SqlSessionTemplate sqlSessionTemplate; @Autowired
private SqlSessionFactory sqlSessionFactory; /**
* 初始化datasource
*/
@Before
public void init(){
DataSource ds = null;
try {
ds = BaseDataTest.createUnpooledDataSource(BaseDataTest.DERBY_PROPERTIES);
BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-schema.sql");
BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-dataload.sql");
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} /**
* 测试mybatis自身的查询
*/
@Test
public void testMybatisSelect(){
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
UserPO userPo = mapper.getUserByUsername("zhangsan");
System.out.println("-----testMybatisSelect:"+userPo.getUsername());
} /**
* mybatis-spring : sqlSessionTemplate测试查询
* java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession不要在意
*/
@Test
public void testSelect(){
UserPO result = sqlSessionTemplate.selectOne("com.mybatis.demo.lazyload.mapper.UserMapper.getUserByUsername", "zhangsan");
System.out.println(result);
} }
笔者这里不是纯使用mybatis,而是使用mybatis+spring,讲解第一部分的时候,我还是会用带有spring的方式来给大家讲解,大家注重看原理就好
第一部分的时候会用到测试用例;testMybatisSelect
大家可以看到,测试用例里边获取dao接口的方法时session.getMapper(UserMapper.class);那咱们就看看sqlsession是怎么样获取usermapper接口的,返回的这个usermaper接口又有什么改变
获取usermapper接口代理对象的时序图
返回的Usermapper编程了jdk代理对象,org.apache.ibatis.binding.MapperProxy@7e276594
虽然这里打印信息显示貌似mapperproxy是usermapper的实现类,但是笔者认为,mapperproxy不能算是usermapper的实现类,因为笔者觉得实现类的概念是应该实现usermapper接口的,但是mapperproxy不是,mapperproxy只是usermapper执行方法之前的一个拦截器
所以session.getMapper(UserMapper.class)返回的其实是usermapper的代理对象,而且usermapper中定义的方法执行都是通过mapperproxy的invoke方法代理执行的,
接下来我们看看mapper.getUserByUsername("zhangsan");这行代码的执行过程,通过usermapper一个方法的执行来讲解mybatis是怎么通过dao接口执行数据库操作的
mapperproxy.invoke(相当于一个拦截器):
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);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
这个方法中,首先会过滤object中的通用方法,遇到object方法会直接执行
但是如果是非通用方法,就会调用mappermethod.execute来代理执行方法,
mappermethod.execute
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这个execute方法会根据不同的注解@select,@update,@delete,@insert来分配不同的执行sql环境,进行操作数据库,其实这四个操作可以分为两类,一类是更新类型,返回更新的行数;一类是查询类型,返回查询的结果,这两部分的内部原理我会在其他的文章中进行详细解释。
所以,可以总结说,mybatis执行jdk代理的dao接口方法,跳转到mappermethod,execute方法来执行具体的数据库操作,并且返回结果;而且通过jdk代理的方法返回的代理对象,让人感觉和原接口对象一样,造成使用没有实现类的接口来执行的感觉
第二部分:
spring+mybatis注解方式是怎样在没有实现类的dao接口的情况下结合的
咱们先看一下spring是怎么管理mybatis的dao接口的吧。
我画了一个流程图
配置文件扫描所有mybatis的dao接口:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mybatis.demo.*.mapper" />
<!-- 这里要用传beanName,不能传bean的ref,否则,会提前加载,用不到PropertyPlaceholder,切记 -->
<property name="sqlSessionFactoryBeanName" value="demo_sqlSessionFactory" />
</bean>
ClasspathMapperScanner.doScan
/**
* Calls the parent search that will search and register all the candidates.
* Then the registered objects are post processed to set them as
* MapperFactoryBeans
*/
@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
<span style="color:#ff6666;">definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
definition.setBeanClass(MapperFactoryBean.class);</span> 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;
}
大家注意看红色部分,红色部分的意思就是对于mybatis的dao接口,spring是以MapperFactoryBean的方式来管理的,举个例子说
@autowired
private UserMapper userMapper;
这个userMapper返回的实例对象会是MapperFactoryBean,这个过程是由spring控制的,因为笔者对于spring原理没有深入研究过,笔者在这里不做说明。
可能大家好奇,为什么这里不能直接像第一部分一样,通过sqlsession.getMapper(...)的方式来获取dao接口对象呢,笔者在这里觉得,之所以出现MapperFactoryBean
这个中间对象,是因为SqlSessionTemplate,sqlsessionTemplate是mybatis-spring封装的用于方法执行mybatis方法的工具类,但是大家平时可能很少用到这个,
笔者在这里做了一个小测试利用,简单的看一下它的用法:
/**
*
* 类SqlTemplateTest.java的实现描述:TODO 类实现描述
* @author yuezhihua 2015年7月29日 下午2:07:44
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath*:spring/demo-locator.xml"
})
public class SqlTemplateTest { @Autowired
private SqlSessionTemplate sqlSessionTemplate; @Autowired
private SqlSessionFactory sqlSessionFactory; /**
* 初始化datasource
*/
@Before
public void init(){
DataSource ds = null;
try {
ds = BaseDataTest.createUnpooledDataSource(BaseDataTest.DERBY_PROPERTIES);
BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-schema.sql");
BaseDataTest.runScript(ds, "com/mybatis/demo/databases/lazyloader/lazyloader-dataload.sql");
} catch (IOException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
} /**
* 测试mybatis自身的查询
*/
@Test
public void testMybatisSelect(){
SqlSession session = sqlSessionFactory.openSession();
UserMapper mapper = session.getMapper(UserMapper.class);
System.out.println("jdk proxy mapper : "+mapper);
UserPO userPo = mapper.getUserByUsername("zhangsan");
System.out.println("-----testMybatisSelect:"+userPo.getUsername());
} /**
* mybatis-spring : sqlSessionTemplate测试查询
* java.lang.UnsupportedOperationException: Manual close is not allowed over a Spring managed SqlSession不要在意
*/
@Test
public void testSelect(){
UserPO result = sqlSessionTemplate.selectOne("com.mybatis.demo.lazyload.mapper.UserMapper.getUserByUsername", "zhangsan");
System.out.println(result);
} }
大家看上面testSelect这个测试用例,可以看到sqlsessiontemplate的基本使用方法
spring+mybatis注解方式获取dao接口对象的方法;:
MapperFactoryBean.getObject
/**
* {@inheritDoc}
*/
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这里边的getSqlSession其实就是sqlsessiontemplate
总结:对于第一部分可以说是返回了mybatis的dao接口的jdk代理对象,通过mapperproxy这个类似于拦截器一样的类跳转执行sql的,可以说是原生dao接口的一层代理对象;
那么对于第二部分来说,确实有三层代理对象:
所以,咱们在spring中使用
@autowired
private UserMapper userMapper;
来注入对象的时候,其实是经历了 cglib --> mapperfactorybean --> sqlsessiontemplate --> mapperproxy --> 原生dao接口 的包装过程,才获取的
所以咱们在使用spring来调用没有实现类的mybatis的dao接口的时候,并不是像看起来那么简单,而是经过多层代理包装的一个代理对象,对方法的执行也跳转到mybatis框架中的mappermethod中了
mybatis源码学习--spring+mybatis注解方式为什么mybatis的dao接口不需要实现类的更多相关文章
- mybatis源码学习(一) 原生mybatis源码学习
最近这一周,主要在学习mybatis相关的源码,所以记录一下吧,算是一点学习心得 个人觉得,mybatis的源码,大致可以分为两部分,一是原生的mybatis,二是和spring整合之后的mybati ...
- mybatis源码学习:一级缓存和二级缓存分析
目录 零.一级缓存和二级缓存的流程 一级缓存总结 二级缓存总结 一.缓存接口Cache及其实现类 二.cache标签解析源码 三.CacheKey缓存项的key 四.二级缓存TransactionCa ...
- mybatis源码学习:插件定义+执行流程责任链
目录 一.自定义插件流程 二.测试插件 三.源码分析 1.inteceptor在Configuration中的注册 2.基于责任链的设计模式 3.基于动态代理的plugin 4.拦截方法的interc ...
- Mybatis源码学习第六天(核心流程分析)之Executor分析
今Executor这个类,Mybatis虽然表面是SqlSession做的增删改查,其实底层统一调用的是Executor这个接口 在这里贴一下Mybatis查询体系结构图 Executor组件分析 E ...
- mybatis源码学习:基于动态代理实现查询全过程
前文传送门: mybatis源码学习:从SqlSessionFactory到代理对象的生成 mybatis源码学习:一级缓存和二级缓存分析 下面这条语句,将会调用代理对象的方法,并执行查询过程,我们一 ...
- Spring mybatis源码学习指引目录
前言: 分析了很多方面的mybatis的源码以及与spring结合的源码,但是难免出现错综的现象,为了使源码陶冶更为有序化.清晰化,特作此随笔归纳下分析过的内容.博主也为mybatis官方提供过pul ...
- Mybatis源码学习之整体架构(一)
简述 关于ORM的定义,我们引用了一下百度百科给出的定义,总体来说ORM就是提供给开发人员API,方便操作关系型数据库的,封装了对数据库操作的过程,同时提供对象与数据之间的映射功能,解放了开发人员对访 ...
- Mybatis源码学习第八天(总结)
源码学习到这里就要结束了; 来总结一下吧 Mybatis的总体架构 这次源码学习我们,学习了重点的模块,在这里我想说一句,源码的学习不是要所有的都学,一行一行的去学,这是错误的,我们只需要学习核心,专 ...
- mybatis源码学习(三)-一级缓存二级缓存
本文主要是个人学习mybatis缓存的学习笔记,主要有以下几个知识点 1.一级缓存配置信息 2.一级缓存源码学习笔记 3.二级缓存配置信息 4.二级缓存源码 5.一级缓存.二级缓存总结 1.一级缓存配 ...
随机推荐
- Python的控制语句
1. 控制语句 控制语句是用来改变程序执行的顺序.程序利用控制语句有条件地执行语句,循环地执行语句或者跳转到程序中的其他部分执行语句. Python支持三种不同的控制语句:if,for和while, ...
- CentOS7脱机安装SQL Server 2017
SQL Server on Linux也发布一段时间了,官方上支持Red Hat, SUSE, Ubuntu.手上没有以上Linux版本,选用了与Red Hat最接近的CentOS7.4来进行安装和测 ...
- JavaWeb学习总结(二)——Tomcat服务器学习和使用(一)(转)
转载自 http://www.cnblogs.com/xdp-gacl/p/3734395.html 一.Tomcat服务器端口的配置 Tomcat的所有配置都放在conf文件夹之中,里面的serve ...
- 基于C++11的线程池
1.封装的线程对象 class task : public std::tr1::enable_shared_from_this<task> { public: task():exit_(f ...
- Spark Shuffle模块——Suffle Read过程分析
在阅读本文之前.请先阅读Spark Sort Based Shuffle内存分析 Spark Shuffle Read调用栈例如以下: 1. org.apache.spark.rdd.Shuffled ...
- [Android] AutoCompleteTextView:自己主动完毕输入内容的控件(自己主动补全)
AutoCompleteTextView是EditText的直接子类,与普通EditText的最大不同就是.在用户输入的过程中,能够列出可供选择的输入项.方便使用者. AutoCompleteText ...
- 自己定义定时器(Timer)
近期做项目的时候,用到了java.util.Timer定时器类.也初步使用了,个人感觉不错.只是,在某些方面Timer类无法满足项目的需求.比方,在使用Timer时,调用schedule()方法之后( ...
- IDEA 初始配置教程
IDEA 初始配置教程 如果你是第一次使用 IDEA,或者对 IDEA 常用配置仍然不熟悉,那么本文就特别适合你. 本文只是根据我自己的使用经验来进行配置,不一定适合所有的情况,但是对你肯定会有帮助. ...
- 微信小程序教学第二章:小程序中级实战教程之预备篇 - 项目结构设计 |基于最新版1.0开发者工具
iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...
- 通过自动回复机器人学Mybatis 笔记:接口式编程
[接口式编程]尚未遇见Spring --> 代码量反而增加 1.增加约定,减少犯错的可能(不用直接去写字符串 修改点1:命名空间 修改点2:增加接口,方法名与配置文件中的id对应 package ...