博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis - DAO接口不需要实现类分析
阅读量:1996 次
发布时间:2019-04-27

本文共 12633 字,大约阅读时间需要 42 分钟。

相信大家在刚开始学习mybatis注解方式,或者spring+mybatis注解方式的时候,一定会有一个疑问,为什么mybatis的dao接口只需要一个接口,不需要实现类,就可以正常使用,笔者最开始的时候也会有这种疑问,当时在网上查了很多资料,也问过公司比较年长的同事,但是并没有得到答案,后来通过自己看mybatis的源码的方式才明白其中道理,接下来我就对大家分享,为什么dao接口不需要实现类的原理,这篇文章的讲解主要分为两部分:

1. mybatis注解方式是怎样通过没有实现类的dao接口进行数据库操作

2. spring+mybatis注解方式是怎样在没有实现类的dao接口的情况下结合

一、结论

  1. mybatis注解方式通过没有实现类的dao接口进行数据库操作的原理,一句话概括,就是jdk proxy,就是jdk代理
    1)原理上:JDK动态动态代理的原理是根据 InvocationHandler 中的invoke()方法,由jdk为你的接口手动生成了一个实现了对应接口的类,因此,你的接口可以调用,这是理解mybatis接口没有实现类能被调用的关键。
    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
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
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
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
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,而是使用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代理的方法返回的代理对象,让人感觉和原接口对象一样,造成使用没有实现类的接口来执行的感觉。

 

(2)spring+mybatis注解方式是怎样在没有实现类的dao接口的情况下结合

先看一下spring是怎么管理mybatis的dao接口

配置文件扫描所有mybatis的dao接口:

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
doScan(String... basePackages) { Set
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; }

意思就是对于mybatis的dao接口,spring是以MapperFactoryBean的方式来管理的,举个例子说:

@autowiredprivate 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-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中使用

@autowiredprivate UserMapper userMapper;

来注入对象的时候,其实是经历了 cglib --> mapperfactorybean  --> sqlsessiontemplate  --> mapperproxy  -->  原生dao接口  的包装过程,才获取的。

所以咱们在使用spring来调用没有实现类的mybatis的dao接口的时候,并不是像看起来那么简单,而是经过多层代理包装的一个代理对象,对方法的执行也跳转到mybatis框架中的mappermethod中了。

 

转自:

转载地址:http://toktf.baihongyu.com/

你可能感兴趣的文章
DOM(总结)
查看>>
JS高级总结
查看>>
JS判断一个数据的数据类型
查看>>
MySQL学习总结(一)
查看>>
MySQL学习总结(二)
查看>>
MySQL学习总结(三)
查看>>
未来已至,5G加持下的云游戏将走向何方?
查看>>
Mysql常用删除方式比较
查看>>
备案问题汇总
查看>>
“头号玩家”距离我们究竟有多远?
查看>>
首批!金山云获得可信金融云解决方案认证
查看>>
金山云张斯聪:探索沉浸式解决方案 提升教育云上体验
查看>>
高防弹性IP 金山云荣获“可信云技术最佳实践--网络安全”奖
查看>>
高清画质随心评测!金山云魔镜平台手机端正式发布
查看>>
第六期 | 金山云2020年H1云服务全新升级之「大数据产品」
查看>>
金山云广东总部落户梅州,猴赛雷!
查看>>
金山云区块链:“上云上链、用数赋智” 是数字经济完整的乐章
查看>>
第七期 | 金山云2020年H1云服务全新升级之「云数据库产品」
查看>>
一图看懂金山云2020年Q2财报:营收15.35亿元,同比增长64.1%
查看>>
数脉链与金山云达成战略合作 携手打造综合型大数据服务生态
查看>>