框架 - MyBatis源码(一)

简介

你了解MyBatis多少?了解SqlSessionFactory构建过程吗?了解XML解析过程吗?了解Mapper代理都干了什么事儿吗?本章以MyBatis 3.5.3带你一起阅读源代码,让你不在局限于会用,而是领会MyBatis的精髓。

通过简单Mybatis工程一步一步深入

Maven依赖包
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.3</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
User 实体类

目录:com\user\User.java

@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 用户ID
     */
    private Long userId;
    /**
     * 手机号
     */
    private String mobile;
}
UserMapper 接口

目录:com\user\UserMapper.java

public interface UserMapper {
    /**
     * 查询
     */
    User select(Long userId);
    /**
     * 插入
     */
    int insert(User user);
    /**
     * 修改
     */
    int update(User user);
    /**
     * 删除
     */
    int delete(Long userId);
}
UserMapper.xml 配置文件

目录resources\mapper\UserMapper.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.user.UserMapper">
  <resultMap id="BaseResultMap" type="com.user.User">
    <id column="user_id" jdbcType="BIGINT" property="userId" />
    <result column="mobile" jdbcType="VARCHAR" property="mobile"/>
  </resultMap>
  <sql id="Base_Column_List">
    user_id, mobile
  </sql>
  <select id="select" parameterType="java.lang.Long" 
          resultMap="BaseResultMap">
    select 
    <include refid="Base_Column_List" />
    from `user`
    where user_id = #{userId,jdbcType=BIGINT}
  </select>
  <delete id="delete" parameterType="java.lang.Long">
    delete from `user`
    where user_id = #{userId,jdbcType=BIGINT}
  </delete>
  <insert id="insert" parameterType="com.user.User">
    insert into `user` (user_id, mobile)
    values (#{userId,jdbcType=BIGINT},
        #{mobile,jdbcType=VARCHAR})
  </insert>
  <update id="update" parameterType="com.user.User">
    update `user`
    set mobile = #{mobile,jdbcType=VARCHAR}
    where user_id = #{userId,jdbcType=BIGINT}
  </update>
</mapper>
Mybaits 配置文件

目录:resources\config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"></properties>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>
    <environments default="test">
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/UserMapper.xml"/>
    </mappers>
</configuration>
JDBC配置文件

目录:resources\jdbc.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.1.100:3306/mybaits
username=root
password=root
Log配置文件

目录:resources\log4j.properties

log4j.rootLogger=DEBUG, stdout
log4j.logger.org.mybatis.example.BlogMapper=TRACE
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
数据库
CREATE TABLE `user`  (
  `user_id` bigint(19) NOT NULL COMMENT '用户ID',
  `mobile` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '手机号',
  PRIMARY KEY (`user_id`) USING BTREE,
  UNIQUE INDEX `cs_user_mobile_index`(`mobile`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户' ROW_FORMAT = Dynamic;
 
INSERT INTO `user` VALUES (1, '13100001001');
INSERT INTO `user` VALUES (2, '13100001002');
INSERT INTO `user` VALUES (3, '13100001003');
INSERT INTO `user` VALUES (4, '13100001004');
INSERT INTO `user` VALUES (5, '13100001005');
测试类Test
public class Test {
    private static SqlSessionFactory getSessionFactory() 
            throws IOException {
        String resource = "config.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        return new SqlSessionFactoryBuilder().build(reader);
    }
    public static void main(String[] args) throws IOException {
        SqlSession sqlSession = getSessionFactory().openSession();
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        User user = userMapper.select(1L);
        System.out.println(user.toString());
    }
}
输出日志
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - PooledDataSource forcefully closed/removed all connections.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 1579132337.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@5e1fa5b1]
DEBUG [main] - ==>  Preparing: select user_id, mobile from `user` where user_id = ? 
DEBUG [main] - ==> Parameters: 1(Long)
DEBUG [main] - <==      Total: 1
User(userId=1, mobile=13100001001)
Disconnected from the target VM, address: '127.0.0.1:52936', transport: 'socket'
简要解析
String resource = "mybaits.xml";
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);

先使用Resources.getResourceAsReader(resource)加载配xml置文件,然后SqlSessionFactoryBuilder的build方法构建SqlSessionFactory,这里主要关注SqlSessionFactory的构建过程。
1、SqlSessionFactoryBuilder委托XMLConfigBuilder
2、XMLConfigBuilder创建 XMLMapperEntityResolver DTD加载器,并委托XPathParser解析器解析mybaits.xml配置文件
3、XPathParser解析器使用XPathFactory加载xml和DocumentBuilderFactory构建Document
4、XMLConfigBuilder使用第三步的结果创建Configuration,并给Configuration赋值,最后完成XMLConfigBuilder的创建
5、SqlSessionFactoryBuilder中调用XMLConfigBuilder的parse()解析方法,解析mybaits.xml中各个节点
6、XMLConfigBuilder解析mappers节点,遍历mapper路径配置列表
7、构建XMLMapperBuilder加载上面得到的mapper配置文件,并调用parse()解析mapper配置文件中各个节点。
8、XMLMapperBuilder中把路径"mapper/UserMapper.xml"、namespace、namespace反射得到的Class等加入Configuration
9、XMLMapperBuilder调用MapperRegistry.addMapper(Class type),构建MapperProxyFactory向Mapper已知管理器中注册
9、SqlSessionFactoryBuilder通过上面创建的Configuration构造DefaultSqlSessionFactory

SqlSession sqlSession = getSessionFactory().openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.select(1L);

使用上面获得的SqlSessionFactory,先打开会话获取SqlSession,然后从SqlSession中获取UserMapper,最后调用UserMapper的方法,这里需要关注Mapper是怎么被代理的
1、调用DefaultSqlSessionFactory的openSession()方法,先获取执行器类型,然后通过类型打开Session
2、创建事务和执行器,构建SqlSession并返回
3、通过Class从SqlSession中获取Mapper,此过程及其重要。实际上是从Configuration中获取MapperProxyFactory 代理工厂,调用newInstance创建代理类MapperProxy并返回
5、调用UserMapper的方法,实际上是调用代理对象方法,主要需要关注MapperProxy的invoke方法
6、Mapp接口有实现类时直接invoke调用,没有实现类时先从methodCache中取,没取到构建MapperMethod并放入
7、按增删改查类型执行调用相应的执行,并包装结果返回

posted @ 2020-03-24 15:58  源码猎人  阅读(166)  评论(0编辑  收藏  举报