Mybatis源碼詳解系列(一)--持久層框架解決了什么及如何使用Mybatis


簡介

mybatis 是一個持久層框架,它讓我們可以方便、解耦地操作數據庫。 相比 hibernate,mybatis 在國內更受歡迎,而且 mybatis 更面向數據庫,可以靈活地對 sql 語句進行優化。

針對 mybatis 的分析,我會拆分成使用、配置、源碼、生成器等部分,都放在 Mybatis 這個系列里,內容將持續更新。這篇博客是系列里的第一篇文章,將從下面兩個問題展開 :

  1. 為什么要用持久層框架?

  2. 如何使用 mybatis?

為什么要用持久層框架

在分析如何用之前,我們先來分析為什么要用?和往常一樣,我們只會回答為什么要使用某種類庫,而不會去回答為什么要使用某個具體的類庫。說來慚愧,用了這么久 mybatis,我從來沒有認真思考過這個問題,要不是寫這篇博客的緣故,估計永遠也不會思考。

我們與數據庫的交互過程

首先,先從一張圖開始,這張圖大致描述了我們與數據庫交互的過程。這里我結合查詢員工的例子來說明。

mybatis_demo_01

根據分層抽象的思想,接口 B 不應該對外暴露任何的數據庫細節,從調用者的角度看,我們只需要想着從一堆員工里找出符合條件的那幾個就行,而不需要去考慮從哪查、如何查。接口示例如下:

List<Employee> queryListByNameAndGenderAndPhone(String name, Integer gender, String phone);

而在接口 B 里面,邏輯就比較復雜了,需要先將入參對象映射到 sql 里面,然后執行 sql,最后將結果集映射為出參對象。那么,這段邏輯應該怎么實現呢?

使用JDBC實現接口B

在沒有持久層框架之前,最直接的做法就是用 JDBC API 來實現接口 B 的邏輯。代碼大致如下:

    public List<Employee> queryListByNameAndGenderAndPhone(String name, Integer gender, String phone) throws SQLException{
        
        String sql = "select * from demo_employee where name = ? and gender = ? and phone = ? and deleted = 0";
        
        // 獲得連接、語句對象
        PreparedStatement statement = connection.prepareStatement(sql);
        
        // 入參映射
        statement.setString(1, name);
        statement.setInt(2, gender);
        statement.setString(3, phone);
        
        // 執行sql
        ResultSet resultSet = statement.executeQuery();
        
        // 出參映射
        List<Employee> employees = new ArrayList<>();
        while(resultSet.next()) {
            Employee employee = new Employee();
            employee.setId(resultSet.getString("id"));
            employee.setName(resultSet.getString("name"));
            employee.setGender(resultSet.getInt("gender"));
            employee.setNo(resultSet.getString("no"));
            employee.setAddress(resultSet.getString("address"));
            employee.setDeleted(resultSet.getInt("deleted"));
            employee.setDepartmentId(resultSet.getString("department_id"));
            employee.setPassword(resultSet.getString("password"));
            employee.setPhone(resultSet.getString("phone"));
            employee.setStatus(resultSet.getInt("status"));
            employee.setGmtCreate(resultSet.getDate("gmt_create"));
            employee.setGmtModified(resultSet.getDate("gmt_modified"));
            employees.add(employee);
        }
        
        // 釋放資源
        resultSet.close();
        statement.close();
        
        return employees;
    }

我們會發現,直接使用 JDBC API 操作數據庫還是比較繁瑣,尤其是出入參的映射。如果每個持久層方法都這么搞,麻煩程度可想而知。出於程序員的本能,我們自然會想要優化它。當我們嘗試從這類代碼中抽取共性時,將會發現一些規律:查詢員工也好,查詢部門也好,它們用的 JDBC 代碼幾乎是一模一樣的,要說不一樣的地方,只有三個:

  1. 我們要執行什么 sql
  2. 入參對象如何映射進 sql
  3. 結果集如何映射成出參對象

基於以上思考,這里提供其中一種優化思路:我們可以將 JDBC 代碼抽取為公共方法,而“三個不同“采用單獨配置實現。mybatis、hibernate 就是按照這種思路設計的。

其實,分析到這一步,我們已經得到了答案,使用持久層框架本質上就是為了更簡單地實現接口 B

