Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

更正:Dao 接口里的方法可以重载,但是Mybatis的XML里面的ID不允许重复! #1122

Closed
charlienss opened this issue Mar 10, 2021 · 6 comments
Labels
discuss discuss a problem enhancement New feature or request or suggestion perfect Improve knowledge points or descriptions

Comments

@charlienss
Copy link

charlienss commented Mar 10, 2021

更正:Dao 接口里的方法可以重载,但是Mybatis的XML里面的ID不允许重复!

guide

此处错误更正:

Dao 接口里的方法可以重载,但是Mybatis的XML里面的ID不允许重复。

Mybatis版本3.3.0,亲测如下:

/**
 * Mapper接口里面方法重载
 */
public interface StuMapper {

	List<Student> getAllStu();
    
	List<Student> getAllStu(@Param("id") Integer id);
}

然后在StuMapper.xml中利用Mybatis的动态sql就可以实现。

	<select id="getAllStu" resultType="com.pojo.Student">
 		select * from student
		<where>
			<if test="id != null">
				id = #{id}
			</if>
		</where>
 	</select>

能正常运行,并能得到相应的结果,这样就实现了在Dao接口中写重载方法。

总结:

MybatisDao接口可以有多个重载方法,但是方法对应的映射必须只有个,也就是MapperXML中定义的ID名不能重复,否则启动会报错。
@Snailclimb
Copy link
Owner

Dao 接口里的方法可以重载,但是Mybatis的XML里面的ID不允许重复

👍LGTM!

@Snailclimb Snailclimb added enhancement New feature or request or suggestion perfect Improve knowledge points or descriptions labels Mar 10, 2021
@plusmancn
Copy link

plusmancn commented Mar 14, 2021

阅读前提
源码版本为:org.mybatis.mybatis@3.5.6

答案讲了一半,还需要考虑 default method 的情况,下面分情况讨论。

Java 语法层面 JDK Proxy 是支持方法重载的,所以在 MapperInterface  里是可以写重载方法的,这是两种情况下的共性。

情况 1:在 MapperInterface 中的普通 interface method
这也是我们对 mybatis 最普遍的使用方式,在 Mapper 里面定义接口,在 xml 或者注解里面定义 sql

这种情况下,不同的重载方法由于 methodName  相同,最终会生成一致的 statementId ,会被代理到同一个 MapStatement 上,在 MapStatement 里可以通过条件判断来处理不同形式的入参。

从源码角度解读 statementId 生成规则,跟踪代理方法生成路径,找到 statementId 生成函数

// file => /org/apache/ibatis/binding/MapperProxy.java:92
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
   	// ......
	else {
        return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
    }
    // ......
}

// file => /org/apache/ibatis/binding/MapperMethod.java:224
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    // ......
    MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
    // ......
}

// file => /org/apache/ibatis/binding/MapperMethod.java:254
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
    // 实际调试过程显示 
    // "cn.plusman.mybatis.mapper.BlogMapper"      + "." + "selectBlog"
    String statementId = mapperInterface.getName() + "." + methodName;
    // 注意 statementId 里面是不带方法的参数类型的,所以到 MappedStatement 这里是不存在 Java 语法定义上的重载的。
}

情况 2:在 MapperInterface 中的 default mehtod
示例代码如下,这种情况下是可以实现完全意义上的方法重载的,mybatis 也不会将 default method 关联到特定的 MapStatement,而是直接代理执行 default method。

public interface BlogMapper {
    /**
     * 根据 id 获取博客内容
     * @param id
     * @return
     */
    Blog selectBlog(
        int id
    );
    
    /**
     * default method
     * @return
     */
    default Blog selectBlog() {
        // 可以自定义一些业务逻辑逻辑,也可以理解成 mybatis 的一个拓展点
        // some code
        
        // 可以通过调用内部其他方法,返回真实数据
        return this.selectBlog(12);
        
        // 单元测试的时候,也可以返回 fake 数据
        // return new Blog()
        //     .setBlogId(12)
        //     .setContent("hello world");
    }
}

源码实现相对简单,跟踪如下

