关于Mybatis的mapper接口函数重载问题
作者:qq_37121463
这篇文章主要介绍了关于Mybatis的mapper接口函数重载问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
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 字段进行判断
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。