首頁 > 軟體

關於Mybatis的mapper介面函數過載問題

2022-02-09 10:00:25

Mybatis的介面函數能不能進行過載?

  • mybatis版本:3.4x
  • java版本:java 8

語法層面

1、介面的方法可以進行過載,因為 java 語法可以讓介面函數進行過載。

Mybatis框架方面

1、結論:可以有條件的進行過載。

2、為什麼會有這個問題?:mybatis裡面將介面裡面的方法名稱和組態檔裡面的id屬性進行唯一配對,在同一個名稱空間下只能有一個id,那麼所有函數名稱相同的過載函數都會被繫結到一個id上,所以,如果要實現函數的過載,必須讓一個SQL語句去適應多個函數的引數,如果是單純的過載是肯定不行的(過載函數的定義就是引數相關),但是得益於mybatis的多種傳參方式和隱性的分頁功能,可以在介面裡面進行函數過載,但是還是需要將所有的過載函數適配到同一個id的SQL上面去,仍然有很大的侷限性,並不是可以隨意的進行過載。

測試

1、資料庫環境

DROP TABLE IF EXISTS sys_user;
CREATE TABLE sys_user(
  id BIGINT NOT NULL AUTO_INCREMENT COMMENT '使用者的ID',
  user_name VARCHAR(50) COMMENT '使用者名稱稱',
  user_password VARCHAR(50) COMMENT '密碼',
  user_email VARCHAR(50) COMMENT '郵箱',
  user_info TEXT COMMENT '簡介',
  head_img BLOB COMMENT '頭像',
  create_time DATETIME COMMENT '建立時間',
  PRIMARY KEY (id)
)DEFAULT CHARSET utf8;
ALTER TABLE sys_user COMMENT '使用者表';
INSERT INTO sys_user VALUES ('1','admin','123456','admin@mybatis.tk','管理員',null,'2016-04-01 17:00:58');
INSERT INTO sys_user VALUES ('1001','test','123456','test@mybatis.tk','測試使用者',null,'2016-04-01 17:01:52');

2、實體類

public class SysUser {
    private Long id;
    private String userName;
    private String userPassword;
    private String userEmail;
    private String userInfo;
    private byte[] headImg;
    private Date createTime;
    public SysUser(){
    }
    /**setter and getter*/
}

3、測試

3.1、測試模板程式碼

