架構師之路——MyBatis系列第3篇:Mybatis使用詳解(1)

 

Mybatis系列目標:從入門開始開始掌握一個高級開發所需要的Mybatis技能。

這是mybatis系列第3篇。

主要內容

1、 快速入門

  • 準備數據庫

  • 我們的需求

  • 使用idea創建項目

  • pom.xml中引入mybatis依賴

  • 配置mybatis全局配置文件

  • 創建Mapper xml文件

  • mybatis全局配置文件中引入Mapper xml文件

  • 構建SqlSessionFactory對象

  • 構建SqlSession對象

  • 引入lombok(非必須)

  • 引入logback支持(非必須)

  • 寫一個測試用例

2、 使用SqlSesion執行sql操作

  • SqlSession常見的用法

  • 新增操作

  • 執行刪除

  • 執行修改

  • 執行查詢

3、 Mapper接口的使用

  • 爲什麼需要Mapper接口

  • Mapper接口的用法

  • 案例:使用Mapper接口來實現增刪改查

  • Mapper接口使用時注意的幾點

  • Mapper接口的原理

4、 案例源碼獲取方式

快速入門

準備數據庫

mysql中執行下面sql:

/*創建數據庫javacode2018*/
DROP DATABASE IF EXISTS `javacode2018`;
CREATE DATABASE `javacode2018`;
USE `javacode2018`;

/*創建表結構*/
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE t_user (
  id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主鍵,用戶id,自動增長',
  `name` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` SMALLINT NOT NULL DEFAULT 1 COMMENT '年齡',
  `salary` DECIMAL(12,2) NOT NULL DEFAULT 0 COMMENT '薪水',
  `sex` TINYINT NOT NULL DEFAULT 0 COMMENT '性別,0:未知,1:男,2:女'
) COMMENT '用戶表';

SELECT * FROM t_user;

上面我們創建了一個數據庫:javacode2018,一個用戶表t_user

我們的需求

使用mybatis來實現對t_user表增刪改查。

使用idea創建項目

我們在上一篇文章mybatis-series項目中創建另外一個模塊chat02,過程如下:

選中mybatis-series,如下圖:

點擊右鍵->New->Module,如下圖:

選中上圖中的Maven,點擊Next,如下圖:

出現下面窗口:

上圖中輸入ArtifactIdchat02,點擊Next,如下圖:

點擊上圖中的Finish完成chat02模塊的創建,項目結構如下圖:

pom.xml中引入mybatis依賴

<dependencies>
    <!-- mybatis依賴 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
    </dependency>
    <!-- mysql 驅動 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- lombok支持 -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    <!-- 單元測試junit支持 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <!-- 引入logback用來輸出日誌 -->
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
    </dependency>
</dependencies>

上面我們引入了依賴mybatis、mysql驅動、lombok支持、junit、logback支持,其實運行mybatis只需要引入下面這一個構件就行了:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
</dependency>

注意:上面pom引入的構建中沒有寫版本號,是因爲構件的版本號在父pom.xml中已經聲明瞭,所以chat03/pom.xml中就不需要再去寫了。

配置mybatis全局配置文件

使用mybatis操作數據庫,那麼當然需要配置數據庫相關信息,這個需要在mybatis全局配置文件中進行配置。

mybatis需提供一個全局配置的xml文件,可以在這個配置文件中對mybatis進行配置,如事務的支持,數據源的配置等等,這個屬於配置文件,我們一般放在main/resource中。

chat03/src/main/resource中創建mybatis-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>
    <!-- 環境配置,可以配置多個環境 -->
    <environments default="chat03">
        <!-- 
            environment用來對某個環境進行配置
            id:環境標識,唯一
         -->
        <environment id="chat03">
            <!-- 事務管理器工廠配置 -->
            <transactionManager type="org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory"/>
            <!-- 數據源工廠配置,使用工廠來創建數據源 -->
            <dataSource type="org.apache.ibatis.datasource.pooled.PooledDataSourceFactory">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/javacode2018?characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root123"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

我們做一下解釋。

configuration元素

這個是mybatis全局配置文件的根元素,每個配置文件只有一個

environments元素

用來配置mybatis的環境信息,什麼是環境?比如開發環境、測試環境、線上環境,這3個環境中的數據庫可能是不一樣的,可能還有更多的環境。

environments元素中用來配置多個環境的,具體的一個環境使用environment元素進行配置,environment元素有個id用來標識某個具體的環境。

配置了這麼多環境,那麼mybatis具體會使用哪個呢?

environments元素有個default屬性,用來指定默認使用哪個環境,如上面默認使用的是chat03

environment元素

用來配置具體的環境信息,這個元素下面有兩個子元素:transactionManager和dataSource

  • transactionManager元素

    用來配置事務工廠的,有個type屬性,type的值必須是org.apache.ibatis.transaction.TransactionFactory接口的實現類,TransactionFactory看名字就知道是一個工廠,用來創建事務管理器org.apache.ibatis.transaction.Transaction對象的,TransactionFactory接口默認有2個實現:

    org.apache.ibatis.transaction.managed.ManagedTransactionFactory
    org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory
    

    一般情況下我們使用org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory這個,mybatis和其他框架集成,比如和spring集成,事務交由spring去控制,spring中有TransactionFactory接口的一個實現org.mybatis.spring.transaction.SpringManagedTransactionFactory,有興趣的朋友可以去研究一下,這個到時候講到spring的使用會詳細說。

  • dataSource元素

    這個用來配置數據源的,type屬性的值必須爲接口org.apache.ibatis.datasource.DataSourceFactory的實現類,DataSourceFactory也是一個工廠,用來創建數據源javax.sql.DataSource對象的,mybatis中這個接口默認有3個實現類:

    org.apache.ibatis.datasource.jndi.JndiDataSourceFactory
    org.apache.ibatis.datasource.pooled.PooledDataSourceFactory
    org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory
    

    我們使用第2個org.apache.ibatis.datasource.pooled.PooledDataSourceFactory,這個用來創建一個數據庫連接池類型的數據源,可以實現數據庫連接共用,減少連接重複創建銷燬的時間。

    配置數據源需要指定數據庫連接的屬性信息,比如:驅動、連接db的url、用戶名、密碼,這個在dataSource元素下面的property中配置,property元素的格式:

    <property name="屬性名稱" value="值"/>
    

創建Mapper xml文件

我們需要對t_user表進行操作,需要寫sql,sql寫在什麼地方呢?

在mybatis中一般我們將一個表的所有sql操作寫在一個mapper xml中,一般命名爲XXXMapper.xml格式。

創建文件chat02/src/main/resource/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.javacode2018.chat02.UserMapper">
</mapper>

mapper xml根元素爲mapper,這個元素有個namespace屬性,系統中會有很多表,每個表對應一個Mapper xml,爲了防止mapper文件重複,我們需要給每個mapper xml文件需要指定一個namespace,通過這個可以區分每個mapper xml文件,上面我們指定爲com.javacode2018.chat02.UserMapper

一會對t_user表的所有操作相關的sql,我們都會寫在上面這個xml中。

mybatis全局配置文件中引入Mapper xml文件

UserMapper.xml我們寫好了,如何讓mybatis知道這個文件呢,此時我們需要在mybatis-config.xml全局配置文件中引入UserMapper.xml,在mybatis-config.xml加入下面配置:

<mappers>
    <mapper resource="mapper/UserMapper.xml" />
</mappers>

mappers元素下面有多個mapper元素,通過mapper元素resource屬性可以引入Mapper xml文件,resource是相對於classes的路徑。

上面說的都是一些配置文件,配置文件都ok了,下面我們就需要將mybatis跑起來了,此時需要使用到mybatis中的一些java對象了。

構建SqlSessionFactory對象

//指定mybatis全局配置文件
String resource = "mybatis-config.xml";
//讀取全局配置文件
InputStream inputStream = Resources.getResourceAsStream(resource);
//構建SqlSessionFactory對象
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSessionFactory是一個接口,是一個重量級的對象,SqlSessionFactoryBuilder通過讀取全局配置文件來創建一個SqlSessionFactory,創建這個對象是比較耗時的,主要耗時在對mybatis全局配置文件的解析上面,全局配置文件中包含很多內容,SqlSessionFactoryBuilder通過解析這些內容,創建了一個複雜的SqlSessionFactory對象,這個對象的生命週期一般和應用的生命週期是一樣的,隨着應用的啓動而創建,隨着應用的停止而結束,所以一般是一個全局對象,一般情況下一個db對應一個SqlSessionFactory對象。

構建SqlSession對象

SqlSession相當於jdbc中的Connection對象,相當於數據庫的一個連接,可以用SqlSession來對db進行操作:如執行sql、提交事務、關閉連接等等,需要通過SqlSessionFactory來創建SqlSession對象,SqlSessionFactory中常用的有2個方法來創建SqlSession對象,如下:

//創建一個SqlSession,默認不會自動提交事務
SqlSession openSession();
//創建一個SqlSession,autoCommit:指定是否自動提交事務
SqlSession openSession(boolean autoCommit);

SqlSession接口中很多方法,直接用來操作db,方法清單如下,大家眼熟一下:

<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();

上面以select開頭的可以對db進行查詢操作,insert相關的可以對db進行插入操作,update相關的可以對db進行更新操作。

引入lombok支持(非必須)

聲明一下:lombok不是mybatis必須的,爲了簡化代碼而使用的,以後我們會經常使用。

Lombok能以簡單的註解形式來簡化java代碼,提高開發人員的開發效率。例如開發中經常需要寫的javabean,都需要花時間去添加相應的getter/setter,也許還要去寫構造器、equals等方法,而且需要維護,當屬性多時會出現大量的getter/setter方法,這些顯得很冗長也沒有太多技術含量,一旦修改屬性,就容易出現忘記修改對應方法的失誤。

Lombok能通過註解的方式,在編譯時自動爲屬性生成構造器、getter/setter、equals、hashcode、toString方法。出現的神奇就是在源碼中沒有getter和setter方法,但是在編譯生成的字節碼文件中有getter和setter方法。這樣就省去了手動重建這些代碼的麻煩,使代碼看起來更簡潔些。

lombok的使用步驟

  1. 先在idea中安裝lombok插件

    打開idea,點擊File->Settings->plugins,然後搜索Lombok Plugin,點擊安裝就可以了。

  2. maven中引入lombok支持

    <dependency>
       <groupId>org.projectlombok</groupId>
       <artifactId>lombok</artifactId>
       <version>1.18.10</version>
       <scope>provided</scope>
    </dependency>
    
  3. 代碼中使用lombok相關功能

引入logback(非必須)

聲明一下:日誌框架mybatis中也不是必須的,不用配置也可以正常運行。

爲了方便查看mybatis運行過程中產生的日誌,比如:執行的sql、sql的參數、sql的執行結果等等調試信息,我們需要引入日誌框架的支持,logback是一個很好的日誌框架,此處我們就使用這個

mybatis中集成logback步驟

  1. maven中引入logback支持

    <dependency>
       <groupId>ch.qos.logback</groupId>
       <artifactId>logback-classic</artifactId>
       <version>1.2.3</version>
    </dependency>
    
  2. src/main/resources中創建logback.xml文件:

    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
       <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
           <encoder>
               <pattern>%d{mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
           </encoder>
       </appender>
    
       <logger name="com.javacode2018" level="debug" additivity="false">
           <appender-ref ref="STDOUT" />
       </logger>
    
    </configuration>
    

logback.xml具體的寫法不是本文討論的範圍,有興趣的朋友可以去研究一下logback具體的用法。

上面xml中配置了com.javacode2018包中所有的類,使用logback輸出日誌的時候,debug級別及以上級別的日誌會輸出到控制檯,方便我們查看。

寫一個測試用例

chat02/src/test下創建一個類:

com.javacode2018.chat02.UserTest

內容如下:

package com.javacode2018.chat02;

import lombok.extern.slf4j.Slf4j;
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 java.io.IOException;
import java.io.InputStream;

@Slf4j
public class UserTest {

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        //指定mybatis全局配置文件
        String resource = "mybatis-config.xml";
        //讀取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //構建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Test
    public void sqlSession() {
        SqlSession sqlSession = this.sqlSessionFactory.openSession();
        log.info("{}", sqlSession);
    }

}

上面代碼中有個@Slf4j註解,這個是lombok提供的,可以在這個類中生成下面代碼:

private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(UserTest.class);

運行一下上面的用例:sqlSession方法,輸出如下:

45:51.289 [main] INFO  com.javacode2018.chat02.UserTest - org.apache.ibatis.session.defaults.DefaultSqlSession@1f021e6c

使用SqlSesion執行sql操作

SqlSession常見的用法

SqlSession相當於一個連接,可以使用這個對象對db執行增刪改查操作,操作完畢之後需要關閉,使用步驟:

1.獲取SqlSession對象:通過該sqlSessionFactory.openSession方法獲取SqlSession對象
2.對db進行操作:使用SqlSession對象進行db操作
3.關閉SqlSession對象:sqlSession.close();

常見的使用方式如下:

//獲取SqlSession
SqlSession sqlSession = this.sqlSessionFactory.openSession();
try {
    //執行業務操作,如:增刪改查
} finally {
    //關閉SqlSession
    sqlSession.close();
}

上面我們將SqlSession的關閉放在finally塊中,確保close()一定會執行。更簡單的方式是使用java中的try()的方式,如下:

try (SqlSession sqlSession = this.sqlSessionFactory.openSession();) {
    //執行業務操作,如:增刪改查
}

新增操作

需求:傳入UserModel對象,然後將這個對象的數據插入到t_user表中。

創建一個`UserModel`

新建一個com.javacode2018.chat02.UserModel類,代碼如下:

package com.javacode2018.chat02;

import lombok.*;

/**
 * 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@ToString
public class UserModel {
    private Long id;
    private String name;
    private Integer age;
    private Double salary;
    private Integer sex;
}

這個類的字段和t_user表對應。

UserMapper.xml中定義插入操作

我們說過了,對t_user表的所有sql操作,我們都放在UserMapper.xml中,我們在UserMapper.xml中加入下面配置,使用insert元素定義插入操作:

<!-- insert用來定義一個插入操作
     id:操作的具體標識
     parameterType:指定插入操作接受的參數類型
 -->
<insert id="insertUser" parameterType="com.javacode2018.chat02.UserModel">
    <![CDATA[
    INSERT INTO t_user (id,name,age,salary,sex) VALUES (#{id},#{name},#{age},#{salary},#{sex})
     ]]>
</insert>

insert元素用來定義了一個對db的insert操作

id:是這個操作的一個標識,一會通過mybatis執行操作的時候會通過這個namespace和id引用到這個insert操作,

parameterType:用來指定這個insert操作接受的參數的類型,可以是:各種javabean、map、list、collection類型的java對象,我們這個插入接受的是UserModel對象。

insert元素內部定義了具體的sql,可以看到是一個insert的sql,向t_user表插入數據。

需要插入的值從UserModel對象中獲取,取UserModel對象的的字段,使用#{字段}這種格式可以獲取到UserModel中字段的值。

調用SqlSession.insert方法執行插入操作

t_user插入的sql我們已經在UserMapper中寫好,此時我們怎麼調用呢?

需要調用SqlSession.insert方法:

int insert(String statement, Object parameter)

這個方法有2個參數:

statement:表示那個操作,值爲Mapper xml的namespace.具體操作的id,如需要調用UserMapper.xml中的insertUser操作,這個值就是:

com.javacode2018.chat02.UserMapper.insertUser

parameter:insert操作的參數,和Mapper xml中的insert中的parameterType指定的類型一致。

返回值爲插入的行數。

UserTest類中新增一個測試用例:

@Test
public void insertUser() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(false);) {
        //創建UserModel對象
        UserModel userModel = UserModel.builder().id(2L).name("javacode2018").age(30).salary(50000D).sex(1).build();
        //執行插入操作
        int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
        log.info("插入影響行數:{}", result);
        //提交事務
        sqlSession.commit();
    }
}

運行輸出如下:

01:46.683 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==>  Preparing: INSERT INTO t_user (id,name,age,salary,sex) VALUES (?,?,?,?,?) 
01:46.745 [main] DEBUG c.j.chat02.UserMapper.insertUser - ==> Parameters: 2(Long), javacode2018(String), 30(Integer), 50000.0(Double), 1(Integer)
01:46.751 [main] DEBUG c.j.chat02.UserMapper.insertUser - <==    Updates: 1
01:46.751 [main] INFO  com.javacode2018.chat02.UserTest - 影響行數:1

輸出中打印了詳細的sql語句,以及sql的參數信息,可以看到Mapper xml中的#{}被替換爲了?,這個使用到了jdbc中的PreparedStatement來對參數設置值。

輸出中的第二行詳細列出了參數的值以及每個值的類型。

第三行輸出了insert的結果爲1,表示插入成功了1行記錄。

去db中看一下,如下,插入成功:

mysql> SELECT * FROM t_user;
+----+---------------+-----+----------+-----+
| id | name          | age | salary   | sex |
+----+---------------+-----+----------+-----+
|  1 | 路人甲Java    |  30 | 50000.00 |   1 |
+----+---------------+-----+----------+-----+
1 row in set (0.00 sec)

上面代碼中創建SqlSession,我們使用的是sqlSessionFactory.openSession()創建的,這個方法創建的SqlSession,內部事務是非自動提交的方式,所以需要我們手動提交:

sqlSession.commit();

如果想自動提交事務,可以將上面的測試用例改成下面這樣:

@Test
public void insertUser() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        //創建UserModel對象
        UserModel userModel = UserModel.builder().id(1L).name("路人甲Java").age(30).salary(50000D).sex(1).build();
        //執行插入操作
        int result = sqlSession.insert("com.javacode2018.chat02.UserMapper.insertUser", userModel);
        log.info("影響行數:{}", result);
    }
}

上面在創建SqlSession的時候調用了sqlSessionFactory.openSession(true),指定事務爲自動提交模式,所以最後我們不需要手動提交事務了。

更新操作

需求:傳入UserModel對象,然後通過id更新數據。

UserMapper.xml中定義Update操作

使用update定義更新操作:

<!-- update用來定義一個更新操作
     id:操作的具體標識
     parameterType:指定操作接受的參數類型
 -->
<update id="updateUser" parameterType="com.javacode2018.chat02.UserModel">
    <![CDATA[
    UPDATE t_user SET name = #{name},age = #{age},salary = #{salary},sex = #{sex} WHERE id = #{id}
    ]]>
</update>

寫法和insert操作的寫法類似,指定id標識、parameterType指定操作的參數類型,元素體中是具體的sql語句。

調用SqlSession.update方法執行更新操作

需要調用SqlSession.update方法:

int update(String statement, Object parameter)

這個方法有2個參數:

statement:表示哪個操作,值爲Mapper xml的namespace.具體操作的id,如需要調用UserMapper.xml中的updateUser操作,這個值就是:

com.javacode2018.chat02.UserMapper.updateUser

parameter:update操作的參數,和Mapper xml中的update中的parameterType指定的類型一致。

返回值爲update影響行數。

UserTest類中新增一個測試用例:

@Test
public void updateUser() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        //創建UserModel對象
        UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
        //執行更新操作
        int result = sqlSession.update("com.javacode2018.chat02.UserMapper.updateUser", userModel);
        log.info("影響行數:{}", result);
    }
}

運行輸出:

14:09.051 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==>  Preparing: UPDATE t_user SET name = ?,age = ?,salary = ?,sex = ? WHERE id = ? 
14:09.095 [main] DEBUG c.j.chat02.UserMapper.updateUser - ==> Parameters: 路人甲Java,你好(String), 18(Integer), 5000.0(Double), 0(Integer), 1(Long)
14:09.100 [main] DEBUG c.j.chat02.UserMapper.updateUser - <==    Updates: 1
14:09.101 [main] INFO  com.javacode2018.chat02.UserTest - 影響行數:1

db中去看一下:

mysql> SELECT * FROM t_user;
+----+------------------------+-----+----------+-----+
| id | name                   | age | salary   | sex |
+----+------------------------+-----+----------+-----+
|  1 | 路人甲Java,你好       |  18 |  5000.00 |   0 |
|  2 | javacode2018           |  30 | 50000.00 |   1 |
+----+------------------------+-----+----------+-----+
2 rows in set (0.00 sec)

刪除操作

需求:根據用戶的id刪除對應的用戶記錄

UserMapper.xml中定義Delete操作

使用update元素定義刪除操作:

<!-- update用來定義一個刪除操作
     id:操作的具體標識
     parameterType:指定操作接受的參數類型
 -->
<update id="deleteUser" parameterType="java.lang.Long">
    <![CDATA[
    DELETE FROM t_user WHERE id = #{id}
    ]]>
</update>

寫法和update操作的寫法類似,指定id標識、parameterType指定操作的參數類型,用戶id爲Long類型的,元素體中是具體的delete語句。

調用SqlSession.update方法執行更新操作

需要調用SqlSession.delete方法:

int delete(String statement, Object parameter)

這個方法有2個參數:

statement:表示哪個操作,值爲Mapper xml的namespace.具體操作的id,如需要調用UserMapper.xml中的deleteUser操作,這個值就是:

com.javacode2018.chat02.UserMapper.

parameter:delete操作的參數,和Mapper xml中的delete中的parameterType指定的類型一致。

返回值爲delete影響行數。

UserTest類中新增一個測試用例:

@Test
public void deleteUser() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        //定義需要刪除的用戶id
        Long userId = 1L;
        //執行刪除操作
        int result = sqlSession.delete("com.javacode2018.chat02.UserMapper.deleteUser", userId);
        log.info("影響行數:{}", result);
    }
}

運行輸出:

24:45.427 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==>  Preparing: DELETE FROM t_user WHERE id = ? 
24:45.476 [main] DEBUG c.j.chat02.UserMapper.deleteUser - ==> Parameters: 1(Long)
24:45.485 [main] DEBUG c.j.chat02.UserMapper.deleteUser - <==    Updates: 1
24:45.485 [main] INFO  com.javacode2018.chat02.UserTest - 影響行數:1

執行查詢

需求:查詢所有用戶信息

UserMapper.xml中定義Select操作

<!-- select用來定義一個查詢操作
     id:操作的具體標識
     resultType:指定查詢結果保存的類型
 -->
<select id="getUserList" resultType="com.javacode2018.chat02.UserModel">
    <![CDATA[
    SELECT * FROM t_user
    ]]>
</select>

寫法和update操作的寫法類似,指定id標識、parameterType指定操作的參數類型,resultType指定查詢結果的類型,元素體中是具體的select語句。

調用SqlSession.select方法執行更新操作

UserTest添加一個用例:

@Test
public void getUserList() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        //執行查詢操作
        List<UserModel> userModelList = sqlSession.selectList("com.javacode2018.chat02.UserMapper.getUserList");
        log.info("結果:{}", userModelList);
    }
}

多插入幾行,然後運行上面的用例,輸出如下:

36:39.015 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user 
36:39.048 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters: 
36:39.066 [main] DEBUG c.j.chat02.UserMapper.getUserList - <==      Total: 3
36:39.067 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1)
36:39.069 [main] INFO  com.javacode2018.chat02.UserTest - UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1)

Mapper接口的使用

爲什麼需要Mapper接口

上面我們講解了對一個表的增刪改查操作,都是通過調用SqlSession中的方法來完成的,大家再來看一下SqlSession接口中剛纔用到的幾個方法的定義:

int insert(String statement, Object parameter);
int update(String statement, Object parameter);
int delete(String statement, Object parameter);
<E> List<E> selectList(String statement);

這些方法的特點我們來看一下:

  1. 調用這些方法,需要明確知道statement的值,statement的值爲namespace.具體操作的id,這些需要打開Mapper xml中去查看了才知道,寫起來不方便

  2. parameter參數都是Object類型的,我們根本不知道這個操作具體類型是什麼,需要查看Mapper xml才知道,隨便傳遞個值,可能類型不匹配,但是隻有在運行的時候才知道有問題

  3. selectList方法返回的是一個泛型類型的,通過這個方法我們根本不知道返回的結果的具體類型,也需要去查看Mapper xml才知道

以上這幾點使用都不是太方便,有什麼方法能解決上面這些問題麼?

有,這就是mybatis中的Mapper接口,我們可以定義一個interface,然後和Mapper xml關聯起來,Mapper xml中的操作和Mapper接口中的方法會進行綁定,當我們調用Mapper接口的方法的時候,會間接調用到Mapper xml中的操作,接口的完整類名需要和Mapper xml中的namespace一致。

Mapper接口的用法(三步)

步驟1:定義Mapper接口

去看一下,UserMapper.xml中的namespace,是:

<mapper namespace="com.javacode2018.chat02.UserMapper">

我們創建的接口完整的名稱需要和上面的namespace的值一樣,下面我們創建一個接口com.javacode2018.chat02.UserMapper,如下:

package com.javacode2018.chat02;

/**
 * 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
 */
public interface UserMapper {
}

UserMapper.xml中有4個操作,我們需要在UserMapper接口中也定義4個操作,和UserMapper.xml的4個操作對應,如下:

package com.javacode2018.chat02;

import java.util.List;

/**
 * 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
 */
public interface UserMapper {

    int insertUser(UserModel model);

    int updateUser(UserModel model);

    int deleteUser(Long userId);

    List<UserModel> getUserList();
}

UserMapper接口中定義了4個方法,方法的名稱需要和UserMapper.xml具體操作的id值一樣,這樣調用UserMapper接口中的方法的時候,纔會對應的找到UserMapper.xml中具體的操作。

比如調用UserMapper接口中的insertUser方法,mybatis查找的規則是:通過接口完整名稱.方法名稱去Mapper xml中找到對應的操作。

步驟2:通過SqlSession獲取Mapper接口對象

SqlSession中有個getMapper方法,可以傳入接口的類型,獲取具體的Mapper接口對象,如下:

  /**
   * Retrieves a mapper.
   * @param <T> the mapper type
   * @param type Mapper interface class
   * @return a mapper bound to this SqlSession
   */
  <T> T getMapper(Class<T> type);

如獲取UserMapper接口對象:

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

步驟3:調用Mapper接口的方法對db進行操作

如調用UserMapper接口的insert操作:

@Test
public void insertUser() {
    try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //創建UserModel對象
        UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
        //執行插入操作
        int insert = mapper.insertUser(userModel);
        log.info("影響行數:{}", insert);
    }
}

案例:使用Mapper接口來實現增刪改查

chat02/src/test/java中創建一個測試類,代碼如下:

package com.javacode2018.chat02;

import lombok.extern.slf4j.Slf4j;
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 java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
 */
@Slf4j
public class UserMapperTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        //指定mybatis全局配置文件
        String resource = "mybatis-config.xml";
        //讀取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //構建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Test
    public void insertUser() {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //創建UserModel對象
            UserModel userModel = UserModel.builder().id(System.currentTimeMillis()).name("路人甲Java").age(30).salary(50000D).sex(1).build();
            //執行插入操作
            int insert = mapper.insertUser(userModel);
            log.info("影響行數:{}", insert);
        }
    }

    @Test
    public void updateUser() {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //創建UserModel對象
            UserModel userModel = UserModel.builder().id(1L).name("路人甲Java,你好").age(18).salary(5000D).sex(0).build();
            //執行更新操作
            int result = mapper.updateUser(userModel);
            log.info("影響行數:{}", result);
        }
    }

    @Test
    public void deleteUser() {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //定義需要刪除的用戶id
            Long userId = 1L;
            //執行刪除操作
            int result = mapper.deleteUser(userId);
            log.info("影響行數:{}", result);
        }
    }

    @Test
    public void getUserList() {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //執行查詢操作
            List<UserModel> userModelList = mapper.getUserList();
            userModelList.forEach(item -> {
                log.info("{}", item);
            });
        }
    }
}

大家認真看一下上面的代碼,這次我們使用了UserMapper來間接調用UserMapper.xml中對應的操作,可以去運行一下感受一下效果。

Mapper接口使用時注意的幾點

  1. Mapper接口的完整類名必須和對應的Mapper xml中的namespace的值一致

  2. Mapper接口中方法的名稱需要和Mapper xml中具體操作的id值一致

  3. Mapper接口中方法的參數、返回值可以不和Mapper xml中的一致

Mapper接口的原理

這個使用java中的動態代理實現的,mybatis啓動的時候會加載全局配置文件mybatis-config.xml,然後解析這個文件中的mapper元素指定的UserMapper.xml,會根據UserMapper.xml的namespace的值創建這個接口的一個動態代理,具體可以去看一下mybatis的源碼,主要使用java中的Proxy實現的,使用java.lang.reflect.Proxy類中的newProxyInstance方法,我們可以創建任意一個接口的一個代理對象:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

我們使用Proxy來模仿Mapper接口的實現:

package com.javacode2018.chat02;

import lombok.extern.slf4j.Slf4j;
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 java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

/**
 * 公衆號:路人甲Java,工作10年的前阿里P7分享Java、算法、數據庫方面的技術乾貨!堅信用技術改變命運,讓家人過上更體面的生活!
 */
@Slf4j
public class ProxyTest {

    public static class UserMapperProxy implements InvocationHandler {
        private SqlSession sqlSession;

        private Class<?> mapperClass;

        public UserMapperProxy(SqlSession sqlSession, Class<?> mapperClass) {
            this.sqlSession = sqlSession;
            this.mapperClass = mapperClass;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.debug("invoke start");
            String statement = mapperClass.getName() + "." + method.getName();
            List<Object> result = sqlSession.selectList(statement);
            log.debug("invoke end");
            return result;
        }
    }

    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void before() throws IOException {
        //指定mybatis全局配置文件
        String resource = "mybatis-config.xml";
        //讀取全局配置文件
        InputStream inputStream = Resources.getResourceAsStream(resource);
        //構建SqlSessionFactory對象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Test
    public void test1() {
        try (SqlSession sqlSession = this.sqlSessionFactory.openSession(true);) {
            UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), new Class[]{UserMapper.class}, new UserMapperProxy(sqlSession, UserMapper.class));
            log.info("{}", userMapper.getUserList());
        }
    }
}

上面代碼中:UserMapper是沒有實現類的,可以通過Proxy.newProxyInstance給UserMapper接口創建一個代理對象,當調用UserMapper接口的方法的時候,會調用到UserMapperProxy對象的invoke方法。

運行一下test1用例,輸出如下:

16:34.288 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke start
16:34.555 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==>  Preparing: SELECT * FROM t_user 
16:34.580 [main] DEBUG c.j.chat02.UserMapper.getUserList - ==> Parameters: 
16:34.597 [main] DEBUG c.j.chat02.UserMapper.getUserList - <==      Total: 4
16:34.597 [main] DEBUG com.javacode2018.chat02.ProxyTest - invoke end
16:34.597 [main] INFO  com.javacode2018.chat02.ProxyTest - [UserModel(id=2, name=javacode2018, age=30, salary=50000.0, sex=1), UserModel(id=1575621274235, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575621329823, name=路人甲Java, age=30, salary=50000.0, sex=1), UserModel(id=1575623283897, name=路人甲Java, age=30, salary=50000.0, sex=1)]

注意上面輸出的invoke startinvoke end,可以看到我們調用userMapper.getUserList時候,被UserMapperProxy#invoke方法處理了。

Mybatis中創建Mapper接口代理對象使用的是下面這個類,大家可以去研究一下:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章