转:http://blog.csdn.net/fqz_hacker/article/details/53485833

Mybatis-Spring

1.应用
mybatis是比较常用的数据库中间件,我们都知道我们来看看怎么在spring中使用mybatis,假设有用户表User,包含四个字段 (id,name,sex,mobile),在Spring中使用mybatis操作User表非常简单,这里使用的是mybatis-spring 1.3.0,首先定义接口,
  1. public interface UserMapper {
  2. int createUser(@Param("user") User user);
  3. }

然后,定义对应的mybatis xml文件,

  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <!--PUBLIC后面跟着的可以用于验证文档结构的 DTD 系统标识符和公共标识符。-->
  3. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  4. <mapper namespace="com.fqz.mybatis.dao.UserMapper"><!--namespace是必须的,指向对应的java interface,要把包名写全,此例中为com.fqz.mybatis.dao.UserMapper -->
  5. <resultMap id="User" type="User">
  6. <id property="id" column="id"/>
  7. <result property="name" column="name"/>
  8. <result property="sex" column="sex"/>
  9. <result property="mobile" column="mobile"/>
  10. </resultMap>
  11. <insert id="createUser" parameterType="User" useGeneratedKeys="true" keyColumn="id" keyProperty="user.id">
  12. INSERT INTO
  13. User
  14. (name,sex,mobile)
  15. VALUES
  16. (#{user.name},#{user.sex},#{user.mobile})
  17. </insert>
  18. </mapper>

紧接着,添加配置文件,命名为spring-dao.mxl,

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans.xsd
  7. http://www.springframework.org/schema/context
  8. http://www.springframework.org/schema/context/spring-context.xsd">
  9. <context:component-scan base-package="com.fqz.mybatis"/>
  10. <!-- Data Source -->
  11. <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
  12. <property name="driverClass" value="com.mysql.jdbc.Driver"/>
  13. <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/fqz"/>
  14. <property name="user" value="root"/>
  15. <property name="password" value="abcd1234"/>
  16. <!-- 当连接池中的连接耗尽的时候c3p0一次同时获取的连接数  -->
  17. <property name="acquireIncrement" value="5"></property>
  18. <!-- 初始连接池大小 -->
  19. <property name="initialPoolSize" value="10"></property>
  20. <!-- 连接池中连接最小个数 -->
  21. <property name="minPoolSize" value="5"></property>
  22. <!-- 连接池中连接最大个数 -->
  23. <property name="maxPoolSize" value="20"></property>
  24. </bean>
  25. <!-- 扫描对应的XML Mapper -->
  26. <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  27. <!-- 数据源 -->
  28. <property name="dataSource" ref="dataSource"></property>
  29. <!-- 别名,它一般对应我们的实体类所在的包,这个时候会自动取对应包中不包括包名的简单类名作为包括包名的别名。多个package之间可以用逗号或者分号等来进行分隔。 -->
  30. <property name="typeAliasesPackage" value="com.fqz.mybatis.entity"></property>
  31. <!-- sql映射文件路径,它表示我们的Mapper文件存放的位置,当我们的Mapper文件跟对应的Mapper接口处于同一位置的时候可以不用指定该属性的值。 -->
  32. <property name="mapperLocations" value="classpath*:mybatis/*.xml"></property>
  33. </bean>
  34. <!-- 扫描对应的Java Mapper -->
  35. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  36. <property name="basePackage" value="com.fqz.mybatis.dao"/>
  37. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
  38. </bean>
  39. </beans>
定义服务UserService接口和实现类,此处没给出UserService接口定义,
  1. @Service
  2. public class UserServiceImpl implements UserService {
  3. @Resource
  4. UserMapper userMapper;
  5. public int createUser(UserDTO userDTO) {
  6. User user = new User();
  7. BeanUtils.copyProperties(userDTO,user,new String[]{"id"});
  8. int row = userMapper.createUser(user);//插入返回值为作用的记录数;生成的主键已经被赋值到user对象上
  9. if(row >= 1)
  10. return user.getId();
  11. return -1;
  12. }
  13. public UserDTO getUserById(Integer id) {
  14. return null;
  15. }
  16. }

完成接口定义、mybatis xml定义和配置文件,就可以直接使用接口来操作数据库,

  1. @RunWith(SpringJUnit4ClassRunner.class)
  2. @ContextConfiguration(locations = {"classpath*:spring/spring-*.xml"})
  3. public class TestUser{
  4. private static UserService userService;
  5. @BeforeClass
  6. public static void beforeClass(){
  7. ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring/spring-dao.xml");
  8. userService = context.getBean(UserService.class);
  9. }
  10. @Test
  11. public void testCreatUser(){
  12. UserDTO userDTO = new UserDTO();
  13. userDTO.setName("your name");
  14. userDTO.setSex(true);
  15. userDTO.setMobile("12134232211");
  16. Integer userId = userService.createUser(userDTO);
  17. System.out.println(userId);
  18. System.out.println(userDTO.getId());
  19. }
  20. }
2. 原理

从UserServiceImpl实现类中可以看出,服务类直接使用的UserMapper接口来操作数据库,而UserMapper接口没有对应的实现
类,这一切都是由spring-mybatis库通过动态代理实现的,接下来分析下它的实现原理,先由SqlSessionFactoryBean生成
SQLSessionFactory和并扫描接口,为接口生成动态代理

1. SqlSessionFactoryBean配置
它的主要作用是扫描sql xml文件,查看源码,
  1. public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean

SqlSessionFactoryBean
实现了FactoryBean和InitializingBean,因此会首先执行afterPropertiesSet()方法,然后根据
getObject()方法返回的对象生产Spring
Bean,这里生产的是SqlSessionFactory类型的对象,在afterPropertiesSet方法中,执行了
buildSqlSessionFactory方法来初始化SqlSessionFactory对象,来看看其中的一段,

  1. if (!isEmpty(this.mapperLocations)) {
  2. for (Resource mapperLocation : this.mapperLocations) {
  3. if (mapperLocation == null) {
  4. continue;
  5. }
  6. try {
  7. XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
  8. configuration, mapperLocation.toString(), configuration.getSqlFragments());
  9. xmlMapperBuilder.parse();
  10. }

进入XmlMapperBuilder.parse方法,

  1. private void bindMapperForNamespace() {
  2. String namespace = builderAssistant.getCurrentNamespace();
  3. if (namespace != null) {
  4. Class<?> boundType = null;
  5. try {
  6. boundType = Resources.classForName(namespace);
  7. } catch (ClassNotFoundException e) {
  8. //ignore, bound type is not required
  9. }
  10. if (boundType != null) {
  11. if (!configuration.hasMapper(boundType)) {
  12. // Spring may not know the real resource name so we set a flag
  13. // to prevent loading again this resource from the mapper interface
  14. // look at MapperAnnotationBuilder#loadXmlResource
  15. configuration.addLoadedResource("namespace:" + namespace);
  16. configuration.addMapper(boundType);
  17. }
  18. }
  19. }
  20. }

namespace指定了mybatis dao接口的路径,通过configuration.addMapper(boundType)将接口的动态代理委托给MapperRegistry,MapperRegistry中通过MapperFactory生成动态代理,

  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  3. if (mapperProxyFactory == null) {
  4. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5. }
  6. try {
  7. return mapperProxyFactory.newInstance(sqlSession);
  8. } catch (Exception e) {
  9. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10. }
  11. }
  12. public <T> boolean hasMapper(Class<T> type) {
  13. return knownMappers.containsKey(type);
  14. }
  15. public <T> void addMapper(Class<T> type) {
  16. if (type.isInterface()) {
  17. if (hasMapper(type)) {
  18. throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
  19. }
  20. boolean loadCompleted = false;
  21. try {
  22. knownMappers.put(type, new MapperProxyFactory<T>(type));
  23. // It's important that the type is added before the parser is run
  24. // otherwise the binding may automatically be attempted by the
  25. // mapper parser. If the type is already known, it won't try.
  26. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  27. parser.parse();
  28. loadCompleted = true;
  29. } finally {
  30. if (!loadCompleted) {
  31. knownMappers.remove(type);
  32. }
  33. }
  34. }
  35. }

MapperProxyFactory中使用Proxy.newProxyInstance来生成动态代理

  1. protected T newInstance(MapperProxy<T> mapperProxy) {
  2. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  3. }
  4. public T newInstance(SqlSession sqlSession) {
  5. final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  6. return newInstance(mapperProxy);
  7. }

MapperProxy为动态代理的处理类,实际上将SqlSession操作db的过程封装在了MapperMethod类中,

  1. public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
  2. this.sqlSession = sqlSession;
  3. this.mapperInterface = mapperInterface;
  4. this.methodCache = methodCache;
  5. }
  6. @Override
  7. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  8. if (Object.class.equals(method.getDeclaringClass())) {
  9. try {
  10. return method.invoke(this, args);
  11. } catch (Throwable t) {
  12. throw ExceptionUtil.unwrapThrowable(t);
  13. }
  14. }
  15. final MapperMethod mapperMethod = cachedMapperMethod(method);
  16. return mapperMethod.execute(sqlSession, args);
  17. }

