`
wen866595
  • 浏览: 268874 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

MyBatis Mapper 代理创建过程

阅读更多

本文主要关注与 SpringBoot 集成时的初始化过程。

1. 核心组件

  • Configuration:MyBatis所有的配置信息都保存在Configuration对象之中,配置文件中的大部分配置都会存储到该类中。
  • SqlSession:MyBatis 的顶层 API,表示与数据库交互的会话,完成数据库增删改查操作。
  • Executor:执行器是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓存的维护。
  • StatementHandler:封装了 JDBC Statement 操作,如设置参数等。
  • ParameterHandler:负责将用户传递的参数转换成 JDBC Statement 所对应的数据类型。
  • ResultSetHandler:负责将 JDBC 返回的 ResultSet 结果集转换成 List 类型的集合。
  • TypeHandler:负责将 Java 数据类型和 jdbc 数据类型之间的映射与转换。

  • MapperFactoryBean:与 Spring 集成时表示一个 Mapper 原型的工厂 bean,用以创建最终的代理。
  • MapperProxy:与一个 mapperInterface 对应,维护了 Map<Method, MapperMethod> 映射。
  • MapperAnnotationBuilder:基于注解驱动的 MappedStatement 解析器,解析接口类的每个方法,封装成 MappedStatement。
  • MappedStatement:维护了一条 <select|update|delete|insert> 节点的封装。
  • SqlSource:负责根据用户传递的 parameterObject 动态生成 SQL 语句,将信息封装成 BoundSql 对象。
  • BoundSql:表示动态生成的 SQL 语句以及相应的参数信息。

整体架构如下图:
mybatis-整体架构

 

2. 与 SpringBoot 集成的初始化

2.1 概览

SpringBoot 自动组装 MyBatis 的属性(前缀为 “mybatis”)配置成 MybatisProperties 对象。

SpringBoot 的自动配置机制会解析配置类 MybatisAutoConfiguration,其内部方法定义了两个 Bean 。

@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {...}

@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {...}

sqlSessionFactory(dataSource) 方法里利用 MybatisProperties 初始化 SqlSessionFactoryBean,通过 SqlSessionFactoryBean.buildSqlSessionFactory() 来创建 Configuration,该方法的主要逻辑是创建并配置 Configuration,然后用 Configuration 创建 SqlSessionFactory

再用 SqlSessionFactory 创建 SqlSessionTemplate

SpringBoot 会解析处理配置类上的 @MapperScan("package.to.scan") 注解,调用该注解上的导入类 MapperScannerRegistrarMapperScannerRegistrar.registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 方法创建一个 ClassPathMapperScanner,利用 @MapperScan 上的信息初始化这个扫描器,以限定扫描的接口范围,对于扫描到的每个 bean 定义,设置其 beanClassMapperFactoryBean,如果有指定 sqlSessionFactory/sqlSessionTemplate bean 的引用则直接使用,否则采用按类型自动注入。

ClassPathMapperScanner 默认扫描所有的接口并进行注册。

2.2. SqlSessionTemplate

SqlSessionTemplate 是线程安全、Spring 管理的 SqlSession,可以注入到其他 Spring 管理的 bean 里去使用。与 Spring 事务管理机制一起工作以保证实际使用的 SqlSession 是与当前的 Spring 事务关联的。另外,它管理着会话的生命周期,包括关闭、提交或回滚会话,基于 Spring 的事务配置。

该模板默认使用 MyBatisExceptionTranslator 把 MyBatis PersistenceExceptions 转换为不受检查的异常 DataAccessExceptions

因为 SqlSessionTemplate 是线程安全的,一个实例可以被所有的 DAOs 共享,这样也可以节省点内存。

public class SqlSessionTemplate implements SqlSession, DisposableBean {
    // 用于创建 SqlSession
    private final SqlSessionFactory sqlSessionFactory;

    // 执行器的类型,默认取 sqlSessionFactory 里的
    private final ExecutorType executorType;

    // 拦截对 SqlSession 接口里声明的方法的调用,以便与 Spring 事务管理集成
    private final SqlSession sqlSessionProxy;

    // 异常转换器
    private final PersistenceExceptionTranslator exceptionTranslator;
}

2.3 Configuration 的构建过程

下面是创建 Configuration 对象的过程:

// SqlSessionFactoryBean
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
    Configuration configuration;

    XMLConfigBuilder xmlConfigBuilder = null;
    if (this.configuration != null) {
        configuration = this.configuration;
        if (configuration.getVariables() == null) {
            configuration.setVariables(this.configurationProperties);
        } else if (this.configurationProperties != null) {
            configuration.getVariables().putAll(this.configurationProperties);
        }
    } else if (this.configLocation != null) {
        xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
        configuration = xmlConfigBuilder.getConfiguration();
    } else {
        configuration = new Configuration();
        configuration.setVariables(this.configurationProperties);
    }

    if (this.objectFactory != null) {
        configuration.setObjectFactory(this.objectFactory);
    }

    if (this.objectWrapperFactory != null) {
        configuration.setObjectWrapperFactory(this.objectWrapperFactory);
    }

    if (this.vfs != null) {
        configuration.setVfsImpl(this.vfs);
    }

    if (hasLength(this.typeAliasesPackage)) {
        String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
        ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeAliasPackageArray) {
            configuration.getTypeAliasRegistry().registerAliases(packageToScan,
                typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
        }
    }

    if (!isEmpty(this.typeAliases)) {
        for (Class<?> typeAlias : this.typeAliases) {
            configuration.getTypeAliasRegistry().registerAlias(typeAlias);
        }
    }

    if (!isEmpty(this.plugins)) {
        for (Interceptor plugin : this.plugins) {
            configuration.addInterceptor(plugin);
        }
    }

    if (hasLength(this.typeHandlersPackage)) {
        String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
        for (String packageToScan : typeHandlersPackageArray) {
            configuration.getTypeHandlerRegistry().register(packageToScan);
        }
    }

    if (!isEmpty(this.typeHandlers)) {
        for (TypeHandler<?> typeHandler : this.typeHandlers) {
            configuration.getTypeHandlerRegistry().register(typeHandler);
        }
    }

    if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
        try {
            configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
        } catch (SQLException e) {
            throw new NestedIOException("Failed getting a databaseId", e);
        }
    }

    if (this.cache != null) {
        configuration.addCache(this.cache);
    }

    if (xmlConfigBuilder != null) {
        try {
            xmlConfigBuilder.parse();
        } catch (Exception ex) {
            throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
        } finally {
            ErrorContext.instance().reset();
        }
    }

    if (this.transactionFactory == null) {
        // 设置事务工厂为 SpringManagedTransactionFactory,以便与 Spring 管理的事务进行协作
        this.transactionFactory = new SpringManagedTransactionFactory();
    }

    configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

    // 适用于 Mapper 的 XML 文件集中存放的场景
    if (!isEmpty(this.mapperLocations)) {
        // 如果指定了 Mapper 存放的路径则解析
        for (Resource mapperLocation : this.mapperLocations) {
            if (mapperLocation == null) {
                continue;
            }

            try {
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                configuration, mapperLocation.toString(), configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
                throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

    return this.sqlSessionFactoryBuilder.build(configuration);
}

// SqlSessionFactoryBuilder
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

2.4 Mapper 定义扫描

Mapper 的扫描一是可以通过属性 mybatis.mapperLocations 来指定其存放路径,也可以通过 @MapperScan("package.to.scan") 注解来配置。

该注解扫描指定包下的所有接口、并为其生成代理注册到 Spring 的容器里。

MapperScannerRegistrar@MapperScan 注解获取扫描配置,用以初始化 ClassPathMapperScanner 并完成最终的扫描工作。

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 {
        processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
        definition = (GenericBeanDefinition) holder.getBeanDefinition();

        definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
        // 设置bean定义的类为 MapperFactoryBean
        definition.setBeanClass(this.mapperFactoryBean.getClass());

        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) {
            // 如果没有显式设置 sqlSessionTemplate 或 sqlSessionFactory 则按类型注入
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        }
    }
}

上述代码最重要的是 设置 bean 定义的类为 MapperFactoryBean

2.5 Mapper 实例化

Mapper 的 Bean 定义被扫描后会注册到 Spring 容器里,再由 Spring 实例化时调用 MapperFactoryBean.getObject() 方法得到该类型的 Bean 实例,注册到容器里,供应用使用。

public abstract class SqlSessionDaoSupport extends DaoSupport {
    private SqlSession sqlSession;
    private boolean externalSqlSession;

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        if (!this.externalSqlSession) {
          this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
        }
    }

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSession = sqlSessionTemplate;
        this.externalSqlSession = true;
    }

    public SqlSession getSqlSession() {
        return this.sqlSession;
    }

    // 省略其他
}

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
    // 该方法在父类的 afterPropertiesSet 里调用
    // 用于把接口类型注册到 Configuration 里。供后面创建代理时使用
    protected void checkDaoConfig() {
        super.checkDaoConfig();

        notNull(this.mapperInterface, "Property 'mapperInterface' is required");

        Configuration configuration = getSqlSession().getConfiguration();
        if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
            try {
                configuration.addMapper(this.mapperInterface);
            } catch (Exception e) {
                logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
                throw new IllegalArgumentException(e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }

    public T getObject() throws Exception {
        return getSqlSession().getMapper(this.mapperInterface);
    }

    // 省略其他
}

SqlSessionDaoSupport 的主要作用是把 sqlSession 设置为 SqlSessionTemplateDaoSupport 提供了属性设置后回调的初始化钩子,触发调用 MapperFactoryBean.checkDaoConfig 方法。

下面的代理的创建调用链:

// SqlSessionTemplate
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

// Configuration
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

// MapperRegistry
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

// MapperProxyFactory
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

最终创建了一个 JDK 的动态代理,代理的方法处理器为 MapperProxy ,其持有的 SqlSession 仍然是 SqlSessionTemplate 的实例。


欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。

 

分享到:
评论

相关推荐

    MyBatis Mapper代理方式

    MyBatis Mapper代理方式 MyBatis 是一个流行的持久层框架,它提供了多种方式来实现数据的 CRUD 操作,其中Mapper 代理方式是其中的一种。Mapper 代理方式是指通过创建一个 Mapper 接口,并在该接口中定义了各种数据...

    Mybatis Mapper的使用

    Mybatis Mapper通过动态代理机制,使得我们可以直接在XML映射文件中定义SQL,然后在接口中声明对应的方法,系统会在运行时自动生成实现类,这样就消除了对实现类的直接依赖。 下面我们将详细探讨Mybatis Mapper的几...

    Mybatis的Mapper方式整合elasticsearch的DSL调用,基于接口和代理生成bean注入的方式进行调用

    本篇文章将详细介绍如何将Mybatis的Mapper方式与Elasticsearch的DSL(Domain Specific Language)查询相结合,以及如何通过CGlib实现动态代理来优化这一过程。 首先,让我们理解什么是Mybatis的Mapper方式。Mybatis...

    spring和mybatis整合(mapper代理自动扫描方式实现)

    本文将详细介绍如何将Spring和MyBatis进行整合,并采用Mapper代理自动扫描的方式实现,同时还会提及到JUnit测试。 首先,我们需要在项目中引入Spring和MyBatis的相关依赖。通常,我们会使用Maven或Gradle作为构建...

    mybatis开发dao之mapper代理方式

    在Mapper代理方式下,我们无需手动创建DAO实现类,只需定义接口,MyBatis会自动生成对应的动态代理实现。 首先,我们创建一个`Mapper`接口,这个接口通常包含对应数据库表的操作方法,如增删查改。例如,我们可以...

    Mybatis框架+Mapper代理

    ### Mybatis框架+Mapper代理 #### 一、Mapper代理开发详解 **Mybatis** 是一款优秀的持久层框架,它支持自定义SQL、存储过程以及高级映射。**Mapper代理模式**是Mybatis提供的一个非常方便的功能,允许我们通过...

    Mybatis mapper接口动态代理开发步骤解析

    Mybatis mapper接口动态代理开发步骤解析 Mybatis 是一个流行的Java持久层框架,它提供了一个灵活的方式来访问数据库。其中,Mapper 接口动态代理是 Mybatis 的一个重要特性,它允许开发者使用接口来定义数据库操作...

    (代码)SpringCloud第03讲:整合MyBatis通用Mapper

    在本课程中,我们将深入探讨如何在Spring Cloud项目中整合MyBatis通用Mapper,以便实现高效、便捷的数据访问。Spring Cloud作为一个微服务架构的集合,提供了丰富的工具和服务,帮助开发者构建分布式系统。而MyBatis...

    mybatis增删改查小例子(包括Dao开发和Mapper代理开发)

    在本示例中,我们将探讨如何使用MyBatis进行基本的增删改查操作,包括Dao开发和Mapper代理开发,同时配合MySQL数据库的使用。 **1. MyBatis概述** MyBatis是一个轻量级的ORM(Object-Relational Mapping)框架,它的...

    Mybatis_SpringMapper

    Spring整合Mybatis后,可以使用MapperScannerConfigurer扫描指定包下的Mapper接口,自动创建并注入Mapper代理对象。 5. **事务管理**:在Spring中,我们可以使用PlatformTransactionManager接口来管理事务。当整合...

    Mybatis Mapper接口工作原理实例解析

    "Mybatis Mapper接口工作原理实例解析" ...Mybatis 的 Mapper 接口工作原理是基于代理模式和动态代理机制的,它可以自动创建实现类,并将我们的 Mapper 接口转换为一个可执行的对象,从而完成具体的 CRUD 方法调用。

    mybatis_spring(mapper代理开发方法的整合整合项目)

    Mapper代理的实现原理是Spring为每个Mapper接口创建一个代理对象,这个代理对象在执行方法时,会调用MyBatis的SqlSession执行相应的SQL语句。 1. **Mapper接口设计:** Mapper接口通常包含对应SQL语句的方法,这些...

    mybatis 通用mapper

    如果使用Spring框架,需要增加配置让Spring自动扫描包含继承了自定义Mapper接口的所有接口类,并为它们生成代理对象。配置示例如下: ```xml **.dao"/&gt; ``` 5. **修改(或生成)实体类** 对实体类进行适当的...

    mybatis动态代理

    当创建Mapper接口的代理对象时,MyBatis会创建一个实现了InvocationHandler接口的内部类,这个内部类负责处理接口方法的调用,包括解析SQL,设置参数,执行SQL,以及处理结果映射等任务。通过Proxy.newProxyInstance...

    SpringMVC + Mybatis整合Mapper方式

    下面我们将深入探讨SpringMVC与Mybatis整合过程中使用Mapper方式的细节。 首先,我们需要了解SpringMVC的运行机制。SpringMVC通过DispatcherServlet接收HTTP请求,然后根据配置的HandlerMapping找到对应的...

    Spring集成MyBatis 通用Mapper以及 pagehelper分页插件

    通用Mapper通过动态代理技术实现了方法的自动映射,只需要定义Mapper接口,无需编写XML,即可实现对数据库的基本操作。 PageHelper是另一个MyBatis的分页插件,它提供了强大的分页功能,可以无缝地与MyBatis和...

    MyBatis动态代理

    MyBatis动态代理是MyBatis框架中的一个重要特性,它主要负责在运行时为Mapper接口创建代理对象,以便实现SQL的动态执行。MyBatis通过Java的反射和JDK动态代理技术来实现这一功能,使得我们可以在不编写任何具体DAO...

    MyBatis SQL mapper framework for Java.zip

    4. **Mapper代理**: 当调用Mapper接口的方法时,MyBatis会生成一个代理对象,这个代理对象会解析XML中的SQL并执行,然后返回结果。 5. **映射器注解**: 除了XML配置,MyBatis也支持使用注解来定义SQL映射。在Mapper...

    Mybatis代理开发

    Mybatis通过动态代理(JDK Proxy或CGLIB)创建Mapper接口的实现类,这个实现类在运行时动态生成,包含了执行SQL的方法。当我们调用Mapper接口的方法时,实际上是在调用这个动态生成的实现类的方法,从而完成SQL的...

    springboot + mybatis(通用mapper) + druid多数据源

    通用Mapper是MyBatis的一种扩展,提供了很多预定义的CRUD操作,简化了开发过程,减少了代码量。而Druid的多数据源支持,则允许程序同时连接并操作不同的数据库,这在处理分布式系统或需要分离读写操作的场景中非常...

Global site tag (gtag.js) - Google Analytics