MyBatis 搭建及源码分析

一. Mybatis 简单示例

什么是mybatis
半自动ORM框架,在传统jdbc模式中每次执行对数据库的操作,都需要获取数据库连接,执行sql语句,关闭数据库连接,获取到的数据手动映射,比较复杂,重复一些无关的代码,通过mybatis,自动获取数据库连接,只关注sql执行,通过动态代理,自动对数据进行映射等

1. 搭建mybatis步骤总结

  1. 引入依赖
  2. 创建存有数据库连接信息的properties,配置文件,与log4j日志相关的配置文件(不配置日志可能会报错)
  3. 创建xml配置文件
    加载properties配置文件,读取文件中的数据库用户名密码,url等,配置数据源DataSource注入到容器中
    配置SqlSessionFactory,持有数据源扫描对应数据库表的mapper.xml
    扫描与mapper.xml进行绑定,通过动态代理生成代理类的mapper接口
  4. 创建对应数据库中每一张表的mapper.xml
  5. 创建于mapper.xml进行绑定,通过动态代理生成代理类的mapper接口,接口的全类名是对应mapper.xml的命名空间值
  6. 代码执行

2. 代码示例

  1. 引入依赖(此处直接以Spring整合mybatis为例,多引入了spring使用mybatis需要的大部分依赖)
			<!-- Mybatis -->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>${mybatis-spring.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>${mybatis.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>${spring-version}</version>
            </dependency>
            <!-- MySql -->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <!--ibatis 依赖-->
            <dependency>
                <groupId>org.apache.ibatis</groupId>
                <artifactId>ibatis-core</artifactId>
                <version>${ibatis.version}</version>
            </dependency>
            <!-- 连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
  1. 创建properties配置文件
    数据库连接信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mysql?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=

配置日志的

#console log
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c - %m%n


#logger
#log4j.logger.org.springframework=DEBUG,CONSOLE
#log4j.logger.org.hibernate=INFO,CONSOLE
#log4j.logger.org.apache=INFO,CONSOLE

log4j.rootLogger=DEBUG,CONSOLE
  1. 创建关于mybatis的xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

	<!-- 1.加载属性文件(例如存有数据库连接,登入密码的jdbc.properties,与配置日志相关的 log4j.properties-->
	<context:property-placeholder location="classpath:properties/*.properties"/>

	<!-- 2.配置数据源 -->
	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<!-- 配置url ${jdbc.url}从属性文件获取 -->
		<property name="url" value="${jdbc.url}"/>
		<!-- 配置用户名 -->
		<property name="username" value="${jdbc.username}"/>
		<!-- 配置数据库密码 -->
		<property name="password" value=""/>
		<!-- 使用Druid无需配置driver,会自动的根据url得到driver -->
	</bean>

	<!-- 3.配置SqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="configLocation" value="classpath:mybatis/config.xml"/>
		<property name="dataSource" ref="dataSource"/>
		<!-- 配置自动扫描mybatis Mapper.java Mapper.xml -->
		<property name="mapperLocations" value="classpath:mapper/*.xml"/>
	</bean>

	<!--扫描对应mapper.xml的映射接口-->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 扫描包 -->
		<property name="basePackage" value="com.ssm.dao" />
		<!-- sqlSessionFactoryBeanName是批factory的名称 此处使用value,而不是ref -->
		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
	</bean>
</beans>
  1. 创建对应数据库表的mapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ssm.dao.HelpCategoryDao">
<!--mapper namespace 当前mapper.xml的命名空间,指向当前xml文件对应的接口全类名-->

    <resultMap type="com.ssm.entity.HelpCategory" id="HelpCategoryMap">
        <result property="helpCategoryId" column="help_category_id" jdbcType="OTHER"/>
        <result property="name" column="name" jdbcType="VARCHAR"/>
        <result property="parentCategoryId" column="parent_category_id" jdbcType="OTHER"/>
        <result property="url" column="url" jdbcType="VARCHAR"/>
    </resultMap>

    <!--查询单个-->
    <select id="queryById" resultMap="HelpCategoryMap">
        select
          help_category_id, name, parent_category_id, url
        from mysql.help_category
        where help_category_id = #{helpCategoryId}
    </select>

    <select id="selectAll" resultMap="HelpCategoryMap">
        select
          help_category_id, name, parent_category_id, url
        from mysql.help_category
    </select>

    <!--查询指定行数据-->
    <select id="queryAllByLimit" resultMap="HelpCategoryMap">
        select
          help_category_id, name, parent_category_id, url
        from mysql.help_category
        limit #{offset}, #{limit}
    </select>

    <!--通过实体作为筛选条件查询-->
    <select id="queryAll" resultMap="HelpCategoryMap">
        select
          help_category_id, name, parent_category_id, url
        from mysql.help_category
        <where>
            <if test="helpCategoryId != null">
                and help_category_id = #{helpCategoryId}
            </if>
            <if test="name != null and name != ''">
                and name = #{name}
            </if>
            <if test="parentCategoryId != null">
                and parent_category_id = #{parentCategoryId}
            </if>
            <if test="url != null and url != ''">
                and url = #{url}
            </if>
        </where>
    </select>

    <!--新增所有列-->
    <insert id="insert" keyProperty="helpCategoryId" useGeneratedKeys="true">
        insert into mysql.help_category(name, parent_category_id, url)
        values (#{name}, #{parentCategoryId}, #{url})
    </insert>

    <!--通过主键修改数据-->
    <update id="update">
        update mysql.help_category
        <set>
            <if test="name != null and name != ''">
                name = #{name},
            </if>
            <if test="parentCategoryId != null">
                parent_category_id = #{parentCategoryId},
            </if>
            <if test="url != null and url != ''">
                url = #{url},
            </if>
        </set>
        where help_category_id = #{helpCategoryId}
    </update>

    <!--通过主键删除-->
    <delete id="deleteById">
        delete from mysql.help_category where help_category_id = #{helpCategoryId}
    </delete>

    <insert id="saveReturnKey" parameterType="com.ssm.entity.HelpCategory" useGeneratedKeys="true">
        insert into help_category value (
        <set>
            <if test="name != null and name != ''">
                #{name},
            </if>
            <if test="parentCategoryId != null">
                #{parentCategoryId},
            </if>
            <if test="url != null and url != ''">
                #{url},
            </if>
        </set>
        )
    </insert>

</mapper>
  1. 与mapper.xml进行绑定的接口
public interface HelpCategoryDao {

    /**
     * 通过ID查询单条数据
     *
     * @param helpCategoryId 主键
     * @return 实例对象
     */
    HelpCategory queryById(Object helpCategoryId);

    /**
     * 查询指定行数据
     *
     * @param offset 查询起始位置
     * @param limit 查询条数
     * @return 对象列表
     */
    List<HelpCategory> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);


    /**
     * 通过实体作为筛选条件查询
     *
     * @param helpCategory 实例对象
     * @return 对象列表
     */
    List<HelpCategory> queryAll(HelpCategory helpCategory);

    /**
     * 新增数据
     *
     * @param helpCategory 实例对象
     * @return 影响行数
     */
    int insert(HelpCategory helpCategory);

    /**
     * 修改数据
     *
     * @param helpCategory 实例对象
     * @return 影响行数
     */
    int update(HelpCategory helpCategory);

    /**
     * 通过主键删除数据
     *
     * @param helpCategoryId 主键
     * @return 影响行数
     */
    int deleteById(Object helpCategoryId);

    List<HelpCategory> selectAll();

    String saveReturnKey(HelpCategory helpCategory);

}
  1. 创建执行代码,此处通过在容器中获取SqlSessionFactory,手动执行sql,演示sql执行步骤
    执行时,通过 SqlSessionFactory 获取到 SqlSession,通过SqlSession,进行数据库的增删改查
 	@org.junit.Test
    public void test3(){
        //创建ioc容器
        ApplicationContext context = new ClassPathXmlApplicationContext
                ("classpath:spring/applicationContext-mybatis.xml");

        SqlSessionFactory sessionFactory = (SqlSessionFactory) context.getBean("sqlSessionFactory");

        //通过SqlSessionFactory获取SqlSession,注意SqlSessin是非线程安全的,
        SqlSession sqlSession = sessionFactory.openSession();

        //不使用接口的情况下,通过mapper.xml执行sql:
        //selectOne()方法的第一个参数,指定mapper.xml的命名空间+要执行的sql语句的id值
        //通过这个参数,可以定位到到底是哪个sql执行,第二个参数为执行该sql语句需要的参数
        HelpCategory helpCategory = sqlSession.selectOne( "com.ssm.dao.HelpCategoryDao.queryById", 1);
        System.out.println(helpCategory);


        //使用接口的动态代理,生成代理类,通过代理类执行的方式二:
        //该方式需要对应每个mapper.xml 创建接口,mapper.xml的命名空间指向对应的接口的全类名
        //接口中提供与mapper.xml中要执行的sql语句对应的抽象方法,入参就是sql执行需要的参数,
        //返回值就是sql执行的返回值,方法名,就是对应sql的id

        //通过 SqlSession 调用 getMapper() 方法,传递接口的类型,
        //获取该接口的全类名,通过接口的全类名找到对应的mapper.xml
        //与mapper.xml进行绑定,通过动态代理生成该接口的实体类
        //(此处是手动指定的,mybatis中需要配置自动扫描这些接口,与对应的xml,自动绑定)
        HelpCategoryDao helpCategoryDao = sqlSession.getMapper(HelpCategoryDao.class);

        //通过接口与mapper.xml生成的实体类调用接口中的方法,也就是对应每一条sql的方法
        List<HelpCategory> hList = helpCategoryDao.selectAll();
        hList.stream().forEach(h -> System.out.println(h));

        //关闭sqlSession
        sqlSession.close();

    }

二. 源码分析

通过代码执行sql操作数据库数据可以发现,是通过SqlSessionFactory,获取到SqlSession,然后通过SqlSession获取到对应数据表的接口对象,通过接口对象执行sql方法,
分为三步了解源码:

  1. SqlSessionFactoryBean继承了FactoryBean接口,通过重写的该接口的getObject()方法,创建初始化SqlSesionFactory,通过配置的SqlSessionFactoryBean的属性拿到存有全局变量的配置文件与存有sql执行语句对应数据库表的xml文件,基于DOM4J进行解析,最终将每一条sql语句解析为MappedStatement ,将解析后的所有数据存入Configuration中,创建 DefaultSqlSessionFactory 对象
  2. 通过SqlSessionFactory创建出SqlSession,首先会通过存有解析后数据的Configuration拿到我们配置的执行器类型(默认是Simple),通过执行器类型创建执行器,数据库的增删改查在最顶层是有执行器来完成的,通过执行器与Configuration创建DefaultSqlSession
  3. 对应数据库表的接口代理类的创建: 通过扫描觉得方式获取到指定接口,根据接口的全类名找到绑定的对应数据库表的xml(xml的命名空间就是接口的全类名),创建MapperProxyFactory代理工厂,通过代理工厂创建代理类MapperProxy
  4. sql方法的执行 : 实际执行调用的是生成的代理类MapperProxy的invoke(),在执行前通过Configuration获取到当前要执行的方法的的sql详细信息,将当前要执行的sql方法封装为 MapperMethod ,该类中有三个静态内部类: SqlCommand 存有分析方法后得出的sql指令,ParamMap 是一个容器,存有方法执行前后的需要的参数,MethodSignature 该类中主要关注持有的ParamNameResolver参数名解析器,通过该解析器,可以解析保存参数的所有信息,例如参数类型,参数注解等,将执行的sql方法解析封装到MapperMethod 的这三个内部类中,还是通过SqlSession调用方法执行操作数据库
  5. SqlSession调用方法执行数据库操作继续向下查看会发现,通过前面创建的执行器BaseExecutor来执行,在执行器执行时,首先会创建一个StatementHandler ,通过StatementHandler 创建出Statement,通过Statement执行数据库的操作,在执行的过程中还会创建另外几个Handler,例如处理执行sql参数的ParameterHandler, 处理返回结果参数的ResultHandler等,

1. SqlSessionFactoryBean 初始化 SqlSessionFactory

在搭建Spring+mybatis项目时,需要配置数据源,配置SqlSessionFactory,查看配置文件发现,实际在配置文件中配置注入的是SqlSessionFactoryBean该类继承了 FactoryBean与 ApplicationListener 两个接口,继承FactoryBean接口可以得出这是一个工厂,当向容器中注入一个实现了FactoryBean接口的类时,实际注入的是该类中重写的getObject() 方法返回的 bean(具体查看FactoryBean方式注入),在通过该方法创建DefaultSqlSessionFactory注入时,同时会解析存有全局变量的xml跟存有sql语句,与数据库表进行映射的xml,存入Configuration中
在这里插入图片描述
在这里插入图片描述

在buildSqlSessionFactory() 方法中判断SqlSessionFactoryBean都是配置了哪些属性,通过这个属性读取配置文件,进行解析,将解析后的数据存入Configuration中,此处我们通过配置SqlSessionFactoryBean 的 mapperLocations 属性扫描对应数据库表的 xml

protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
		//1.创建基于Dom4j的解析器 XMLConfigBuilder 
        XMLConfigBuilder xmlConfigBuilder = null;
        //2.声明为后续存放解析数据的 Configuration 
        Configuration configuration;
        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);
            }
            //3.判断是否配置了configuration 属性值,如果配置了,通过该属性值读取配置文件进行解析(例如此处配置该属性值是开启二级缓存)
        } else if (this.configLocation != null) {
        	//通过解析器解析使用 configLocation 属性设置的的xml
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property `configuration` or 'configLocation' not specified, using default MyBatis Configuration");
            }
            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);
        }
        String[] typeHandlersPackageArray;
        String[] var4;
        int var5;
        int var6;
        String packageToScan;
        if (StringUtils.hasLength(this.typeAliasesPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
            var4 = typeHandlersPackageArray;
            var5 = typeHandlersPackageArray.length;
            for(var6 = 0; var6 < var5; ++var6) {
                packageToScan = var4[var6];
                configuration.getTypeAliasRegistry().registerAliases(packageToScan, this.typeAliasesSuperType == null ? Object.class : this.typeAliasesSuperType);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
                }
            }
        }
        int var27;
        if (!ObjectUtils.isEmpty(this.typeAliases)) {
            Class[] var25 = this.typeAliases;
            var27 = var25.length;
            for(var5 = 0; var5 < var27; ++var5) {
                Class<?> typeAlias = var25[var5];
                configuration.getTypeAliasRegistry().registerAlias(typeAlias);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type alias: '" + typeAlias + "'");
                }
            }
        }
        if (!ObjectUtils.isEmpty(this.plugins)) {
            Interceptor[] var26 = this.plugins;
            var27 = var26.length;
            for(var5 = 0; var5 < var27; ++var5) {
                Interceptor plugin = var26[var5];
                configuration.addInterceptor(plugin);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered plugin: '" + plugin + "'");
                }
            }
        }
        if (StringUtils.hasLength(this.typeHandlersPackage)) {
            typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
            var4 = typeHandlersPackageArray;
            var5 = typeHandlersPackageArray.length;
            for(var6 = 0; var6 < var5; ++var6) {
                packageToScan = var4[var6];
                configuration.getTypeHandlerRegistry().register(packageToScan);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
                }
            }
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            TypeHandler[] var28 = this.typeHandlers;
            var27 = var28.length;

            for(var5 = 0; var5 < var27; ++var5) {
                TypeHandler<?> typeHandler = var28[var5];
                configuration.getTypeHandlerRegistry().register(typeHandler);
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Registered type handler: '" + typeHandler + "'");
                }
            }
        }
        if (this.databaseIdProvider != null) {
            try {
                configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException var24) {
                throw new NestedIOException("Failed getting a databaseId", var24);
            }
        }
        if (this.cache != null) {
            configuration.addCache(this.cache);
        }
        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();
                if (LOGGER.isDebugEnabled()) {
                    LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
                }
            } catch (Exception var22) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, var22);
            } finally {
                ErrorContext.instance().reset();
            }
        }
        if (this.transactionFactory == null) {
            this.transactionFactory = new SpringManagedTransactionFactory();
        }
        configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
        //查看我们的配置文件是通过 mapperLocations 属性配置扫描的mapper.xml
        //解析对应数据库表的mapper.xml
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] var29 = this.mapperLocations;
            var27 = var29.length;

            for(var5 = 0; var5 < var27; ++var5) {
                Resource mapperLocation = var29[var5];
                if (mapperLocation != null) {
                    try {
                    	//读取mapper.xml配置文件,创建解析器进行解析
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                        //解析,并将解析后的数据存入 configuration 中
                        xmlMapperBuilder.parse();
                    } catch (Exception var20) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                    } finally {
                        ErrorContext.instance().reset();
                    }

                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            }
        } else if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
		//通过 sqlSessionFactoryBuilder 调用 build() 方法传递解析好数据的 configuration
		//创建DefaultSqlSessionFactory对象返回
        return this.sqlSessionFactoryBuilder.build(configuration);
    }

