(2)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 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-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中了。