简单看下结构,

总结,SqlSessionFactoryBean实际上对应的是SqlSessionFactory类,它会扫描sql
xml文件,并对接口创建动态代理,将接口类的Class和动态代理关系保存在SqlSessionFactory中,这仅仅是完成了动态代理的生成,而
动态代理在哪里被使用到,怎么使用,这些都是由MapperScannerConfigurer完成,接下来看看
MapperScannerConfigurer都做了些什么?

2. MapperScannerConfigurer
从开始的配置文件可以看出,MapperScannerConfigurer依赖于SqlSessionFactoryBean和Mapper
接口所在package,之前也说过SqlSessionFactoryBean实际上对应的是SqlSessionFactory,它可以提供
Mapper接口的动态代理类,而Mapper所在package提供了扫描的路径,在扫描过程中,会把每个Mapper接口对应到一个
MapperFactoryBean,MapperFactoryBean实际上对应的是动态代理类,这一切也就说通了,下面来看看源码,
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,因此会在bean

factory初始化的时候,被调用到,调用的方法为postProcessBeanDefinitionRegistry,因此看看
postProcessBeanDefinitionRegistry的源码,

  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  2. if (this.processPropertyPlaceHolders) {
  3. processPropertyPlaceHolders();
  4. }
  5. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  6. scanner.setAddToConfig(this.addToConfig);
  7. scanner.setAnnotationClass(this.annotationClass);
  8. scanner.setMarkerInterface(this.markerInterface);
  9. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  10. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  11. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  12. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  13. scanner.setResourceLoader(this.applicationContext);
  14. scanner.setBeanNameGenerator(this.nameGenerator);
  15. scanner.registerFilters();
  16. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  17. }