解析对应数据库表的mapper.xml

XMLMapperBuilder 中的 parse()方法,在调用该方法时已经读取到了对应数据库表的指定xml

	public void parse() {
        if (!this.configuration.isResourceLoaded(this.resource)) {
        	//读取xml中的mapper标签,也就是当前xml的命名空间(命名空间的值是对应进行绑定的接口的全类名)
            this.configurationElement(this.parser.evalNode("/mapper"));
            this.configuration.addLoadedResource(this.resource);
            this.bindMapperForNamespace();
        }
        this.parsePendingResultMaps();
        this.parsePendingChacheRefs();
        this.parsePendingStatements();
    }
	private void configurationElement(XNode context) {
        try {
        	//读取当前xml的命名空间(也就是当前xml绑定的接口全类名)
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
            	//将当前xml的命名空间设置到 builderAssistant 属性中
                this.builderAssistant.setCurrentNamespace(namespace);
                
                //解析当前xml中的各种标签
                
                //解析缓存相关标签是由有缓存设置
                this.cacheRefElement(context.evalNode("cache-ref"));
                this.cacheElement(context.evalNode("cache"));
                //解析参数集标签
                this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                //解析自定义的返回结果集标签
                this.resultMapElements(context.evalNodes("/mapper/resultMap"));
                //解析抽取的可重用sql标签
                this.sqlElement(context.evalNodes("/mapper/sql"));
                //解析当前xml中的增删改查标签
                this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
            } else {
                throw new BuilderException("Mapper's namespace cannot be empty");
            }
        } catch (Exception var3) {
            throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
        }
    }

