Mybatis全方位深入源码剖析

Posted 传智教育官方博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis全方位深入源码剖析相关的知识,希望对你有一定的参考价值。

文章目录



提示:以下是本篇文章正文内容,下面案例可供参考

MyBatis源码剖析

1 MyBatis 框架概述

mybatis 是一个优秀的基于 java 的持久层框架,它内部封装了 jdbc,使开发者只需要关注 sql 语句本身,而不需要花费精力去处理加载驱动、创建连接、创建 statement 等繁杂的过程。

mybatis 通过 xml或注解的方式将要执行的各种 statement 配置起来,并通过 java 对象和 statement 中 sql的动态参数进行映射生成最终执行的 sql 语句,最后由 mybatis 框架执行 sql 并将结果映射为 java 对象并返回。采用 ORM 思想解决了实体和数据库映射的问题,对 jdbc 进行了封装,屏蔽了 jdbc api 底层访问细节,使我们不用与 jdbc api 打交道,就可以完成对数据库的持久化操作。

2 JDBC缺陷总结

  • 数据库链接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
  • Sql 语句在代码中硬编码,造成代码不易维护,实际应用 sql 变化的可能较大, sql 变动需要改变java 代码。
  • 使用 preparedStatement 向占有位符号传参数存在硬编码,因为 sql 语句的 where 条件不一定,可能多也可能少,修改 sql 还要修改代码,系统不易维护。
  • 对结果集解析存在硬编码(查询列名), sql 变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成 pojo 对象解析比较方便。

3 MyBatis快速入门

3.1 搭建 Mybatis 开发环境

创建工程之前,我们先新建数据库mybatis,并在数据库中新建一张User表,并加一些数据。表包含:id,username,birthday,sex,address字段

2.2.1 创建Maven工程
2.2.2 导入依赖
  1. mybatis

  2. mysql驱动

  3. log4j

  4. junit单元测试

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.itheima</groupId>
        <artifactId>mybatis-day01-demo1</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <!--打包方式-->
        <packaging>jar</packaging>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        </properties>
    
        <!--引入相关依赖-->
        <dependencies>
            <!--MyBatis依赖包-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.4.5</version>
            </dependency>
    
            <!--MySQL驱动包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
                <scope>runtime</scope>
            </dependency>
    
            <!--日志包-->
            <dependency>
                <groupId>log4j</groupId>
                <artifactId>log4j</artifactId>
                <version>1.2.12</version>
            </dependency>
    
            <!--测试包-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.xml</include>
                    </includes>
                </resource>
            </resources>
        </build>
    </project>
    
2.2.2 创建User

创建com.itheima.domain包,在该包下创建User对象,并添加对应的属性。

public class User implements Serializable 
    private Integer id;
    private String username;
    private Date birthday;
    private String sex;
    private String address;
    //略 get...set...toString...

2.2.3 创建UserMapper接口

创建com.itheima.mapper包,并在该包下创建接口,代码如下:

它其实就是dao层的接口

public interface UserMapper 
    List<User> findAll();

2.2.4 创建UserMapper.xml

这个xml配置文件的位置,必须和对应的那个Mapper接口的位置一样。而且其文件名也要和接口名一样

在com.itheima.mapper包下创建UserMapper.xml,并在UserMapper.xml中添加一个select查询结点,代码如下:

<?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.itheima.mapper.UserMapper">
    <!--findAll-->
    <select id="findAll" resultType="com.itheima.domain.User">
        SELECT * FROM  user
    </select>
</mapper>
2.2.5 创建SqlMapConfig.xml

在main/resources下创建SqlMapConfig.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>
	    <!--数据源配置-->
	    <environments default="development">
	        <environment id="development">
	            <transactionManager type="JDBC"/>
	            <dataSource type="POOLED">
	                <property name="driver" value="com.mysql.jdbc.Driver"/>
	                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
	                <property name="username" value="root"/>
	                <property name="password" value="123"/>
	            </dataSource>
	        </environment>
	    </environments>
	    <!--加载映射文件-->
	    <mappers>
	        <mapper resource="com/itheima/mapper/UserMapper.xml"/>
	    </mappers>
	</configuration>
2.2.6 创建log4j.properties

为了方便查看日志,在main/resources下创建log4j.properties文件,代码如下:

log4j.rootLogger=DEBUG,Console
log4j.appender.Console=org.apache.log4j.ConsoleAppender
log4j.appender.Console.layout=org.apache.log4j.PatternLayout
log4j.appender.Console.layout.ConversionPattern=%d [%t] %-5p [%c] - %m%n
log4j.logger.org.apache=DEBUG

2.3 编写测试类

在test包下创建com.itheima.test,再在该包下创建MyBatisTest类,代码如下:

