Skip to content
New issue

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

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

Already on GitHub? # to your account

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

Closed
charlienss opened this issue Mar 10, 2021 · 7 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接口中写重载方法。

总结:

Mybatis的Dao接口可以有多个重载方法但是方法对应的映射必须只有一个也就是Mapper的XML中定义的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
所以我觉得应该是不支持重载的

@WFUing
Copy link

WFUing commented Aug 9, 2024

LGTM

# for free to join this conversation on GitHub. Already have an account? # 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

8 participants
@plusmancn @ZeroMing @YAsheep @Snailclimb @charlienss @heihei180 @WFUing and others