解析Mapper.xml中的每一条sql语句

最终将每个 sql封装为 MappedStatement 类型,存入 Configuration 中(MappedStatement 可以简单理解为对应sql语句的类,保存了一条增删改查的详细信息)

private void buildStatementFromContext(List<XNode> list) {
        if (this.configuration.getDatabaseId() != null) {
        	//this.configuration.getDatabaseId() 获取到增删改查相关标签中的所有节点
    		//然后进行解析
            this.buildStatementFromContext(list, this.configuration.getDatabaseId());
        }
        this.buildStatementFromContext(list, (String)null);
    }
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        Iterator i$ = list.iterator();

        while(i$.hasNext()) {
            XNode context = (XNode)i$.next();
            XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

            try {
            	//解析sql语句标签
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

    }
public void parseStatementNode() {
		//读取sql标签的id值
        String id = this.context.getStringAttribute("id");
        String databaseId = this.context.getStringAttribute("databaseId");
        if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        	//解析sql标签中的所有节点
            Integer fetchSize = this.context.getIntAttribute("fetchSize");
            Integer timeout = this.context.getIntAttribute("timeout");
            String parameterMap = this.context.getStringAttribute("parameterMap");
            String parameterType = this.context.getStringAttribute("parameterType");
            Class<?> parameterTypeClass = this.resolveClass(parameterType);
            String resultMap = this.context.getStringAttribute("resultMap");
            String resultType = this.context.getStringAttribute("resultType");
            String lang = this.context.getStringAttribute("lang");
            LanguageDriver langDriver = this.getLanguageDriver(lang);
            Class<?> resultTypeClass = this.resolveClass(resultType);
            String resultSetType = this.context.getStringAttribute("resultSetType");
            StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
            ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
            String nodeName = this.context.getNode().getNodeName();
            SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
            includeParser.applyIncludes(this.context.getNode());
            this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
            SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
            String resultSets = this.context.getStringAttribute("resultSets");
            String keyProperty = this.context.getStringAttribute("keyProperty");
            String keyColumn = this.context.getStringAttribute("keyColumn");
            String keyStatementId = id + "!selectKey";
            keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
            Object keyGenerator;
            if (this.configuration.hasKeyGenerator(keyStatementId)) {
                keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
            } else {
                keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
            }
			//解析每一个sql方法封装为 MappedStatement,存入Configuration 中
            this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
    }

2. 获取SqlSession

(没找到Sqring创建SqlSession此处示例的是通过SqlSessionFactory调用openSession()方法获取SqlSession)
创建SqlSession流程是通过配置爱的执行器类型创建Executor 执行器,执行器中封装了实际的增删改查方法,通过Configuration与执行器,创建DefaultSqlSession

public SqlSession openSession() {
		//configuration.getDefaultExecutorType() 获取配置的执行器类型,默认是"SIMPLE"
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }
 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
        	//1.在配置中获取当前环境数据
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //创建一个事物
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //2.根据配置的执行器类型通过configuration创建一个执行器
            Executor executor = this.configuration.newExecutor(tx, execType);
            //3.使用configuration与创建的executor创建一个DefaultSqlSession对象返回
            var8 = new DefaultSqlSession(this.config uration, executor, autoCommit);
        } catch (Exception var12) {
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
            ErrorContext.instance().reset();
        }

        return var8;
    }

3. 执行sql的流程

MapperProxy 代理类的 invoke() 方法

在项目运行时,会通过MapperProxyFactory创建与mapper.xml进行绑定的接口的代理类MapperProxy,调用映射接口中的方法时,执行的是生成的代理类MapperProxy中的invoke()方法,

扫描二维码关注公众号,回复: 11083445 查看本文章
  1. 判断当前执行的方法是否是Object中的方法如果是直接放行,例如toString,equals等,不是关于数据库的
  2. 如果是关于数据库的sql方法,调用cachedMapperMethod()解析方法,将sql方法再次进行封装为 MapperMethod 类型
  3. 通过 MapperMethod 调用 execute() 执行sql的方法 执行方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		//1.首先判断当前执行的方法是否是Object下的方法(例如toString,equals等)如果是直接放行
        if (Object.class.equals(method.getDeclaringClass())) {
            try {
                return method.invoke(this, args);
            } catch (Throwable var5) {
                throw ExceptionUtil.unwrapThrowable(var5);
            }
        } else {
        	//执行sql相关的方法
        	//2.将执行的Sql 方法封装为 MapperMethod 类型
            MapperMethod mapperMethod = this.cachedMapperMethod(method);
            //3.执行sql方法
            return mapperMethod.execute(this.sqlSession, args);
        }
    }