public class SysUserDaoTest {
    private static SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    @BeforeClass
    public static void beforeClass(){
        String resource = "plus/mybatis-config.xml";
        try{
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    @Before
    public void before(){
        //事物手動提交
        sqlSession = sqlSessionFactory.openSession(false);
    }
    @After
    public void after(){
        //回滾事物
        sqlSession.rollback(true);
        //關閉對談
        sqlSession.close();
    }
}

3.2、測試場景一

過載實現分頁、引數型別轉換實現過載。

3.2.1、介面

public interface SysUserDao {
    SysUser selectById(Long id);
    List<SysUser> selectById(Long id,RowBounds rowBound);
 }

3.2.2、mapper組態檔

<!--
    id:注意事項:介面方法不能進行過載,在3.4版本後是錯的!!!!!,因為至少可以分頁!!!
            1、介面中的所有過載方法對應XML裡面的同一個ID,但是3.4之後有一個分頁的外掛,所以至少可以接受一個org.apache.ibatis.session.RowBounds的隱性引數。
            2、雖然介面裡面的方法可以進行過載,但是需要一個前提:不要指定 parameterType 屬性!!
                介面方法如下:
                SysUser selectById(Long id);
                List<SysUser> selectById(Long id,RowBounds rowBound);
                SysUser selectById(String name);
                測試程式碼如下:
                SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
                SysUser sysUser = sysUserDao.selectById(1L);
                Assert.assertEquals("admin",sysUser.getUserName());
                SysUser sysUser1 = sysUserDao.selectById("1");
                Assert.assertEquals("admin",sysUser1.getUserName());
                /*
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(String)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                 */
                 RowBounds rowBound = new RowBounds(0,2);
                List<SysUser> sysUsers = sysUserDao.selectById(1L,rowBound);
                Assert.assertEquals(1,sysUsers.size());
                /*
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                 */
                事實證明,當不指定引數型別的時候(也僅僅當不指定引數型別的時候才可以,因為過載過載的是引數),是可以進行
                介面方法過載的,因為mybatis的引數型別推斷會自動的轉換引數型別,比如:
                    sysUserDao.selectById("楚雲飛");
                因為ID的型別是Long型別,所以資料型別的自動轉換失敗,但是是不會出現異常的,只是它選取出來的資料集為null,因為ID的型別是bigint,沒有bigint值為楚雲飛,所以為null結果集。
    -->
    <select id="selectById" resultMap="sysUser" flushCache="true">
        SELECT * FROM sys_user WHERE id = #{id};
    </select>
    <resultMap id="sysUser" type="plus.pojo.SysUser">
        <id property="id" column="id"/>
        <result property="userName" column="user_name"/>
        <result property="userPassword" column="user_password"/>
        <result property="userEmail" column="user_email"/>
        <result property="userInfo" column="user_info"/>
        <result property="headImg" column="head_img" jdbcType="BLOB"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </resultMap>

3.3、測試場景二

引數傳遞方式實現過載,map引數傳遞和固定引數名的引數傳遞方式

3.3.1、介面

public interface SysUserDao {
    SysUser selectByCondition(@Param("id")Long id,@Param("userName")String userName,@Param("userPassword")String userPassword);
    SysUser selectByCondition(Map params);
}

3.3.2、mapper組態檔

<!--
    固定引數傳遞:
       函數介面:
           SysUser selectByCondition(@Param("id")Long id,@Param("userName")String userName,@Param("userPassword")String userPassword);
           SysUser selectByCondition(Map params);
       測試函數:
            @Test
            public void selectByCondition(){
                SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
                SysUser sysUser = sysUserDao.selectByCondition(1L,"admin","123456");
                Assert.assertEquals("admin@mybatis.tk",sysUser.getUserEmail());
                /*
                plus.dao.SysUserDao.selectByCondition - ==>  Preparing: SELECT * FROM sys_user WHERE id = ? AND user_name = ? AND user_password = ?;
                plus.dao.SysUserDao.selectByCondition - ==> Parameters: 1(Long), admin(String), 123456(String)
                plus.dao.SysUserDao.selectByCondition - <==      Total: 1
                 */
                Map<String,Object> params = new HashMap<String,Object>(3);
                //傳入的鍵值必須和SQL語句裡面的以及過載函數的引數名稱相同才可以實現正確的過載,否則將過載出錯。
                params.put("id",1L);
                params.put("userName","admin");
                params.put("userPassword","123456");
                SysUser sysUser1 = sysUserDao.selectByCondition(params);
                /*
                plus.dao.SysUserDao.selectByCondition - ==>  Preparing: SELECT * FROM sys_user WHERE id = ? AND user_name = ? AND user_password = ?;
                plus.dao.SysUserDao.selectByCondition - ==> Parameters: 1(Long), admin(String), 123456(String)
                plus.dao.SysUserDao.selectByCondition - <==      Total: 1
                 */
                Assert.assertEquals("admin@mybatis.tk",sysUser1.getUserEmail());
            }
-->
    <select id="selectByCondition" resultMap="sysUser" flushCache="true">
        SELECT * FROM sys_user WHERE id = #{id} AND user_name = #{userName} AND user_password = #{userPassword};
    </select>

3.4、完整的測試程式碼

package plus.dao;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import org.junit.*;
import plus.pojo.SysUser;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class SysUserDaoTest {
    private static SqlSessionFactory sqlSessionFactory;
    private SqlSession sqlSession;
    @BeforeClass
    public static void beforeClass(){
        String resource = "plus/mybatis-config.xml";
        try{
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    @Before
    public void before(){
        //事物手動提交
        sqlSession = sqlSessionFactory.openSession(false);
    }
    @After
    public void after(){
        //回滾事物
        sqlSession.rollback(true);
        //關閉對談
        sqlSession.close();
    }
    @Test
    public void selectById(){
        SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
        SysUser sysUser = sysUserDao.selectById(1L);
        Assert.assertEquals("admin",sysUser.getUserName());
        SysUser sysUser1 = sysUserDao.selectById("1");
        Assert.assertEquals("admin",sysUser1.getUserName());
        /*
        plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
        plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
        plus.dao.SysUserDao.selectById - <==      Total: 1
        plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
        plus.dao.SysUserDao.selectById - ==> Parameters: 1(String)
        plus.dao.SysUserDao.selectById - <==      Total: 1
         */
        RowBounds rowBound = new RowBounds(0,2);
        List<SysUser> sysUsers = sysUserDao.selectById(1L,rowBound);
        Assert.assertEquals(1,sysUsers.size());
        /*
        plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
        plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
        plus.dao.SysUserDao.selectById - <==      Total: 1
         */
    }
    @Test
    public void selectByCondition(){
        SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
        SysUser sysUser = sysUserDao.selectByCondition(1L,"admin","123456");
        Assert.assertEquals("admin@mybatis.tk",sysUser.getUserEmail());
        /*
        plus.dao.SysUserDao.selectByCondition - ==>  Preparing: SELECT * FROM sys_user WHERE id = ? AND user_name = ? AND user_password = ?;
        plus.dao.SysUserDao.selectByCondition - ==> Parameters: 1(Long), admin(String), 123456(String)
        plus.dao.SysUserDao.selectByCondition - <==      Total: 1
         */
        Map<String,Object> params = new HashMap<String,Object>(3);
        params.put("id",1L);
        params.put("userName","admin");
        params.put("userPassword","123456");
        SysUser sysUser1 = sysUserDao.selectByCondition(params);
        /*
        plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
        plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
        plus.dao.SysUserDao.selectById - <==      Total: 1
         */
        Assert.assertEquals("admin@mybatis.tk",sysUser1.getUserEmail());
    }
}

3.5、完整的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="plus.dao.SysUserDao">
    <!--
    id:當使用介面時,屬性值必須為介面裡面的方法名稱,在同一個名稱空間裡面不重複,否則將啟動報錯;
        在任何情況下,id屬性不能出現英文句點 '.' (Caused by: org.apache.ibatis.builder.BuilderException: Dots are not allowed in element names, please remove it) ;
        注意事項:介面方法不能進行過載,在3.4版本後是錯的!!!!!,因為可以分頁!!!
            1、介面中的所有過載方法對應XML裡面的同一個ID,但是3.4之後有一個分頁的外掛,所以可以接受一個org.apache.ibatis.session.RowBounds的隱性引數。
            2、雖然介面裡面的方法可以進行過載,但是如果要在執行的時候依然不報錯的話,需要一個前提:不要指定 parameterType 屬性!!
                介面方法如下:
                SysUser selectById(Long id);
                List<SysUser> selectById(Long id,RowBounds rowBound);
                SysUser selectById(String name);
                測試程式碼如下:
                SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);
                SysUser sysUser = sysUserDao.selectById(1L);
                Assert.assertEquals("admin",sysUser.getUserName());
                SysUser sysUser1 = sysUserDao.selectById("1");
                Assert.assertEquals("admin",sysUser1.getUserName());
                /*
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(String)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                 */
                 RowBounds rowBound = new RowBounds(0,2);
                List<SysUser> sysUsers = sysUserDao.selectById(1L,rowBound);
                Assert.assertEquals(1,sysUsers.size());
                /*
                plus.dao.SysUserDao.selectById - ==>  Preparing: SELECT * FROM sys_user WHERE id = ?;
                plus.dao.SysUserDao.selectById - ==> Parameters: 1(Long)
                plus.dao.SysUserDao.selectById - <==      Total: 1
                 */
                事實證明,當不指定引數型別的時候(也僅僅當不指定引數型別的時候才可以,因為過載過載的是引數),是可以進行
                介面方法過載的,因為mybatis的引數型別推斷會自動的轉換引數型別,比如:
                    sysUserDao.selectById("楚雲飛");
                因為ID的型別是Long型別,所以資料型別的自動轉換失敗,但是是不會出現異常的,只是它選取出來的資料集為null
    -->
    <select id="selectById" resultMap="sysUser" flushCache="true">
        SELECT * FROM sys_user WHERE id = #{_parameter};
    </select>
    <!--
    引數傳遞問題:
    1、介面函數:
        SysUser selectByCondition(Long id,String userName,String userPassword);
    引數傳遞:
        1、預設引數傳遞:預設的引數的命名為 param + 序位,SQL語句如下:
            SELECT * FROM sys_user WHERE id = #{param1} AND user_name = #{param2} AND user_password = #{param3};
        2、預設引數傳遞:直接序列引數,SQL語句如下:
            SELECT * FROM sys_user WHERE id = #{0} AND user_name = #{1} AND user_password = #{2};
        3、固定引數傳遞:
            函數介面:
                SysUser selectByCondition(@Param("id")Long id,@Param("userName")String userName,@Param("userPassword")String userPassword);
            SQL語句如下:
                SELECT * FROM sys_user WHERE id = #{id} AND user_name = #{userName} AND user_password = #{userPassword};
        4、使用Map傳遞引數:
            函數介面:SysUser selectByCondition(Map params);
            呼叫程式碼段:
                Map<String,Object> params = new HashMap<String,Object>(3);
                params.put("id",1L);
                params.put("userName","admin");
                params.put("userPassword","123456");
                SysUser sysUser1 = sysUserDao.selectByCondition(params);
                Assert.assertEquals("admin@mybatis.tk",sysUser1.getUserEmail());
            SQL語句:
                SELECT * FROM sys_user WHERE id = #{id} AND user_name = #{userName} AND user_password = #{userPassword};
            思考:
                1、與固定引數的SQL相比,沒有改變SQL語句,而且兩個介面實現過載。
                2、介面函數可以實現過載,但是需要SQL語句來相容這個函數的過載,也就是可以進行有條件的過載。
        5、使用 $ 和 # 進行取值的區別:
            1. #將傳入的資料都當成一個字串,會對自動傳入的資料加一個雙引號。如:order by #user_id#,如果傳入的值是111,那麼解析成sql時的值為order by "111", 如果傳入的值是id,則解析成的sql為order by "id".
            2. $將傳入的資料直接顯示生成在sql中。如:order by $user_id$,如果傳入的值是111,那麼解析成sql時的值為order by user_id,  如果傳入的值是id,則解析成的sql為order by id.
            3. #方式能夠很大程度防止sql注入。
            4.$方式無法防止Sql注入。
            5.$方式一般用於傳入資料庫物件,例如傳入表名.
            6.一般能用#的就別用$.
            SELECT * FROM sys_user WHERE id = ${id} AND user_name = '${userName}' AND user_password = '${userPassword}';
    -->
    <select id="selectByCondition" resultMap="sysUser" flushCache="true">
        SELECT * FROM sys_user WHERE id = #{id} AND user_name = #{userName} AND user_password = #{userPassword};
    </select>
    <resultMap id="sysUser" type="plus.pojo.SysUser">
        <id property="id" column="id"/>
        <result property="userName" column="user_name"/>
        <result property="userPassword" column="user_password"/>
        <result property="userEmail" column="user_email"/>
        <result property="userInfo" column="user_info"/>
        <result property="headImg" column="head_img" jdbcType="BLOB"/>
        <result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    </resultMap>
</mapper>

3.6、完整的介面 

public interface SysUserDao {
    SysUser selectById(Long id);
    List<SysUser> selectById(Long id,RowBounds rowBound);
    SysUser selectById(String name);
    SysUser selectByCondition(@Param("id")Long id,@Param("userName")String userName,@Param("userPassword")String userPassword);
    SysUser selectByCondition(Map params);
}

MyBatis實現方法過載的小技巧

QuestionMapper.java

利用介面預設方法 default 實現

QuestionMapper.xml

利用動態sql對 userId 欄位進行判斷

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。 


IT145.com E-mail:sddin#qq.com