使用mybatis實現接口B

接下來,我們看看 mybatis 是如何實現上述思路的。

使用 mybatis 實現 B 接口時,我們不需要寫任何 JDBC 代碼,甚至 B 接口的實現類都不需要我們自己寫,只需要把“三個不同“配置好就行(xml 或注解配置),mybatis 會自動幫我們生成 B 接口的實現類

以下是 mybatis 注解版的員工查詢,兩個注解就完成了“三個不同”的配置,除此之外,我不需要增加任何多余代碼,是不是相當方便呢?

    @Select("select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0")
    @ResultType(Employee.class)
    List<Employee> queryListByNameAndGenderAndPhone(@Param("name") String name, @Param("gender") Integer gender, @Param("phone") String phone);

所以,使用持久層框架,可以讓我們更方便、更解耦地操作數據庫。任何的持久層框架,都不應該脫離這個核心。

搞清楚為什么要用之后,我們接着分析如何用。

項目環境

這里先介紹下項目的一些“基礎設施”。為了讓 mybatis 更加純粹,本項目不會引入任何的依賴注入框架,將使用 mybatis 原生的 API。

工程環境

JDK:1.8.0_231

maven:3.6.3

IDE:Spring Tool Suites4 for Eclipse 4.12 (裝有 Mybatipse 插件)

mysql:5.7.28

依賴引入

mybatis 有自帶的數據源,但實際項目中建議還是引入第三方的比較好。這里使用 HikariCP。

        <!-- Mybatis -->
        <dependency>
            <groupId>org.Mybatis</groupId>
            <artifactId>Mybatis</artifactId>
            <version>3.5.4</version>
        </dependency>
        <!-- 數據庫驅動-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.15</version>
        </dependency>
        <!-- 數據庫連接池 -->
        <dependency>
            <groupId>com.zaxxer</groupId>
            <artifactId>HikariCP</artifactId>
            <version>2.6.1</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

數據庫腳本

本項目的數據庫概念模型如下。為了盡可能模擬出更多的場景,本項目給出的表會比較復雜,涉及到 4 張主表和 2 張中間表,具體的 sql 腳本也提供好了(腳本路徑):

Mybatis_demo項目的數據庫概念模型

入門例子

入門例子需要實現:從數據庫中查詢出符合條件的員工。

配置configuration xml文件

首先,需要在 classpath (一般為 src\main\resources 目錄)下新增 mybatis 的主配置文件 mybatis-config.xml,具體文件名無要求,隨便取都行。配置文件的層級結構如下(注意:配置文件里的 xml 節點必須嚴格按以下順序寫,不然會報SAXParseException ):

configuration(配置)

  • properties(全局參數)
  • settings(全局行為配置)
  • typeAliases(類型別名)
  • typeHandlers(類型處理器)
  • objectFactory(對象工廠)
  • plugins(插件)
  • environments(環境配置)
    • environment(環境變量)
      • transactionManager(事務管理器)
      • dataSource(數據源)
  • databaseIdProvider(數據庫廠商標識)
  • mappers(映射器)

作為入門例子,這里只進行了簡單的配置(實際項目中其實也差不多,一般也只會用到加粗的幾個節點)。

<?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>
    <!-- 全局配置 -->
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
    </settings>
    
    <!-- 配置環境:可以有多個 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事務管理 -->
            <transactionManager type="JDBC"/>
            <!-- 數據源 -->
            <!-- <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.username}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource> -->
            <dataSource type="cn.zzs.mybatis.factory.HikariDataSourceFactory" />
        </environment>
    </environments>
    
    <!-- 映射器 -->
    <mappers>
        <package name="cn.zzs.mybatis.mapper"/>
    </mappers>
</configuration>