调用了ClassPathMapperScanner的scan()方法,

  1. public int scan(String... basePackages) {
  2. int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
  3. doScan(basePackages);
  4. // Register annotation config processors, if necessary.
  5. if (this.includeAnnotationConfig) {
  6. AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  7. }
  8. return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
  9. }

scan
方法又调用了doScan方法,看看ClassPathMapperScanner的doScan方法,Spring会首先把需要实例化的bean加载的
BeanDefinitionHolder的集合中,doScan方法,就是添加mybatis
mapper接口的bean定义到BeanDefinitionHolder集合,

  1. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  3. if (beanDefinitions.isEmpty()) {
  4. logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  5. } else {
  6. processBeanDefinitions(beanDefinitions);
  7. }
  8. return beanDefinitions;
  9. }
  10. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  11. GenericBeanDefinition definition;
  12. for (BeanDefinitionHolder holder : beanDefinitions) {
  13. definition = (GenericBeanDefinition) holder.getBeanDefinition();
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
  16. + "' and '" + definition.getBeanClassName() + "' mapperInterface");
  17. }
  18. // the mapper interface is the original class of the bean
  19. // but, the actual class of the bean is MapperFactoryBean
  20. definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
  21. definition.setBeanClass(this.mapperFactoryBean.getClass());//将其bean Class类型设置为mapperFactoryBean,放入BeanDefinitions
  22. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  23. boolean explicitFactoryUsed = false;
  24. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  25. definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  26. explicitFactoryUsed = true;
  27. } else if (this.sqlSessionFactory != null) {
  28. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  29. explicitFactoryUsed = true;
  30. }
  31. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
  32. if (explicitFactoryUsed) {
  33. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  34. }
  35. definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
  36. explicitFactoryUsed = true;
  37. } else if (this.sqlSessionTemplate != null) {
  38. if (explicitFactoryUsed) {
  39. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  40. }
  41. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
  42. explicitFactoryUsed = true;
  43. }
  44. if (!explicitFactoryUsed) {
  45. if (logger.isDebugEnabled()) {
  46. logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  47. }
  48. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  49. }
  50. }
  51. }

那MapperFactoryBean究竟又做了什么呢,看源码,

  1. MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean

通过集成SqlSessionDaoSupport获得SqlSessionFactory,通过实现FactoryBean,生产动态代理对象,

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


切到这里就已经很显而易见了,Mapper接口对应的Spring
Bean实际上就是getSqlSession().getMapper(this.mapperInterface)返回的动态代理,每次装配
Mapper接口时,就相当于装配了此接口对应的动态代理,这样就顺利成章的被代理成功了。

