Mybatis 快速入門(XML方式) 底層源碼對象初始化過程分析


導讀

官網地址

https://mybatis.org/mybatis-3/zh/index.html

架構原理圖

說明

mybatis配置文件

  1. SqlMapConfig.xml,此文件為mybatis的全局配置文件,配置了mybatis的運行環境等信息
  2. XXXMapper.xml,此文件作為mybatis的sql映射文件,文件中配置了操作數據庫的CRUD語句。需要在SqlMapConfig.xml中加載

SqlSessionFactory

  1. 通過mybatis環境等配置信息構造SqlSessionFactory,既會話工廠

***跟底層源碼查看創建SqlSessionFactory流程***

注:底層如何獲取標簽值,請自行研究(劇透:for循環遍歷XML獲取標簽中的值,然后放入Map)!~

SqlSession

  1. 通過會員工廠創建SqlSession即會話,程序通過SqlSession會話接口對數據庫進行CRUD操作。

Executor執行器

  mybatis底層自定義了Executor執行器接口來具體操作數據庫,Executor接口有兩個實現,一個是基本執行器(默認),一個緩存執行器,SqlSession底層是通過executor接口操作數據庫

Mapped Statement

  他是mybatis一個底層封裝對象,包裝了mybatis配置信息及XXXMapper.xml映射文件等。XXXMapper.xml文件中一個個select/insert/update/delete標簽對應一個Mapped Statement對象

原始JDBC代碼

  原始JDBC和mybatis操作數據庫數據,與上面架構圖流程相對應。

public class JDBCTest {

    public static void main(String[] args) {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            // 加載數據庫驅動
            Class.forName("com.mysql.jdbc.Driver");

            // 通過驅動管理類獲取數據庫鏈接connection = DriverManager
            connection = DriverManager.getConnection(
                              "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8",
                             "root", 
                              "root"
                              );

            // 定義sql語句 ?表示占位符
            String sql = "select * from user where username = ?";
            // 獲取預處理 statement
            preparedStatement = connection.prepareStatement(sql);
            
            // 設置參數,第一個參數為 sql 語句中參數的序號(從 1 開始),第二個參數為設置的
            preparedStatement.setString(1, "王五");
            // 向數據庫發出 sql 執行查詢,查詢出結果集
            resultSet = preparedStatement.executeQuery();
            // 遍歷查詢結果集
            while (resultSet.next()) {
                System.out.println(
                                  resultSet.getString("id") 
                                  + " " + 
                                  resultSet.getString("username")
                     );
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 釋放資源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    // TODO Auto-generated catch block e.printStackTrace();
                }
            }
        }
    }
}

Mybatis 入門基礎

表結構

 表數據

Mybatis環境搭建 

添加依賴

pom.xml

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cyb</groupId>
    <artifactId>mybatis</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <!-- mybatis依賴 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- mysql依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

        <!-- 單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>mybatis</finalName>
    </build>
</project>

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>
    <!-- 引入外部配置文件 -->
    <properties resource="db.properties"></properties>
    <!-- 數據庫鏈接相關 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}" />
                <property name="url" value="${db.url}" />
                <property name="username" value="${db.username}" />
                <property name="password" value="${db.password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 添加映射文件 -->
        <mapper resource="UserMapper.xml" />
    </mappers>
</configuration>

db.properties

db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3306/cyb
db.username=root
db.password=root

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">
<!-- namespace:為了分類管理映射文件中的MappedStatement -->
<mapper namespace="test">
<select id="queryUserById" parameterType="int" resultType="com.cyb.mybatis.demo.User">
        select * from user where id = #{id} 
    </select>
</mapper>

User.java

package com.cyb.mybatis.demo;

import java.util.Date;

public class User {
    private int id;
    private String username;
    private Date birthday;
    private int sex;
    private String address;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public int getSex() {
        return sex;
    }
    public void setSex(int sex) {
        this.sex = sex;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address="
                + address + "]";
    }
}

MybatisDemo.java

package com.cyb.mybatis.demo;

import java.io.InputStream;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

public class MybatisDemo {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception {
        //指定全局配置文件路徑
        String resource = "SqlMapConfig.xml";
        //加載資源文件(包括全局文件和映射文件)
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //使用構建者模式創建SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testSelect() {
        //由SqlSessionFactory工廠去創建SqlSession(會話)
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //調用SqlSession接口,去實現數據庫的CRUD
        User user = sqlSession.selectOne("test.queryUserById", 1);
        System.out.println(user);
        //釋放資源
        sqlSession.close();
    }
}