以上配置作用如下:

  1. settings:一些影響 mybatis 行為的全局配置。例如,上面配置了 mapUnderscoreToCamelCase 為 true,在進行結果集映射時,只要對象字段和數據庫字段之間遵循駝峰命名,mybatis 就能自動將它們映射好,而不需要我們手動配置映射。可配置參數見org.apache.ibatis.builder.xml.XMLConfigBuilder.settingsElement(Properties)
  2. environments:環境配置。這里簡單說下 dataSource,如果使用 mybatis 自帶的數據源,則直接配置數據源參數就行,如果引入第三方數據源,則需要配置你自己重寫的org.apache.ibatis.datasource.DataSourceFactory實現類。建議使用第三方數據源。
  3. mappers:其實就是告訴 mybatis 你的 Mapper 接口和 Mapper xml 文件放在哪里。這里需要注意下,如果我們把 Mapper 接口放在 src/main/java 下的 cn.zzs.mybatis.mapper,那么 Mapper xml 文件就應該放在 src\main\resources 下的 cn\zzs\mybatis\mapper。當然,你也可以把 Mapper xml 文件放在 src/main/java 下的 cn.zzs.mybatis.mapper(不建議),這種情況你就需要在 pom.xml 文件中額外增加以下配置。事實上,你怎么騷操作都可以,只要保證項目編譯打包后 Mapper 接口和 xml 文件在你指定的路徑下就行。
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/mapper/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**</include>
                </includes>
            </resource>
        </resources>
    </build>

創建Mapper接口

然后,我們需要在指定的包下創建好 EmployeeMapper 接口。上面的配置文件中,我們已經指定 Mapper 接口放在cn.zzs.Mybatis.mapper包下面,所以要保持一致才行。這里的@Param用來給入參配置別名。

public interface EmployeeMapper {
    List<Employee> queryListByNameAndGenderAndPhone(@Param("name") String name, @Param("gender") Integer gender, @Param("phone") String phone);
}

配置mapper xml文件

接着,我們需要在指定路徑下創建好 EmployeeMapper.xml 文件。EmployeeMapper.xml 只有很少的幾個頂級元素:

  • cache – 該命名空間的緩存配置。
  • cache-ref – 引用其它命名空間的緩存配置。
  • resultMap – 描述如何將結果集映射為出參對象。
  • parameterMap – 此元素已被廢棄,並可能在將來被移除!
  • sql – 可被其它語句引用的可重用語句塊。
  • insert – 映射插入語句。
  • update – 映射更新語句。
  • delete – 映射刪除語句。
  • select – 映射查詢語句。

作為入門例子,這里只給出了簡單的配置。其中,select 節點中配置了 queryListByNameAndGenderAndPhone 方法的“三個不同”,即需要執行的 sql、入參映射和出參映射。

<?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="cn.zzs.mybatis.mapper.EmployeeMapper">
    <select id="queryListByNameAndGenderAndPhone" parameterType="map" resultType="cn.zzs.mybatis.entity.Employee">
        select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
    </select>
</mapper>

在本例中,我們指定了把結果集映射為Employee,通常情況下,只要對象字段和數據庫字段遵循駝峰命名,都能自動映射成功。但是,總會有例外嘛,如果我的字段命名不遵循駝峰命名怎么辦?這里我給出了兩種可選方案:

    <!-- 特殊字段的映射方法1:resultMap -->
    <resultMap id ="BaseResultMap" type = "cn.zzs.mybatis.entity.Employee">
        <result column="gmt_create" property="create" />
    </resultMap>
    
    <select id="queryListByNameAndGenderAndPhone" parameterType="map" resultMap="BaseResultMap">
        select * from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
    </select>

    <!-- 特殊字段的映射方法2:as -->
    <select id="queryListByNameAndGenderAndPhone" parameterType="map" resultType="cn.zzs.mybatis.entity.Employee">
        select *, gmt_create as `create` from demo_employee where name = #{name} and gender = #{gender} and phone = #{phone} and deleted = 0
    </select>

注意下參數符號 #{name}, 它告訴 mybatis 創建一個預處理語句(PreparedStatement)參數,在 JDBC 中,這樣的一個參數在 SQL 中會由一個“?”來標識,並被傳遞到一個新的預處理語句中。不過有時你就是想直接在 SQL 語句中直接插入一個不轉義的字符串。 這時候你可以使用 “$” 字符:

ORDER BY ${columnName}

編寫測試類

