使用 JDK1.8 default 关键字解决 MyBatis 不支持方法重载问题
配置:
mybatis >= 3.4.2
JDK >= 1.8
问题
MyBatis 是不支持在接口中定义重载方法(如下代码片段)
的,然而方法重载是一个兼容复杂业务重要手段。
public interface AccountMapper {
public List<Account> getAccounts(final String status);
public List<Account> getAccounts(final String status, final int owner);
}
场景
用户
和账户
,每个用户可以拥有多个账户,这是一个 1:N 的关系。我们简单定义它们的属性:
- Account.java
public class Account {
// 主键
private int id;
// 账户名字
private String name;
// 账户状态:active 或者 inactive
private String status;
// 拥有者id
private int owner;
// setters & getters
}
- Owner.java
我并不会使用到 Owner 这个对象,写出来只是让大家有个简单理解。
public class Owner {
private int id;
private String name;
// setters & getters
}
我们尝试实现如下 2 个功能:
- 根据状态
status
获取所有 Account; - 根据状态
status
和所有者owner
获取所有 Account;
一个可能方案(使用注解@Param
只是我的习惯,不是必要,只要参数名字能对上即可):
- AccountMapper.java
public interface AccountMapper {
/**
* 根据状态获取所有Account
*
* @param status active or inactive
* @return list of accounts
*/
List<Account> getAccountsByStatus(@Param("status") String status);
/**
* 根据状态和所有者获取所有Account
*
* @param status active or inactive
* @param owner owner id
* @return list of accounts
*/
List<Account> getAccountsByStatusAndOwner(@Param("status") String status, @Param("owner") int owner);
}
上面的实现是因为传入的参数数量不一致,所以需要拆开两个方法(深层次原因是因为mybatis不支持相同的方法名字但参数不同但方法,简单说重载方法会出现问题)
。当参数的数量不断增加,日后可能需要写更多的方法,如何解决?
相信聪明的你已经想到:
- 提供全部参数签名的方法,结合动态 SQL 实现
- 使用 HashMap 来包裹参数,结合动态 SQL 实现
一个可能的写法:
- AccountMapper.java
/**
* get accounts according to status and owner
*
* @param status active or inactive
* @param owner owner id
* @return list of accounts
*/
List<Account> getAccounts(@Param("status") String status, @Param("owner") int owner);
或者:
/**
*get accounts by conditions
*
* @param args conditions
* @return list of accounts
*/
List<Account> getAccounts(Map<String, Object> args);
- AccountMapp.xml
<select id="getAccounts" resultType="overwrite.Account">
select * from ACCOUNT
<where>
<if test="status != null">
status = #{status}
</if>
<if test="owner != null">
and owner = #{owner}
</if>
</where>
</select>
上面两种方法,都可以实现需求,但是无论哪种实现,都有其缺憾
:
- 带所有参数都方法:每次都需要显式传递 null 或 - 1 来辨别是否有值,
调用方式丑陋
- 使用 Map 包裹:调用者无法知道有所有可选参数以及参数的名字,
调用参数不清晰
优雅的方案
使用 JDK1.8 提供的 default 关键字,并结合动态 SQL 可以很好的处理这个问题:
- AccountMapper.java
这里实现了方法的重载
,较少参数的方法使用default
关键字实现,调用其他重载方法并出入默认值。
public interface AccountMapper {
/**
* get accounts according to status
*
* @param status active or inactive
* @return list of accounts
*/
default List<Account> getAccounts(@Param("status") String status) {
return this.getAccounts(status, -1);
}
/**
* get accounts according to status and owner
*
* @param status active or inactive
* @param owner owner id
* @return list of accounts
*/
List<Account> getAccounts(@Param("status") String status, @Param("owner") int owner);
}
- AccountMapper.xml
status
是一个强制的参数(当前这个场景),而owner
则属于可选参数(可以附带更多其他可选参数)。
<select id="getAccounts" resultType="overwrite.Account">
select * from ACCOUNT
where status = #{status}
<!-- optional arguments -->
<if test="owner > 0">
and owner = #{owner}
</if>
</select>
- Main.java
public class Main {
public static void main(String[] args) throws Exception {
final String configuration = "overwrite/mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(configuration);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession();
AccountMapper mapper = session.getMapper(AccountMapper.class);
// 使用只有一个status参数的重载方法
List<Account> accounts = mapper.getAccounts("active");
accounts.forEach(account -> {
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getStatus());
System.out.println(account.getOwner());
});
// 使用拥有两个参数(status和owner)的重载方法
List<Account> accounts2 = mapper.getAccounts("inactive", 1);
accounts2.forEach(account -> {
System.out.println(account.getId());
System.out.println(account.getName());
System.out.println(account.getStatus());
System.out.println(account.getOwner());
});
}
}
欢迎大家留言讨论,另外完整的例子:mybatis-examples