MyBatis使用篇(五)—— Mapper动态代理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_36378917/article/details/85101086

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');

  创建好以后的数据表如下所示:
在这里插入图片描述

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,得到如下结果说明测试正确。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  运行删除球员的测试方法TestDeleteBasketballPlayerById,得到如下结果说明测试正确。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  运行修改球员的测试方法TestUpdateBasketballPlayerById,得到如下结果说明测试正确。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
  运行查询球员的测试方法TestSelectBasketballPlayerById,得到如下结果说明测试正确。
在这里插入图片描述
  运行查询全部球员的测试方法TestSelectAllasketballPlayer,得到如下结果说明测试正确。
在这里插入图片描述

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后,在控制台打印输出如下日志信息,说明测试通过。
在这里插入图片描述

  执行测试方法TestSelectBasketballPlayerByConditionByIndex后,在控制台打印输出如下日志信息,说明测试通过。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_36378917/article/details/85101086