在以下代碼中,存在四個主要對象:

  1. SqlSessionFactoryBuilder :一旦創建了 SqlSessionFactory,就不再需要它了,因此 SqlSessionFactoryBuilder 實例的最佳作用域是方法作用域。
  2. SqlSessionFactory:一旦被創建就應該在應用的運行期間一直存在,沒有任何理由丟棄它或重新創建另一個實例, 因此 SqlSessionFactory 的最佳作用域是應用作用域。
  3. SqlSession:每個線程都應該有它自己的 SqlSession 實例。SqlSession 實例不是線程安全的,因此不能被共享,所以它的最佳的作用域是請求或方法作用域。 SqlSession 的作用類似於 JDBC 的 Connection ,使用完后必須 close。
  4. EmployeeMapper默認情況下,因為 EmployeeMapper 和 SqlSession 綁定在一起,所以,EmployeeMapper 也是線程不安全的。有的人可能會問,既然 EmployeeMapper 是線程不安全的,那為什么 spring 把它作為單例使用?其實並不矛盾,EmployeeMapper 是否線程安全取決於 SqlSession,而 spring 中使用的 SqlSession 實現類是線程安全的,原理也並不復雜,具體見org.mybatis.spring.SqlSessionTemplate
public class EmployeeMapperTest {
    
    @Test
    public void testSample() throws SQLException {
        // 加載配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        
        // 獲取sqlSession
        // 第一種獲取sqlSession方法:同一線程每次獲取都是同一個sqlSession
        /*SqlSessionManager sqlSessionManager = SqlSessionManager.newInstance(inputStream);
        SqlSession sqlSession = sqlSessionManager.openSession();*/
        
        // 第二種獲取sqlSession方法:同一線程每次獲取都是不同的sqlSession
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        
        // 獲取Mapper代理類
        EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
        
        // 執行查詢
        List<Employee> employees = employeeMapper.queryListByNameAndGenderAndPhone("zzs001", 1, "18826****41");
        
        // 測試
        assertTrue(!CollectionUtils.isEmpty(employees));
        employees.stream().forEach(System.err::println);
        
        // 釋放資源
        sqlSession.close();
    }
}

運行測試

運行上面的方法,會在控制台輸出了 sql 和匹配的員工數據。

==>  Preparing: select * from demo_employee where name = ? and gender = ? and phone = ? and deleted = 0 
==> Parameters: zzs001(String), 1(Integer), 18826****41(String)
<==      Total: 1
Employee(id=cc6b08506cdb11ea802000fffc35d9fa, name=zzs001, gender=1, no=zzs001, password=666666, phone=18826****41, address=北京, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Wed Sep 04 21:48:28 CST 2019, gmtModified=Wed Mar 25 10:44:51 CST 2020)

高級條件查詢

在實際項目中,我們經常需要用到高級條件查詢。這類查詢和入門例子的查詢最大的不同在於,高級條件查詢的某個條件可能為空,所以,在配置入參映射時需要進行判斷。mybatis 提供了豐富的動態 sql 語法以支持此類入參映射,如下:

    <select id = "queryByCondition" parameterType="cn.zzs.mybatis.condition.EmployeeCondition" resultType="cn.zzs.mybatis.entity.Employee">
        select e.* from demo_employee e where 1 = 1 
        <!-- 一般條件 -->
        <if test="con.deleted != null">
            and e.deleted = #{con.deleted}
        </if>
        <!-- 字符類條件 -->
        <if test="con.name != null and con.name != ''">
            and e.name = #{con.name}
        </if>
        <!-- 大於類條件 -->
        <if test="con.createStart != null">
            and e.gmt_create &gt; #{con.createStart}
        </if>
        <!-- 集合類條件 -->
        <if test="con.phoneInclude != null and con.phoneInclude.size() > 0">
            and e.phone in 
            <foreach collection="con.phoneInclude" index="index" item="item" open="(" separator="," close=")">
                #{item}
            </foreach>
        </if>
    </select>

多表查詢

大多數情況下,我們對外提供的查詢員工接口,只是返回員工的字段是遠遠不夠的,還需要返回員工關聯對象的字段,例如,員工所屬部門、員工管理的菜單等等。

這時,我們的查詢將涉及到多個表,針對這種情況,我認為有三種解決方案:

  1. 手動分表查詢;
  2. 自動分表查詢;
  3. 聯表查詢。

手動分表查詢