public class MyBatisTest 
	    @Test
	    public void testFindAll() throws IOException 
	        //读取配置文件
	        InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
	        //创建SqlSessionFactoryBuilder对象
	        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
	        //通过SqlSessionBuilder对象构建一个SqlSessionFactory
	        SqlSessionFactory sqlSessionFactory = builder.build(is);
	        //通过SqlSessionFactory构建一个SqlSession
	        SqlSession session = sqlSessionFactory.openSession();
	        //通过SqlSession实现增删改查
	        UserMapper userMapper = session.getMapper(UserMapper.class);
	        List<User> users = userMapper.findAll();
	        //打印输出
	        for (User user : users) 
	            System.out.println(user);
	        
	        //关闭资源
	        session.close();
	        is.close();
	    
	

4 自定义 Mybatis 框架

本章我们将使用前面所学的基础知识来构建一个属于自己的持久层框架,将会涉及到的一些知识点:工厂模式(Factory 工厂模式)、构造者模式(Builder 模式)、动态代理模式,反射, xml 解析,数据库元数据,数据库元数据等。

4.1 MyBatis框架设计模式分析

我们来看看MyBatis框架使用过程中用到的一些设计模式。

  1. 使用SqlSessionFactoryBuilder创建SqlSessionFactory工厂对象的时候使用的是构建者模式
  2. 使用SqlSessionFactory创建SqlSession时候使用的是工厂模式
  3. 使用SqlSession创建UserMapper接口代理对象的时候使用的是动态代理模式

4.2 执行查询所有用户的SQL语句必须的步骤

  1. 需要获取连接
  2. 需要SQL语句
  3. 需要将结果集中的数据封装到JavaBean中

4.3 基于JDBC实现封装流程分析

基于上面我们说到的JDBC流程再结合MyBatis流程,我们封装一个持久城框架,达到MyBatis中的增删改查效果。来分析一波:

通过上图分析,我们可以发现:

  1. 通过解析主配置文件SqlMapConfig.xml可以获取username,password,driver,url等信息,通过这些信息能够获取Connection对象
  2. 通过解析映射配置文件UserMapper.xml可以获取要执行的SQL语句以及结果集要封装到的JavaBean类的全限定名resultType
  3. 通过反射机制和数据库元数据可以将结果集中的数据遍历出来并封装到对应的JavaBean对象中

4.4 准备工作

4.4.1 需要使用到的jar包:log4j,mysql驱动,dom4j以及其XPath,c3p0连接池
4.4.2 拷贝文件

分别将上一个入门工程中的User.java、UserMapper.java、MyBatisTest.java、UserMapper.xml、log4j.properties、SqlMapConfig.xml都拷贝到该工程中,将XML中引用的DTD文件约束去掉,不然每次解析都会去网上下载。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.itheima</groupId>
    <artifactId>mybatis-day01-demo2-custom</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--打包方式jar-->
    <packaging>jar</packaging>

    <dependencies>
        <!-- 日志坐标 -->
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <!-- mysql驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.36</version>
        </dependency>

        <!-- 解析xml的dom4j -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- dom4j的依赖包jaxen -->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.3</version>
        </dependency>

        <!--c3p0连接池-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

    <build>
        <!--IDEA是不会编译src的java目录的xml文件,如果需要读取,则需要手动指定哪些配置文件需要读取-->
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
</project>

4.4.3 工程错误改造

我们看到上面的工程存在错误,我们对他进行改造一下,首先创建对应的文件来去掉错误。

4.4.3.1 创建Resources类

该类的主要作用是读取类路径下的资源文件,所以要求被它读取的文件务必放到classes下,我们创建它的目的主要是模拟加载读取UserMapper.xml和SqlMapConfig.xml文件。

public class Resources 
    public static InputStream getResourceAsStream(String path)
        InputStream is = Resources.class.getClassLoader().getResourceAsStream(path);
        return is;
    

此时MyBatisTest中的Resources类就引用上面创建的类就可以去掉一个错误了。

4.4.3.2 创建SqlSessionFactoryBuilder类

创建该类,并创建一个build方法返回一个SqlSessionFactory对象,但SqlSessionFactory还没创建,所以接着我们需要创建它。

public class SqlSessionFactoryBuilder 
    public SqlSessionFactory build(InputStream is) 
        return null;
    

4.4.3.3 创建SqlSessionFactory接口及其实现类

创建SqlSessionFactory接口及其实现类,并且创建一个openSession方法。

public interface SqlSessionFactory 
    SqlSession openSession();

public class DefaultSqlSessionFactory implements SqlSessionFactory 
    @Override
    public SqlSession openSession() 
        return null;
    

4.4.3.4 创建SqlSession接口及其实现类

创建SqlSession接口,并在接口里面创建对应方法,然后创建一个DefaultSqlSession实现类

public interface SqlSession 
    UserMapper getMapper(Class<UserMapper> userMapperClass);
    void close();

public class DefaultSqlSession implements SqlSession 
    @Override
    public UserMapper getMapper(Class<UserMapper> userMapperClass) 
        return null;
    
    @Override
    public void close() 
    