項目結構

測試

功能實現

根據用戶id查詢一個用戶信息

根據用戶名稱模糊查詢用戶信息列表

添加用戶

主鍵返回

    <!-- 主鍵返回 -->
    <insert id="insertUser" parameterType="com.cyb.mybatis.demo.User">
        <!-- selectKey將主鍵返回,需要再返回 -->
        <selectKey keyProperty="id" order="AFTER"
            resultType="java.lang.Integer">
            select LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address});
    </insert>

添加selectKey標簽實現主鍵返回。

* keyProperty:指定返回的主鍵,存儲在pojo中的哪個屬性

* orderselectKey標簽中的sql的執行順序,是相對與insert語句來說。由於mysql的自增原理,執行完insert語句之后才將主鍵生成,所以這里selectKey的執行順序為after。

* resultType:返回的主鍵對應的JAVA類型

* LAST_INSERT_ID():是mysql的函數,返回auto_increment自增列新記錄id值。

更新用戶

刪除用戶

Mybatis開發Dao層

mapper代理開發方式

XML方式

使用:只需要開發Mapper接口(Dao接口)和xxxMapper約束文件,不需要編寫實現類。

開發規范:

  1. Mapper接口的類路徑xxxMapper.xml文件中的namespace相同
  2. Mapper接口方法名稱xxxMapper.xml中定義的每個statementid相同
  3. Mapper接口方法的輸入參數類型xxxMapper.xml中定義的每個sql的parameterType的類型相同
  4. Mapper接口方法的返回值類型xxxMapper.xml中定義的每個sql的resultType類型相同

pom.xml

<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/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.cyb</groupId>
    <artifactId>mybatis</artifactId>
    <packaging>war</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatis Maven Webapp</name>
    <url>http://maven.apache.org</url>
    <dependencies>
        <!-- mybatis依賴 -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>
        <!-- mysql依賴 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

        <!-- 單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>mybatis</finalName>
    </build>
</project>

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>
    <!-- 引入外部配置文件 -->
    <properties resource="db.properties"></properties>
    <!-- 數據庫鏈接相關 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}" />
                <property name="url" value="${db.url}" />
                <property name="username" value="${db.username}" />
                <property name="password" value="${db.password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!-- 添加映射文件 -->
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>
</configuration>

db.properties

db.driver=com.mysql.jdbc.Driver
db.url=jdbc:mysql://127.0.0.1:3306/cyb
db.username=root
db.password=root

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">
<!-- namespace:為了分類管理映射文件中的MappedStatement -->
<mapper namespace="com.cyb.mybatis.mapper.UserMapper">
    <!-- 通過ID查詢 -->
    <select id="queryUserById" parameterType="int"
        resultType="com.cyb.mybatis.demo.User">
        select * from user where id = #{id}
    </select>
</mapper>

Use.java

package com.cyb.mybatis.demo;

import java.util.Date;

public class User {
    private int id;
    private String username;
    private Date birthday;
    private int sex;
    private String address;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
    public int getSex() {
        return sex;
    }
    public void setSex(int sex) {
        this.sex = sex;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
    @Override
    public String toString() {
        return "User [id=" + id + ", username=" + username + ", birthday=" + birthday + ", sex=" + sex + ", address="
                + address + "]";
    }
}

UserMapper.java

package com.cyb.mybatis.mapper;

import com.cyb.mybatis.demo.User;

public interface UserMapper {
    /**
     * 根據用戶id查詢用戶信息
     * @param id 內碼
     * @return
     * @throws Exception
     */
    public User queryUserById(int id) throws Exception;
}

MybatisDemo.java

package com.cyb.mybatis.demo;

import java.io.InputStream;
import java.util.Date;
import java.util.List;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Before;
import org.junit.Test;

import com.cyb.mybatis.mapper.UserMapper;

public class MybatisDemo {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception {
        // 指定全局配置文件路徑
        String resource = "SqlMapConfig.xml";
        // 加載資源文件(包括全局文件和映射文件)
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 使用構建者模式創建SqlSessionFactory
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testSelect() throws Exception {
        // 由SqlSessionFactory工廠去創建SqlSession(會話)
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 調用SqlSession接口,去實現數據庫的CRUD
        UserMapper userMapper=sqlSession.getMapper(UserMapper.class);    
        User user = userMapper.queryUserById(1);
        System.out.println(user);
        // 釋放資源
        sqlSession.close();
    }
}

項目結構

 

測試 

全局配置文件

properties標簽