封装sql方法的 MapperMethod

在MapperProxy的invoke()方法中发现将执行的sql方法封装成了MapperMethod ,查看MapperMethod 源码该类中有三个静态内部类 SqlCommand 存有分析方法后得出的sql指令,ParamMap 是一个容器,存有方法执行前后的需要的参数,MethodSignature 该类中主要关注持有的ParamNameResolver参数名解析器,通过该解析器,可以解析保存参数的所有信息,例如参数类型,参数注解等,将执行的sql方法解析封装到MapperMethod 的这三个内部类中
ParamNameResolver 参数的封装过程:

	public ParamNameResolver(Configuration config, Method method) {
		//1.获取方法执行的参数类型
        Class<?>[] paramTypes = method.getParameterTypes();
        //2.获取参数注解
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        SortedMap<Integer, String> map = new TreeMap();
        int paramCount = paramAnnotations.length;
		//3.遍历标注参数下标(也就是执行sql是传递的参数要填充的位置)
        for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
            if (!isSpecialParameter(paramTypes[paramIndex])) {
                String name = null;
                Annotation[] arr$ = paramAnnotations[paramIndex];
                int len$ = arr$.length;

                for(int i$ = 0; i$ < len$; ++i$) {
                    Annotation annotation = arr$[i$];
                    //判断参数是否被@Param注解修饰
                    if (annotation instanceof Param) {
                        this.hasParamAnnotation = true;
                        //如有有name就是@Param注解的value值
                        name = ((Param)annotation).value();
                        break;
                    }
                }

                if (name == null) {
                    if (config.isUseActualParamName()) {
                        name = this.getActualParamName(method, paramIndex);
                    }
					//如果没有被@Param注解修饰,name就是下标
                    if (name == null) {
                        name = String.valueOf(map.size());
                    }
                }
				//3.存入map集合中(如果有@Param注解,就是注解的值,如果没有就是下标位置的索引)
				//这个map中存放的就是sql执行需要的参数,自此将参数填充的位置设置好了
                map.put(paramIndex, name);
            }
        }
        this.names = Collections.unmodifiableSortedMap(map);
    }