深入理解MyBatis-Spring中间件(spring/mybatis整合)的更多相关文章

  1. Spring、Spring MVC、MyBatis整合文件配置详解

    原文  http://www.cnblogs.com/wxisme/p/4924561.html 主题 MVC模式MyBatisSpring MVC 使用SSM框架做了几个小项目了,感觉还不错是时候总 ...

  2. spring、spring mvc、mybatis框架整合基本知识

    学习了一个多月的框架知识了,这两天很想将它整合一下.网上看了很多整合案例,基本都是基于Eclipse的,但现在外面公司基本都在用Intellij IDEA了,所以结合所学知识,自己做了个总结,有不足之 ...

  3. 转载 Spring、Spring MVC、MyBatis整合文件配置详解

    Spring.Spring MVC.MyBatis整合文件配置详解   使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. ...

  4. Spring、Spring MVC、MyBatis整合文件配置详解2

    使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. Spring:http://spring.io/docs MyBatis ...

  5. Spring、Spring MVC、MyBatis 整合文件配置详解

    使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. Spring:http://spring.io/docs MyBatis ...

  6. MyBatis原理,Spring、SpringBoot整合MyBatis

    1. MyBatis概述 MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架.MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集.MyBatis 可 ...

  7. spring mvc与mybatis与maven+mysql框架整合

    <%@ page language="java" import="java.util.*" pageEncoding="utf-8"% ...

  8. Spring MVC Spring MyBatis 整合 - 快速上手

    我个人比较喜欢写注释,在工作中对注释的重要性看的也比较高,所以大部分文字都在注释中,代码外的文字会写的偏少,关键能懂就行 先看一下整合后的工程目录(单工程,多工程以后会采用maven) 5个packa ...

  9. 搭建Spring + SpringMVC + Mybatis框架之二(整合Spring和Mybatis)

    整合Spring和Mybatis 首先给出完整的项目目录: (1)引入项目需要的jar包 使用http://maven.apache.org作为中央仓库即可. Spring核心包,mybatis核心包 ...

  10. MyBatis 3 与 Spring 4 整合关键

    MyBatis 3 与 Spring 4 整合关键 MyBatis与Spring整合,首先需要一个Spring数据源.其次有两个关键,配置sqlSessionFactory时需要配置扫描sql映射xm ...

随机推荐

  1. MERGE批量增删查改数据

    MERGE优点: 在批量处理数据的时候,我可以用到merge一次完成数据处理. 示例代码一: MERGE INTO student AS t using ( AS age) s ON t.Age=s. ...

  2. Gone Fishing

    原题网址 代码已经写出来了,自己测试的时候没有问题,提交上去之后反馈了我一个Runtime error  一口老血啊! 找了半天还是没找到可能越界啊啥的地方 import java.util.Scan ...

  3. Why Nexiq 125032 USB Link Truck diagnostic tool is so helpful ?

    As for as I am concerned , Heavy Duty Diagnostic Nexiq 125032 USB is a helpful tool , which has exce ...

  4. 一些不常见的css知识

    margin-top:20%  ----------------20% 是宽度的20%

  5. Python数据可视化--matplotlib

    抽象化|具体化: 如盒形图 | 现实中的图 功能性|装饰性:没有装饰和渲染 | 包含艺术性美学上的装饰 深度表达|浅度表达:深入层次的研究探索数据 | 易于理解的,直观的表示 多维度|单一维度:数据的 ...

  6. Maven 错误 Failure to transfer ...was cached in the local repository...

    Maven 错误 Failure to transfer ...was cached in the local repository... 我解决的时候多了两步才解决 1. mvn clean ins ...

  7. RTT之内核服务函数

    一 延时函数: rt_thread_delay(t) //调用时进入系统调度. rt_kprintf()函数在kservice.c中实现,如果不使用设备驱动,则由自定义函数void rt_hw_con ...

  8. [PHP]AES加密----PHP服务端和Android客户端

    本文采取128位AES-CBC模式加密和解密 1.首先对服务端安装mcrypt: sudo apt-get install php5-mcrypt php5-dev sudo php5enmod mc ...

  9. Git 设置 Hook

    Git 设置 hook Hook 就是钩子,在需要的时候调用,根据每个钩子脚本(函数)的返回值决定下一步的操作. 在使用 Git 的过程中,有时候需要定制 Git 以便满足实际的需求. 需求 在一个项 ...

  10. c++隐式类型转换和explicit

    什么是隐式转换? 众所周知,C++的基本类型中并非完全的对立,部分数据类型之间是可以进行隐式转换的. 所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为.很多时候用户可能都不知道进行了哪些 ...