// file => /org/apache/ibatis/binding/MapperProxy.java:92
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    // ......
    // 判断是否是 default method
    if (m.isDefault()) {
        try {
            if (privateLookupInMethod == null) {
                return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
                return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException
                 | NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }
    // ......
}


// file => /org/apache/ibatis/binding/MapperProxy.java:156
private static class DefaultMethodInvoker implements MapperMethodInvoker {
   // 直接触发代理执行,无 MapStatement 绑定逻辑
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
}

@ZeroMing
Copy link

ZeroMing commented Dec 1, 2021

本人也是测试过后,发现几个问题,这里其实并不是真正意义上的重载,楼主亲测的可行的重载方法,存在一个很大的问题,就是定义多个有参方法的时候,比如:
`

  1. User getById();
  2. User getById(@param("id) Integer id)
  3. User getById(@param("id) Integer id,@param("name) String name)
    `
    上面你的这种场景下,去调用无参方法是可以成功的,但是调用 2方法,是失败的,会报错 Parameter 'name' not found. Available parameters are [id, param1],
    也就是意味着,只能存在一个无参方法和一个有参方法,其实很鸡肋,并不提倡这么使用,四不像了。

结论是:dao中的接口不可可以重载的。用注解的方式就会报Mapped Statements collection already contains value ***

@Snailclimb Snailclimb added the discuss discuss a problem label Dec 3, 2021
@Snailclimb
Copy link
Owner

本人也是测试过后,发现几个问题,这里其实并不是真正意义上的重载,楼主亲测的可行的重载方法,存在一个很大的问题,就是定义多个有参方法的时候,比如: `

  1. User getById();
  2. User getById(@param("id) Integer id)
  3. User getById(@param("id) Integer id,@param("name) String name)
    `
    上面你的这种场景下,去调用无参方法是可以成功的,但是调用 2方法,是失败的,会报错 Parameter 'name' not found. Available parameters are [id, param1],
    也就是意味着,只能存在一个无参方法和一个有参方法,其实很鸡肋,并不提倡这么使用,四不像了。

结论是:dao中的接口不可可以重载的。用注解的方式就会报Mapped Statements collection already contains value ***

欢迎提交PR👍

@heihei180
Copy link

本人也是测试过后,发现几个问题,这里其实并不是真正意义上的重载,楼主亲测的可行的重载方法,存在一个很大的问题,就是定义多个有参方法的时候,比如: `

  1. User getById();
  2. User getById(@param("id) Integer id)
  3. User getById(@param("id) Integer id,@param("name) String name)
    `
    上面你的这种场景下,去调用无参方法是可以成功的,但是调用 2方法,是失败的,会报错 Parameter 'name' not found. Available parameters are [id, param1],
    也就是意味着,只能存在一个无参方法和一个有参方法,其实很鸡肋,并不提倡这么使用,四不像了。

结论是:dao中的接口不可可以重载的。用注解的方式就会报Mapped Statements collection already contains value ***

说的对,其实这种用法是投机取巧,或者说钻了源码BUG的空子,生产这么用就是挖坑....

@YAsheep
Copy link

YAsheep commented Mar 18, 2022

/*UserDao.java*/
public interface UserDao {
    User findByCondition(@Param("id") Integer id);
    User findByCondition(@Param("id") Integer id, @Param("username") String username);
}
/*MyTest.java*/
public class MyTest {
    public static void main(String[] args) throws IOException {
        //定义mybatis配置文件的路径
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //获取Sqlsession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取UserDao实现类对象
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user1 = userDao.findByCondition(2);
        System.out.println(user1);
        User user2 = userDao.findByCondition(1, "UZI");
        System.out.println(user2);
        //释放资源
        sqlSession.close();
    }
}
<!--UserDao.xml-->
<mapper>
<select id="findByCondition" resultType="org.example.pojo.User">
        select *
        from user
        <where>
            id = #{id}
            <if test="username != null">
                username = #{username}
            </if>
        </where>
    </select>
</mapper>

单独执行1,2都会报如下错误,跟踪源码发现代理的找到的代理方法参数是一致的,猜测应该是MappedStatement生成的时候出了问题
image
所以我觉得应该是不支持重载的

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss discuss a problem enhancement New feature or request or suggestion perfect Improve knowledge points or descriptions
Projects
None yet
Development

No branches or pull requests

7 participants
@plusmancn @ZeroMing @YAsheep @Snailclimb @charlienss @heihei180 and others