execute() 方法

 public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        //1.首先通过switch判断当前要执行的sql是怎删改查什么类型的
        switch(this.command.getType()) {
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
        	//2.以select查询为例,首先判断当前执行的sql是否有参数,参数是什么类型(map,list,基本类型等)
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
            	//3.调用 convertArgsToSqlCommandParam(args)方法,将传递的参数
            	//转换为sql的可执行参数(可以理解为将java类型的数据转换为sql类型的数据)
                param = this.method.convertArgsToSqlCommandParam(args);
                
                //4.还是通过sqlSession调用增删改查来实现的(前面解析xml中的每一条sql封装为
                //MappedStatement 类型,存入 Configuration,在sqlSession执行sql语句时也是
                //在Configuration中,根据当前要执行的sql语句的id值去Configuration中获取)
                result = sqlSession.selectOne(this.command.getName(), param);
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
            return result;
        }
    }

selectOne()通过查询方法了解实际底层查询流程

通过Configuration拿到存有解析到的sql标签详细信息(在解析时将每一条sql的详细信息封装为MappedStatement 存入了Configuration中)

	public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
        	//获取当前要执行的sql标签的详细信息
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //通过Executor执行器调用方法进行实际查询
            //this.wrapCollection(parameter)根据执行sql时传递的数据类型进行封装
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
		//获取BoundSql,sql语句的详细信息,可以理解为再次封装要执行的sql语句
        BoundSql boundSql = ms.getBoundSql(parameter);
        //创建缓存key(如果使用了缓存,通过缓存key先在缓存中查询数据)
        CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
        return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
		//首先通过缓存key在缓存中查询数据
        Cache cache = ms.getCache();
        if (cache != null) {
            this.flushCacheIfRequired(ms);
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, parameterObject, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }
		//最终调用BaseExecutor中的 query() 方法进行查询
        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

BaseExecutor中的 query()

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
            throw new ExecutorException("Executor was closed.");
        } else {
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
                this.clearLocalCache();
            }

            List list;
            try {
                ++this.queryStack;
                //先查询二级缓存
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
                	//二级缓存中没有,在一级缓存中查
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
                	//一级缓存中也没有,执行sql查询
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
                --this.queryStack;
            }

            if (this.queryStack == 0) {
                Iterator i$ = this.deferredLoads.iterator();

                while(i$.hasNext()) {
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

在该方法中调用SimpleExecutor中的 doQuery() 进行查询

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
        	//调用doQuery()方法进行查询
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            this.localCache.removeObject(key);
        }
		//将查询出的数据再次放入缓存中
        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

SimpleExecutor中的 doQuery()

public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
        	//拿到当前的配置信息
            Configuration configuration = ms.getConfiguration();
            //创建一个 StatementHandler 对象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //通过StatementHandler 创建 Statement 
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            //通过 Statement 执行sql语句
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

在这里插入图片描述

发布了53 篇原创文章 · 获赞 0 · 访问量 718

猜你喜欢

转载自blog.csdn.net/qq_29799655/article/details/105439349