把MyBatisTest类重新导包后,错误就全部消失了。接着我们就要开始对每个模块展开分析和代码实现了。

4.5 获取Connection实现

我们回到刚才我们的分析,首先我们要解析SqlMapConfig.xml,并把信息存储到Configuration对象中,然后通过Configuration对象获取数据库连接对象Connection。我们可以分这么几个步骤完成:

  • 创建XMLConfigBuilder解析SqlMapConfig.xml
  • 创建Configuration对象,存储解析的数据库连接信息
  • 改造Configuration对象,使它具备获取数据库链接Connection的功能
4.5.1 创建Configuration对象,存储解析的数据库连接信息
public class Configuration 
    private String username;
    private String password;
    private String url;
    private String driver;
    //get..set..toString..

4.5.2 创建XMLConfigBuilder解析SqlMapConfig.xml
public class XMLConfigBuilder
public static Configuration loadConfiguration(InputStream is)
        try 
            //1)数据库配置信息存储
            Configuration cfg = new Configuration();
            //创建SAXReader对象读取XML文件字节输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(is);
            //解析配置文件,获取根节点信息,//property表示获取根节点下所有的property结点对象
            List<Element> rootList = document.selectNodes("//property");
            //循环迭代所有结点对象
            for (Element element : rootList) 
                //name属性的值
                String name = element.attributeValue("name");
                //vallue属性的值
                String value = element.attributeValue("value");

                //2)将解析的数据库连接信息存储到Configuration中
                //数据库驱动
                if(name.equals("driver"))
                    cfg.setDriver(value);
                else if(name.equals("url"))
                    //数据库连接地址
                    cfg.setUrl(value);
                else if(name.equals("username"))
                    //数据库账号
                    cfg.setUsername(value);
                else if(name.equals("password"))
                    //数据库密码
                    cfg.setPassword(value);
                
            
            //获取需要解析的XML路径
            String resource = element.attributeValue("resource");
            //拿到映射配置文件的路径
            ...接下来看下面的分析准备解析映射配置文件
         catch (Exception e) 
            e.printStackTrace();
        
    

4.5.3 改造Configuration对象,使它具备获取数据库链接Connection的功能

在Configuration对象中创建ComboPooledDataSource对象,并创建获得数据源的方法getDataSource,再创建一个获得Connection的方法getConnection,getConnection通过调用getDataSource获得数据源,然后获得Connection对象。

public class Configuration 
    //数据库用户名
    private String username;
    //数据库用户密码
    private String password;
    //数据库连接地址
    private String url;
    //数据库驱动
    private String driver;
    //创建数据源
    private ComboPooledDataSource dataSource = new ComboPooledDataSource();
    //get..set..toString..
    //获取数据源
    private DataSource getDataSource()
        //设置数据源配置
        try 
            dataSource.setUser(username);
            dataSource.setPassword(password);
            dataSource.setJdbcUrl(url);
            dataSource.setDriverClass(driver);
         catch (PropertyVetoException e) 
            e.printStackTrace();
        
        return dataSource;
    
    public Connection getConnection()
        try 
            return getDataSource().getConnection();
         catch (SQLException e) 
            e.printStackTrace();
        
        return null;
    

4.5.4 解析UserMapper.xml,提取SQL语句和返回参数类型

UserMapper.xml内容:

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.itheima.mapper.UserMapper">
    <!--findAll-->
    <select id="findAll" resultType="com.itheima.domain.User">
        SELECT * FROM  user
    </select>
</mapper>

接着上面流程,我们需要解析UserMapper.xml获取SQL语句,并获取返回值的类型,这个时候我们可以考虑封装一个Mapper对象,存储对应的SQL语句和返回值类型。针对这个操作实现,我们可以分为下面几个步骤实现:

  • 解析UserMapper.xml获得SQL语句和返回类型的全限定名
  • 创建Mapper对象,存储SQL语句和返回类型的全限定名
  • 将解析的结果封装到Mapper中
4.5.4.1 创建Mapper对象,存储SQL语句和返回类型的全限定名

Mapper对象用于存储SQL语句和返回的JavaBean全限定名,这时候我们可以考虑定义2个属性来接收存储。

public class Mapper  
    //执行的SQL语句
    private String sql;
    //执行SQL语句后要返回的JavaBean全限定名
    private String resultType;
	//带参构造函数
	public Mapper(String sql, String resultType) 
        this.sql = sql;
        this.resultType = resultType;
    
    Mybatis源码剖析:传统开发方式源码剖析

MyBatis深入剖析应用分析与最佳实践(下)

剖析Tomcat核心思想和源码

全方位深度剖析PHP7底层源码

[SpringBoot]深入浅出剖析SpringBoot的应用类型识别机制

[SpringBoot]深入浅出剖析SpringBoot中Spring Factories机制