  可以引入java屬性文件中的配置信息

typeAlias標簽

  別名的作用:為了簡化映射文件中parameterType和resultType中POJO類型的包名

默認支持別名

批量指定別名(推薦)

注:可以寫多個package,但是package和typeAlias不能一起用!!!

單個指定別名(typeAlias)

注:可以寫多個typeAlias,但是package和typeAlias不能一起用!!!

mappers標簽

<mapper resource="" /> (不推薦)

 

注:一次加載一個映射文件,相當於資源路徑

<package name="" />(推薦

  注冊指定包下的所有mapper接口,來加載mapper映射文件。

注:mapper接口和mapper映射文件名稱相同,且放到同一目錄下

關聯查詢

一對多

Collection標簽:定義了一對多關聯的結果映射。
property="orders":關聯查詢的結果集存儲在User對象的上哪個屬性。
ofType="orders":指定關聯查詢的結果集中的對象類型即List中的對象類型。此處可以使用別名,也可以使用全限定名。

源碼部分

構建SqlSessionFactory過程

  下面我們具體的查看下是如何構建SqlSessionFactory對象

  通過源碼可以看出,最終生成一個DefaultSqlSessionFactory實例,這個不是我們關注的重點,我們核心是關注如何初始化對象的。

構建XMLConfigBuilder對象

  build函數首先會構造一個XMLConfigBuilder對象,他是解析XML配置文件的。

 

  • XMLxxxBuilder:解析XML配置文件的,不同類型的XMLxxxBuilder解析不同的部位
    • XMLConfigBuilder:解析mybatis全局配置文件
    • XMLMapperBuilder:解析mybatis映射文件
    • XMLStatementBuilder:解析映射文件中statement語句(CRUD Sql語句)
    • MapperBuilderAssistant:輔助解析映射文件並生成MappedStatement對象

這些XMLxxxBuilder都有一個共同的父類->BaseBuilder這個父類維護了全局的Configuration對象,mybatis的配置文件解析后就以Configuration對象的形式存儲。   

 

加載映射文件

  底層代碼量較多,錄制的gif圖片較大不能上傳,分3段上傳的,內容都是連續的;這里拋塊磚,實際還是要自己打個斷點,跟蹤下底層源碼,根據自身愛好,研究相應內容,准備工作:了解知識點:XPath(如,解析XML配置文件,點我直達)、設計模式(如:構建者模式,點我直達)、面向對象等等,要不然跟蹤源碼是件很痛苦的事兒~~~

構建者模式使用地方

XPath使用地方

補充

注意:此處用到了遍歷list節點,因為映射文件有很多的select、update、delete標簽哦~

打開session會話

  我們可以看到,SqlSessionFactory是一個接口,里面有很多重載方法

SqlSessionFactory接口重載說明

 

跟蹤到SqlSessionFactory的實現類DefaultSqlSessionFactory

  通過跟蹤到DefaultSqlSessionFactory,我們可以看到,底層使用了重載,默認自動提交事務未false,從而解答了,我們使用默認無參構造函數時,對數據庫進行:插入、修改、刪除,需要手動提交事務

  細心的小伙伴此時會問,問什么跟蹤源碼的時候,他不是有兩個實現類嗎,為什么要跟蹤上面一個,不跟蹤下面那個,是因為創建SqlSessionFactory的時候,默認返回的是DefaultSqlSessionFactory

跟蹤selectOne

selectList分析

 

我們跟蹤下,是如何從Configuration全局配置文件中(從Map中獲取),獲取MapperStatement的;注:這個全局配置文件(configuration)是在初始化SqlSessionFactory時加載的

接下來我們跟蹤下,是如何解析傳遞的參數的,分3種處理情況,分別為:集合、list、其他;注:參數類型與xxxMapper.xml的parameterType類型想對應,傳什么類型,走什么情況

 

跟蹤executor.query

  先走下面那個實現類,在走上面那個實現類

 

為什么先走下面那個實現類呢?看gif,先委托下面那個實現類

設置參數綁定

  打個斷點,調試下就知道了!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM