天天看點

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

文章目錄

  • 1、綜述
  • 2、實作Mapper動态代理
    • 2.1 映射檔案的namespace屬性值
    • 2.2 Dao接口方法名
    • 2.3 Dao對象的擷取
    • 2.4 删除Dao實作類
  • 3、測試案例
    • 3.1 建立測試資料表
    • 3.2 建立實體類
    • 3.3 建立Dao檔案
    • 3.4 建立SQL映射檔案
    • 3.5 修改資料源方法
    • 3.6 建立測試類
    • 3.7 對球員的增删改查操作測試
  • 4、注意
  • 5、解決多查詢條件無法整體接收問題
    • 5.1 修改接口Dao檔案
    • 5.2 修改SQL映射檔案
    • 5.3 修改測試類

1、綜述

  一般建立Web工程時,從資料庫取資料的邏輯會放置在Dao層(Data Access Object,資料通路對象)。在之前的系列部落格的執行個體中,我們發現:Dao的實作類其實并沒有幹什麼實質性的工作,它僅僅就是通過SqlSession的相關API定位到映射檔案mapper中相應id的SQL語句,真正對DB進行操作的工作其實是由架構通過mapper中的SQL完成的。

  是以在使用MyBatis開發Web工程時,通過Mapper動态代理機制,可以隻編寫資料互動的接口及方法定義,和對應的Mapper映射檔案,具體的互動方法實作由MyBatis來完成, 這樣大大節省了開發Dao層的時間。這種Dao的實作方式稱為Mapper的動态代理方式。Mapper的動态代理方式無須程式員實作Dao接口,接口是由MyBatis結合映射檔案自動生成的動态代理實作的。

2、實作Mapper動态代理

2.1 映射檔案的namespace屬性值

  一般情況下,一個Dao接口的實作類方法使用的是同一個SQL映射id。是以,MyBatis架構要求,将映射檔案中mapper标簽的namespace屬性設定為Dao接口的全類名, 則系統會根據方法所屬的Dao接口,自動到相應namespace的映射檔案中查找相關的SQL映射。

  簡單來說,通過接口名即可定位到SQL映射檔案。

2.2 Dao接口方法名

  MyBatis架構要求,接口中的方法名,與映射檔案中相應的SQL标簽的id值相同。 系統會自動根據方法名到相應的映射檔案中查找同名的SQL映射id。

  簡單來說,通過方法名就可以定位到SQL映射檔案中相應的SQL語句。

2.3 Dao對象的擷取

  在本部落格之前的所有執行個體中,均是通過com.ccff.mybatis.datasource包下的DataConnection類中的getSqlSession方法來擷取SqlSession對象,最終利用SqlSession對象的API方法實作對資料庫的操作。

  在使用Mapper動态代理後,需要用SqlSession對象的getMapper方法,通過動态代理的方式擷取Mapper代理,通過動态代理的方式實作接口相應方法并調用使用。

2.4 删除Dao實作類

  由于通過調用Dao接口方法,不僅可以從SQL映射檔案中找到所要執行的SQL語句,還可以通過方法參數及傳回值,将SQL語句的動态參數傳入,将查詢結果傳回。是以,Dao的實作工作,完全可以由MyBatis系統自動根據映射檔案完成。是以,Dao的實作類就不需要了。

  Dao實作對象時由JDK的Proxy動态代理自動生成的。

3、測試案例

3.1 建立測試資料表

  在mybaits資料庫中建立名為“basketballplayer”的資料表,并添加測試資料。具體的建表語句與插入資料語句如下所示:

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `basketballplayer`
-- ----------------------------
DROP TABLE IF EXISTS `basketballplayer`;
CREATE TABLE `basketballplayer` (
  `id` int(25) NOT NULL auto_increment,
  `name` varchar(255) default NULL,
  `age` int(2) default NULL,
  `weight` double(25,1) default NULL,
  `height` int(25) default NULL,
  `number` int(25) default NULL,
  `team` varchar(255) default NULL,
  `position` varchar(255) default NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of basketballplayer
-- ----------------------------
INSERT INTO `basketballplayer` VALUES ('1', 'LeBron James', '35', '113.4', '203', '23', 'LAL', 'F');
INSERT INTO `basketballplayer` VALUES ('2', 'Stephen Curry', '31', '86.2', '190', '30', 'WAS', 'G');
INSERT INTO `basketballplayer` VALUES ('3', 'Kevin Durant', '31', '108.8', '205', '35', 'WAS', 'F');
INSERT INTO `basketballplayer` VALUES ('4', 'James Harden', '30', '99.8', '195', '13', 'HOU', 'G');
INSERT INTO `basketballplayer` VALUES ('5', 'Chris Paul', '34', '79.4', '182', '3', 'HOU', 'G');
           

  建立好以後的資料表如下所示:

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

3.2 建立實體類

  在com.ccff.mybatis.model包下建立名為“BasketballPlayer”的實體類,并提供有參和無參構造方法、get和set方法以及toString方法。具體代碼如下:

package com.ccff.mybatis.model;

public class BasketballPlayer {
    private int id;
    private String name;
    private int age;
    private Double weight;
    private int height;
    private int number;
    private String team;
    private String position;

    public BasketballPlayer() {
    }

    public BasketballPlayer(String name, int age, Double weight, int height, int number, String team, String position) {
        this.name = name;
        this.age = age;
        this.weight = weight;
        this.height = height;
        this.number = number;
        this.team = team;
        this.position = position;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Double getWeight() {
        return weight;
    }

    public void setWeight(Double weight) {
        this.weight = weight;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int num) {
        this.number = num;
    }

    public String getTeam() {
        return team;
    }

    public void setTeam(String team) {
        this.team = team;
    }

    public String getPosition() {
        return position;
    }

    public void setPosition(String position) {
        this.position = position;
    }

    @Override
    public String toString() {
        return "BasketballPlayer{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                ", height=" + height +
                ", number=" + number +
                ", team='" + team + '\'' +
                ", position='" + position + '\'' +
                '}';
    }
}
           

3.3 建立Dao檔案

  在com.ccff.mybatis.dao包下建立名為“IBasketballPlayerDao”的接口,在該接口中定義對籃球運動員的增删改查基礎操作方法。具體代碼如下:

package com.ccff.mybatis.dao;

import com.ccff.mybatis.model.BasketballPlayer;

public interface IBasketballPlayerDao{

    //添加球員
    void addBasketballPlayer(BasketballPlayer player);

    //根據id删除球員
    void deleteBasketballPlayerById(int id);

    //根據id修改球員
    void updateBasketballPlayerById(BasketballPlayer player);

    //根據id查詢球員
    BasketballPlayer selectBasketballPlayerById(int id);
	
	//查詢所有球員
    List<BasketballPlayer> selectAllasketballPlayer();
}
           

3.4 建立SQL映射檔案

  在config/sqlmap檔案夾下建立名為“BasketballPlayerMapper”的XML檔案,将mapper标簽的namespace設定為IBasketballPlayerDao的全類名,并實作Mapper動态代理,具體代碼如下:

<?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.ccff.mybatis.dao.IBasketballPlayerDao">

    <!--增加球員-->
    <insert id="addBasketballPlayer" parameterType="BasketballPlayer">
        insert into basketballplayer(name,age,weight,height,number,team,position)
          values (#{name},#{age},#{weight},#{height},#{number},#{team},#{position})
    </insert>

    <!--根據id删除球員-->
    <delete id="deleteBasketballPlayerById" parameterType="BasketballPlayer">
        delete from basketballplayer where id = #{id}
    </delete>

    <!--根據id修改球員-->
    <update id="updateBasketballPlayerById" parameterType="BasketballPlayer">
        update basketballplayer set height = #{height} where id = #{id}
    </update>

    <!--根據id查詢球員-->
    <select id="selectBasketballPlayerById" resultType="BasketballPlayer">
        select * from basketballplayer where id = #{id}
    </select>

	<!--查詢所有球員-->
    <select id="selectAllasketballPlayer" resultType="BasketballPlayer">
        select * from basketballplayer
    </select>
</mapper>
           

  将建立好的SQL映射檔案配置到全局配置檔案SqlMapConfig.xml中,具體配置如下:

<mappers>
    <mapper resource="sqlmap/UserMapper.xml"/>
    <mapper resource="sqlmap/StudentMapper.xml"/>
    <mapper resource="sqlmap/BasketballPlayerMapper.xml"/>
</mappers>
           

3.5 修改資料源方法

  在com.ccff.mybatis.datasource包下修改DataConnection類,在該類中添加名為“getBasketballPlayerMapper”的方法用于通過動态代理的方式擷取IBasketballPlayerDao對象,具體代碼如下:

public IBasketballPlayerDao getBasketballPlayerMapper() {
        IBasketballPlayerDao basketballPlayerMapper = sqlSession.getMapper(IBasketballPlayerDao.class);
        return basketballPlayerMapper;
    }
           

3.6 建立測試類

  在com.ccff.mybatis.test包下建立名為“BasketballPlayerTest”的測試類,具體代碼如下:

package com.ccff.mybatis.test;

import com.ccff.mybatis.dao.IBasketballPlayerDao;
import com.ccff.mybatis.datasource.DataConnection;
import com.ccff.mybatis.model.BasketballPlayer;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.io.IOException;
import java.util.List;

public class BasketballPlayerTest {
    private IBasketballPlayerDao basketballPlayerMapper;
    private SqlSession sqlSession;

    public BasketballPlayerTest(){
        super();
        DataConnection dataConnection = new DataConnection();
        try {
            sqlSession = dataConnection.getSqlSession();
            basketballPlayerMapper = dataConnection.getBasketballPlayerMapper();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void TestAddBasketballPlayer(){
        System.out.println("插入球員前:");
        TestSelectAllasketballPlayer();
        BasketballPlayer player = new BasketballPlayer("Kawhi Leonard",28,104.3,200,2,"TRO","F");
        basketballPlayerMapper.addBasketballPlayer(player);
        sqlSession.commit();
        sqlSession.close();
        System.out.println("插入球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestDeleteBasketballPlayerById(){
        System.out.println("删除球員前:");
        TestSelectAllasketballPlayer();
        basketballPlayerMapper.deleteBasketballPlayerById(5);
        sqlSession.commit();
        sqlSession.close();
        System.out.println("删除球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestUpdateBasketballPlayerById(){
        System.out.println("修改球員前:");
        TestSelectAllasketballPlayer();
        BasketballPlayer player = new BasketballPlayer();
        BasketballPlayer basketballPlayer = null;
        player.setId(3);
        player.setHeight(213);
        basketballPlayerMapper.updateBasketballPlayerById(player);
        sqlSession.commit();
        sqlSession.close();
        System.out.println("修改球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestSelectBasketballPlayerById(){
        BasketballPlayer player = basketballPlayerMapper.selectBasketballPlayerById(3);
        sqlSession.close();
        System.out.println(player);
    }

    @Test
    public void TestSelectAllasketballPlayer(){
        List<BasketballPlayer> players = basketballPlayerMapper.selectAllasketballPlayer();
        for (BasketballPlayer player : players){
            System.out.println(player);
        }
        sqlSession.close();
    }
    
}
           

3.7 對球員的增删改查操作測試

  運作添加球員的測試方法TestAddBasketballPlayer,得到如下結果說明測試正确。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

  運作删除球員的測試方法TestDeleteBasketballPlayerById,得到如下結果說明測試正确。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

  運作修改球員的測試方法TestUpdateBasketballPlayerById,得到如下結果說明測試正确。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題
MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

  運作查詢球員的測試方法TestSelectBasketballPlayerById,得到如下結果說明測試正确。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

  運作查詢全部球員的測試方法TestSelectAllasketballPlayer,得到如下結果說明測試正确。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

4、注意

  MyBatis架構對于Dao查詢的自動實作,底層隻會調用selectOne方法與selectList方法。 而架構選擇方法的标準是測試類中用于接收傳回值的對象類型。若接收類型為List,則自動選擇selectList方法;否則,自動選擇selectOne方法

5、解決多查詢條件無法整體接收問題

  在實際工作過程中,表單中所給出的查詢條件有時是無法将其封裝成一個對象的,也就是說,查詢方法隻能攜帶多個參數,而不能攜帶将這多個參數進行封裝的一個對象。對于這個問題,有以下兩種解決辦法。

  • 将多個參數封裝成一個Map
  • 逐個接收多個參數

5.1 修改接口Dao檔案

  修改接口Dao檔案IBasketballPlayerDao,在接口中添加用于測試的方法,具體代碼如下:

package com.ccff.mybatis.dao;

import com.ccff.mybatis.model.BasketballPlayer;

import java.util.List;

public interface IBasketballPlayerDao {

    //添加球員
    void addBasketballPlayer(BasketballPlayer player);

    //根據id删除球員
    void deleteBasketballPlayerById(int id);

    //根據id修改球員
    void updateBasketballPlayerById(BasketballPlayer player);

    //根據id查詢球員
    BasketballPlayer selectBasketballPlayerById(int id);

    //查詢所有球員
    List<BasketballPlayer> selectAllasketballPlayer();

    //根據指定條件查詢球員(示範多查詢條件接收問題)
    List<BasketballPlayer> selectBasketballPlayerByConditionByMap(Map<String,Object> map);
    List<BasketballPlayer> selectBasketballPlayerByConditionByIndex(String name,int age);

}
           

5.2 修改SQL映射檔案

  修改接口SQL映射檔案BasketballPlayerMapper.xml,在SQL映射檔案中添加對應的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="com.ccff.mybatis.dao.IBasketballPlayerDao">

    <!--增加球員-->
    <insert id="addBasketballPlayer" parameterType="BasketballPlayer">
        insert into basketballplayer(name,age,weight,height,number,team,position)
          values (#{name},#{age},#{weight},#{height},#{number},#{team},#{position})
    </insert>

    <!--根據id删除球員-->
    <delete id="deleteBasketballPlayerById" parameterType="BasketballPlayer">
        delete from basketballplayer where id = #{id}
    </delete>

    <!--根據id修改球員-->
    <update id="updateBasketballPlayerById" parameterType="BasketballPlayer">
        update basketballplayer set height = #{height} where id = #{id}
    </update>

    <!--根據id查詢球員-->
    <select id="selectBasketballPlayerById" resultType="BasketballPlayer">
        select * from basketballplayer where id = #{id}
    </select>

    <!--查詢所有球員-->
    <select id="selectAllasketballPlayer" resultType="BasketballPlayer">
        select * from basketballplayer
    </select>

    <!--根據指定條件查詢球員(示範多查詢條件接收問題)-->
    <select id="selectBasketballPlayerByConditionByMap" resultType="BasketballPlayer">
        select * from basketballplayer where name like '%' #{nameCondition} '%' and age > #{ageCondition}
    </select>
    <select id="selectBasketballPlayerByConditionByIndex" resultType="BasketballPlayer">
        select * from basketballplayer where name like '%' #{0} '%' and age > #{1}
    </select>

</mapper>
           

  通過SQL映射檔案的配置可以看到,若通過将多個參數封裝成一個Map的方式解決多查詢參數問題,則#{}中填寫的是Map的鍵名;若通過逐個接收參數的方式解決多查詢參數問題,則#{}中填寫的是每一個參數的下标,下标的參數時從0開始的。

5.3 修改測試類

  修改接口SQL映射檔案BasketballPlayerTest.xml,在測試類中添加名為“TestSelectBasketballPlayerByConditionByMap”和“TestSelectBasketballPlayerByConditionByIndex”的兩個測試方法,具體配置如下:

package com.ccff.mybatis.test;

import com.ccff.mybatis.dao.IBasketballPlayerDao;
import com.ccff.mybatis.datasource.DataConnection;
import com.ccff.mybatis.model.BasketballPlayer;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class BasketballPlayerTest {
    private IBasketballPlayerDao basketballPlayerMapper;
    private SqlSession sqlSession;

    public BasketballPlayerTest(){
        super();
        DataConnection dataConnection = new DataConnection();
        try {
            sqlSession = dataConnection.getSqlSession();
            basketballPlayerMapper = dataConnection.getBasketballPlayerMapper();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void TestAddBasketballPlayer(){
        System.out.println("插入球員前:");
        TestSelectAllasketballPlayer();
        BasketballPlayer player = new BasketballPlayer("Kawhi Leonard",28,104.3,200,2,"TRO","F");
        basketballPlayerMapper.addBasketballPlayer(player);
        sqlSession.commit();
        System.out.println("插入球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestDeleteBasketballPlayerById(){
        System.out.println("删除球員前:");
        TestSelectAllasketballPlayer();
        basketballPlayerMapper.deleteBasketballPlayerById(5);
        sqlSession.commit();
        System.out.println("删除球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestUpdateBasketballPlayerById(){
        System.out.println("修改球員前:");
        TestSelectAllasketballPlayer();
        BasketballPlayer player = new BasketballPlayer();
        BasketballPlayer basketballPlayer = null;
        player.setId(3);
        player.setHeight(213);
        basketballPlayerMapper.updateBasketballPlayerById(player);
        sqlSession.commit();
        System.out.println("修改球員後:");
        TestSelectAllasketballPlayer();
    }

    @Test
    public void TestSelectBasketballPlayerById(){
        BasketballPlayer player = basketballPlayerMapper.selectBasketballPlayerById(3);
        System.out.println(player);
    }

    @Test
    public void TestSelectAllasketballPlayer(){
        List<BasketballPlayer> players = basketballPlayerMapper.selectAllasketballPlayer();
        for (BasketballPlayer player : players){
            System.out.println(player);
        }
    }

    @Test
    public void TestSelectBasketballPlayerByConditionByMap(){
        Map<String,Object> map = new HashMap<>();
        map.put("nameCondition","en");
        map.put("ageCondition",29);
        List<BasketballPlayer> players = basketballPlayerMapper.selectBasketballPlayerByConditionByMap(map);
        for (BasketballPlayer player : players){
            System.out.println(player);
        }
    }

    @Test
    public void TestSelectBasketballPlayerByConditionByIndex(){
        List<BasketballPlayer> players = basketballPlayerMapper.selectBasketballPlayerByConditionByIndex("en",29);
        for (BasketballPlayer player : players){
            System.out.println(player);
        }
    }
}
           

  執行測試方法TestSelectBasketballPlayerByConditionByMap後,在控制台列印輸出如下日志資訊,說明測試通過。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題

  執行測試方法TestSelectBasketballPlayerByConditionByIndex後,在控制台列印輸出如下日志資訊,說明測試通過。

MyBatis使用篇(五)—— Mapper動态代理1、綜述2、實作Mapper動态代理3、測試案例4、注意5、解決多查詢條件無法整體接收問題