转: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. 懒汉式单例要加volatile吗

    private static volatile Something instance = null; public static Something getInstance() { if (insta ...

  2. mapreduce求平均数

    1. 现有某电商关于商品点击情况的数据文件,表名为goods_click,包含两个字段(商品分类,商品点击次数),分隔符“     ”,由于数据很大,所以为了方便统计我们只截取它的一部分数据,内容如下 ...

  3. Java-IO读写文件简单操作

    Java中读写文件的关键在于节点流和处理流的的选取上,而节点流和处理流又分为面向字节.面向字符两种处理模式,因此,需要根据实际的情况选择合适的处理模式,一般而言,往往尽量尝试使用面向字符的处理模式,如 ...

  4. 使用codesmith无法连接mysql问题

    最近研究codesmith的用法,遇到了如题的问题,记录一下解决的方法. 1.问题描述: 在codesmith中选择MySQLSchemaProvider并连接数据库时,会报以下错误: Test fa ...

  5. CAD安装失败怎样卸载CAD 2017?错误提示某些产品无法安装

    AUTODESK系列软件着实令人头疼,安装失败之后不能完全卸载!!!(比如maya,cad,3dsmax等).有时手动删除注册表重装之后还是会出现各种问题,每个版本的C++Runtime和.NET f ...

  6. UGUI EventSystem.current.IsPointerOverGameObject(),判断是否进入了UI上

    EventSystem.current.IsPointerOverGameObject(); //返回一个布尔值,进入了UI上就返回true,用的时候要 using UnityEngine.Event ...

  7. oracle简单命令

    1.cmd 中 sqlplus /nolog 2.SQL> conn sys/password as sysdba

  8. 修改jar包bug的方式

    第一种方式 1. 直接在项目同样的包名里面新建同样的class,会优先jar包的class加载,等同于覆盖. 第二种方式 2. 拿到第一步打包后的jar或者war,找到相应的java类的.class文 ...

  9. JDK一键安装,配置环境

    人懒,就得“多干活”!  正常安装JDK 的话,它会修改系统的path 变量,加入自己的调用路径(jre) 这样,我们刚安装好后,就可直接调用java –verbose 查看jre 安装的目录了. ( ...

  10. 修改model,映射到表中

    1. 当使用EF code first创建数据表后,数据库中会自动创建一个名为:dbo.__MigrationHistory的系统数据表, 如果尚未启用数据库迁移功能,那么每次在应用程序运行时,都会对 ...