手動分表查詢代碼如下。顧名思義,我需要在代碼中單獨查每個表,再將查到的數據組裝起來。

    @Test
    public void testMultiTableQuery1() throws Exception {
        // 先查員工
        Employee employee = employeeMapper.queryById("cc6b08506cdb11ea802000fffc35d9fe");
        
        // 再查部門
        Department department = departmentMapper.queryById(employee.getDepartmentId());
        
        // 組裝數據
        EmployeeVO employeeVO = new EmployeeVO();
        BeanUtils.copyProperties(employeeVO, employee);
        employeeVO.setDepartmentName(department.getName());
        employeeVO.setDepartmentNo(department.getNo());
        
        // 測試
        System.err.println(employeeVO);
    }

這種方案的優點在於;

  1. 如果后期多表分庫,代碼無需改動
  2. Mapper 接口中只要提供通用的基本方法就行,無需增加任何個性化方法
  3. 在應對各種場景的不同 VO 時,這種方案非常靈活

至於缺點嘛,就是會遇到N+1 查詢問題,列表查詢尤甚。

自動分表查詢

自動分表查詢的代碼如下。與手動分表查詢不同,這里的查部門,不是我們自己手動查,而是由 mybatis 幫我們查。

    @Test
    public void testMultiTableQuery2() throws Exception {
        // 查員工(部門也會一起查)
        Employee employee = employeeMapper.queryById("cc6b08506cdb11ea802000fffc35d9fe");
        
        // 取出部門對象
        Department department = employee.getDepartment();
        
        // 組裝數據
        EmployeeVO employeeVO = new EmployeeVO();
        BeanUtils.copyProperties(employeeVO, employee);
        employeeVO.setDepartmentName(department.getName());
        employeeVO.setDepartmentNo(department.getNo());
        
        // 測試
        System.err.println(employeeVO);
    }

與手動分表查詢不同,這里第一步返回的 employee 是一個非常“完整”的對象,它包括部門、角色等,如果你使用過 hibernate,對此應該並不陌生。所以,在某種程度上,這種方案更加面向對象。為了查出這樣一個“完整”的對象,我們需要進行如下配置:

    <resultMap id = "EmployResultMap" type = "cn.zzs.mybatis.entity.Employee">
        <result column="id" property="id"/>
        <result column="department_id" property="departmentId"/>
        <association 
            property="department" 
            column="department_id" 
            select="cn.zzs.mybatis.mapper.DepartmentMapper.queryById"/>
        <collection 
            property="roles"
            column="id"
            select="cn.zzs.mybatis.mapper.RoleMapper.queryByEmployeeId"
            />
    </resultMap>
    
    <!-- 多表查詢1和2 -->
    <select id = "queryById" parameterType = "string" resultMap = "EmployResultMap">
        select e.* 
        from demo_employee e 
        where e.id = #{value}
    </select>

需要注意的是,自動分表查詢最好結合延遲加載使用,即當調用 getDepartment 時才觸發查部門的動作。因為不是每種場景都需要查出那么“完整”的對象,延遲加載很好地避免不必要的性能開銷。延遲加載的配置如下(默認不開啟):

    <!-- 全局配置 -->
    <settings>
        <setting name="lazyLoadingEnabled" value="true" />
    </settings>

和手動多表查詢一樣,自動分表查詢不需要在 Mapper 中增加個性化方法,但是呢,因為耦合多表的關聯,所以,如果后期多表分庫,改動就比較大了。而且,在應對各種場景的 VO 時,這種方案靈活性要比手動多表查詢稍差

當然,這種方案也有N+1查詢問題

聯表查詢

聯表查詢代碼如下。這種方案不需要分表查詢,多表數據一次查出。

    @Test
    public void testMultiTableQuery3() {
        // 執行查詢
        EmployeeVO employeeVO = employeeMapper.queryVOById("cc6b08506cdb11ea802000fffc35d9fe");
        
        // 測試
        System.err.println(employeeVO);
    }

具體做法就是采用聯表查詢,xml 配置如下。

    <select id = "queryVOById" parameterType = "string" resultType = "cn.zzs.mybatis.vo.EmployeeVO">
        select e.*, d.name as departmentName, d.no as departmentNo 
        from demo_employee e left join demo_department d on e.department_id = d.id 
        where e.id = #{value}
    </select>

