Mybatis的缺陷

Mybatis 是业界非常流行的持久层框架,轻量级、易用,在金融 IT 领域完全是领军地位,比 Hibernate 更受欢迎,优势非常多,也是非常值得我们学习的。但 Mybatis 并不尽善尽美,其自身的设计、编码也还有许多不足,甚至是缺陷,这篇文章来简要讨论一下这些缺陷:

1.Mybatis 使用 DTD 作为 XML 配置文件的校验文件,但是很明显,DTD 差不多是快被淘汰的技术了,功能非常有限,扩展性非常差,扩展性非常差,扩展性非常差,可读性也不好,Spring 能够从 DTD 到 XSD 华丽转身,但 Mybatis 始终没这个魄力。

2. 版本兼容性做的不好,就拿 3.3.0—>3.4.0 来说,按业界通用规范,第 2 级版本号升级,可以添加功能,但是要保证向下兼容性,然而 Mybatis 的做法并不完全是这样的,看一下关键接口 StatementHandler 的关键方法 prepare:

// 3.3.0
Statement prepare(Connection connection)
      throws SQLException;

// 3.4.0
Statement prepare(Connection connection, Integer transactionTimeout)
throws SQLException;

这里没有添加一个方法,而是直接在原方法中添加了一个参数!类似例子还有不少,就不一一列举了。

3.Mybatis 的插件,采用一个通用的 Interceptor 接口,配以 @Intercepts、@Signature 等注解,实现对多个组件的多种方法的拦截,看似非常灵活,在我看来其实是结构不够清晰,实际开发时,你会把对 StatementHandler 和 ResultSetHandler 的拦截增强放在一个类里面吗?不会是吧(会?你当单一职责原则、开闭原则都是狗屎吗),那有什么必要强制使用同一个接口呢?

另外,使用 @Signature 注解来设别需要被拦截的组件方法,如果注解有错,编译也是不会报错的,而只能等到运行时才能发现,再看上面的例子:

假设我针对 3.3.0 版本实现了一个插件:

@Intercepts({ 
    @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class })})
public class StatementHandlerInterceptor implements Interceptor {
@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> Object intercept(Invocation invocation) <span style="color: rgba(0, 0, 255, 1)">throws</span><span style="color: rgba(0, 0, 0, 1)"> Throwable {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> invocation.proceed();
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Object plugin(Object target) {
    </span><span style="color: rgba(0, 0, 255, 1)">return</span> Plugin.wrap(target, <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
}

@Override
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> setProperties(Properties properties) {
    
}

}

然后,升级为 3.4.0,结果呢,编译一直正常,但是等到运行,却抛出异常了。

4.Mybatis 的缓存简直就是鸡肋,而且不管有没有配置需要使用缓存、是否更新缓存,都要去计算 CacheKey,不使用缓存、也不更新缓存的情况下,这种计算完全是浪费。

5.Mybatis 的批量执行,看下面的一个 JDBC 例子:

public void testJdbcBatch(Connection conn) throws Exception {
try{ conn.setAutoCommit(false); batchUpdate(conn); clearTestData(conn); conn.commit(); conn.setAutoCommit(true);}catch(Exception e){ conn.rollback(); throw e; } }

private void clearTestData(Connection conn) throws SQLException {
PreparedStatement ps
= null;
try{
ps
= conn.prepareStatement("delete TABLE_NAME1 where FIELD_NAME1 = ?");
ps.setString(
1, "TEST");
int d = ps.executeUpdate();
System.out.println(
"delete counts :" + d);
}
finally{
try{
ps.close();
}
catch(Exception e){}
}
}

private void batchUpdate(Connection conn) throws SQLException {
PreparedStatement ps
= null;
try{
String sql
= "INSERT INTO TABLE_NAME2(FIELD_NAME1, FIELD_NAME2, FIELD_NAME2)VALUES(?,?,?)";
ps
= conn.prepareStatement(sql);
for(int i = 0; i < 10; i++){
String random
= RandomStringUtils.randomAlphabetic(8);
ps.setString(
1, "TEST");//FIELD_NAME1
ps.setString(2, "数据" + random);//FIELD_NAME2
ps.setString(3, "参数" + random);//FIELD_NAME3
ps.addBatch();
}
int[] rs = ps.executeBatch();
}
finally{
try{
ps.close();
}
catch(Exception e){}
}
}

代码没有什么违和感,能够执行正常,也可以按预期的回滚,也就是说同一个事务中的同一个 connection,可以同时运行普通 sql 和 batch,但是你在同一个事务的 SqlSession 中试试,反馈给你的是——不能在同一个事务中切换执行方式!

6、数据库产品的兼容性:Mybatis 把 SQL 的控制权交给了开发人员,于是从道德上占据了制高点——你写的不兼容,那是你自己的水平不行!但,这是一个真正的优秀框架的正确姿势吗?为什么就不能提供一些辅助性的兼容实施?比如说在 Oracle 中被奉为神明的 DECODE 函数,是否可以在 SqlMapper 中提供一个 <decode> 标签,在后面默默的修改成 CASE WHEN?或者说,官方不提供没有关系,但你得提供扩展方式啊,于是又回到了:扩展性非常差,扩展性非常差,扩展性非常差。重要的事说三遍,但,我已经说六遍了。