因為不需要分表查詢,所以聯表查詢可以避免N+1查詢問題,所以,相比前兩種方案,它的性能一般是最好的。但是呢,聯表查詢嚴重耦合了多表的關聯,如果后期多表分庫,改動會比較大。而且,這種方案的靈活性非常差,它幾乎需要在 Mapper 接口中為每個 VO 提供個性化方法。

以上三種方案,各有優缺點,實際項目中采用哪種,需要結合多表分庫、性能等因素具體分析。

分頁查詢

最后再簡單說下分頁查詢吧。其實,mybatis 提供了RowBounds來支持分頁,但是呢,這種分頁不是數據庫分頁,而是應用內存分頁,非常的不友好,所以,分頁查詢還是得另辟蹊徑。

引入插件依賴

本項目使用 pagehelper 插件來實現分頁,首先,我們需要在 pom.xml 文件中增加以下依賴。

        <!-- 分頁插件 -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.10</version>
        </dependency>

修改 mybatis 主配置文件

然后,在 mybatis 主配置文件中增加 plugins 元素,並配置分頁插件。

    <!-- 配置插件 -->
    <plugins>
        <!-- 分頁插件 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>

編寫測試方法

這里調用 startPage 相當於告訴分頁插件對**本線程的下一個列表查詢 sql **進行分頁處理。

    @Test
    public void testPageQuery() {
        // 構建條件
        EmployeeCondition con = new EmployeeCondition();
        con.setDeleted(0);

        // 設置分頁信息
        PageHelper.startPage(0, 3);

        // 執行查詢
        List<Employee> employees = employeeMapper.queryByCondition(con);
        
        // 遍歷結果
        employees.forEach(System.err::println);

        // 封裝分頁模型
        PageInfo<Employee> pageInfo = new PageInfo<>(employees);

        // 取分頁模型的數據
        System.err.println("結果總數:" + pageInfo.getTotal());
    }

測試

運行上面的方法,會在控制台輸出了 sql 和匹配的員工數據。我們發現,這里竟然多了一句查數量的 sql,而且,列表查詢的 sql 被嵌入了分頁參數。到底是怎么做到的呢?這個問題等我們分析源碼的時候再解答吧。

==>  Preparing: SELECT count(0) FROM demo_employee e WHERE 1 = 1 AND e.deleted = ? 
==> Parameters: 0(Integer)
<==      Total: 1
==>  Preparing: select e.id,e.`name`,e.gender,e.no,e.password,e.phone,e.address,e.status,e.deleted,e.department_id,e.gmt_create,e.gmt_modified from demo_employee e where 1 = 1 and e.deleted = ? LIMIT ? 
==> Parameters: 0(Integer), 3(Integer)
<==      Total: 3
Employee(id=2e18f6560b25473480af987141eccd02, name=zzs005, gender=1, no=zzs005, password=admin, phone=18826****41, address=廣東, status=1, deleted=0, departmentId=94e2d2e56cd811ea802000fffc35d9fa, gmtCreate=Sat Mar 28 00:00:00 CST 2020, gmtModified=Sat Mar 28 00:00:00 CST 2020, department=null, roles=null)
Employee(id=cc6b08506cdb11ea802000fffc35d9fa, name=zzs001, gender=1, no=zzs001, password=666666, phone=18826****42, address=北京, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Wed Sep 04 21:48:28 CST 2019, gmtModified=Wed Mar 25 10:44:51 CST 2020, department=null, roles=null)
Employee(id=cc6b08506cdb11ea802000fffc35d9fb, name=zzs002, gender=1, no=zzs002, password=123456, phone=18826****43, address=廣東, status=1, deleted=0, departmentId=65684a126cd811ea802000fffc35d9fa, gmtCreate=Thu Aug 01 21:49:43 CST 2019, gmtModified=Mon Sep 02 21:49:49 CST 2019, department=null, roles=null)
結果總數:9

結語

以上基本講完為什么要使用持久層框架以及如何使用 mybatis。后續發現其他有趣的地方再做補充,也歡迎大家指正不足的地方。

最后,感謝閱讀。

參考資料

Mybatis官方中文文檔

2021-09-29更改

相關源碼請移步:mybatis-demo

本文為原創文章,轉載請附上原文出處鏈接:https://www.cnblogs.com/ZhangZiSheng001/p/12603885.html


免責聲明!

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



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