iBATIS 2.x 時代以 SQL 映射聞名,2010 年更名為 MyBatis 3 並遷移至 Apache。雖核心理念「SQL 獨立於程式碼」不變,但 MyBatis 在介面綁定、動態 SQL、型別處理、緩存、插件機制等方面進行重大重構。本文系統梳理核心差異,助你平滑遷移或技術選型。
| 維度 | iBATIS 2.x | MyBatis 3 | 影響 |
|---|---|---|---|
| DAO 開發模式 | 繼承 SqlMapClientDaoSupport,手寫實作類 | 介面綁定:Mapper 介面 + 註解/XML,自動代理實作 | 極大減少樣板碼,支援多態、單元測試 |
| 動態 SQL | <isEqual>、<iterate> 等標籤 | OGNL 表達式:<if>、<choose>、<where>、<trim>、<foreach> | 功能更強、語法更簡潔、支援複雜條件組合 |
| 關聯映射 | 嵌套查詢(N+1 問題嚴重) | 嵌套結果映射:<resultMap> + <association>/<collection> | 單查詢完成物件圖組裝,效能提升數量級 |
| 型別處理 | TypeHandler 介面 | TypeHandler 介面 + 自動掃描註冊、@MappedTypes、@MappedJdbcTypes | 擴充更簡單,支援 Java 8 時間類型、Enum、JSON 等 |
| 緩存機制 | 簡單二級緩存 | 多層緩存:一級 (Session) + 二級 (Mapper) + 第三方整合 | 更靈活的緩存策略,支援 Ehcache、Redis、Caffeine |
| 插件/攔截器 | 無 | Interceptor 鏈:攔截 Executor、StatementHandler、ParameterHandler、ResultSetHandler | 分頁、加密、審計、多租戶等橫切關注點零侵入 |
| 配置方式 | 純 XML | XML + 註解 + Java API (Configuration) | 微服務、Spring Boot 整合更自然 |
| 最低 JDK | 1.4+ | 8+ (3.5+) | 享受 Lambda、Stream、Optional 等現代特性 |
iBATIS 需手寫 UserDaoImpl extends SqlMapClientDaoSupport,MyBatis 僅需:
// Mapper 介面
public interface UserMapper {
@Select("SELECT * FROM user WHERE id = #{id}")
User selectById(Long id);
@Insert("INSERT INTO user(name, age) VALUES(#{name}, #{age})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
List<User> selectByCondition(@Param("cond") UserCondition cond); // XML 對應
}
MyBatis 以 MapperProxy 動態代理介面,執行時解析註解或 XML 中的 <select id="selectByCondition">,零樣板碼。
iBATIS 僅支援 <resultMap> + <statement> 發起子查詢。MyBatis 引入 <association> (一對一) 與 <collection> (一對多):
<resultMap id="userMap" type="User">
<id property="id" column="u_id"/>
<result property="name" column="u_name"/>
<association property="profile" javaType="Profile">
<id property="id" column="p_id"/>
<result property="bio" column="p_bio"/>
</association>
<collection property="roles" ofType="Role">
<id property="id" column="r_id"/>
<result property="name" column="r_name"/>
</collection>
</resultMap>
<select id="selectUserWithDetails" resultMap="userMap">
SELECT u.id u_id, u.name u_name,
p.id p_id, p.bio p_bio,
r.id r_id, r.name r_name
FROM user u
LEFT JOIN profile p ON u.id = p.user_id
LEFT JOIN user_role ur ON u.id = ur.user_id
LEFT JOIN role r ON ur.role_id = r.id
WHERE u.id = #{id}
</select>
單條 SQL 完成物件圖組裝,避免 N+1。注意:一對多集合映射不支援分頁(MyBatis 限制),需改用嵌套查詢或應用層分頁。
MyBatis 採用 OGNL,表達力遠超 iBATIS:
<select id="selectByCondition" resultType="User">
SELECT * FROM user
<where>
<if test="cond.name != null">AND name LIKE CONCAT('%', #{cond.name}, '%')</if>
<if test="cond.age != null">AND age = #{cond.age}</if>
<if test="cond.roles != null and cond.roles.size() > 0">
AND id IN
<foreach collection="cond.roles" item="roleId" open="(" separator="," close=")">
#{roleId}
</foreach>
</if>
</where>
ORDER BY create_time DESC
</select>
<where> 自動處理首個 AND/OR<trim> 更精細控制前後綴<foreach> 遍歷集合/陣列/Map,支援 index 變數MyBatis 最經典插件場景——分頁攔截器:
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PaginationInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 獲取 MappedStatement、參數、RowBounds
// 修改 SQL 加上 LIMIT/OFFSET 或 Oracle ROWNUM
return invocation.proceed();
}
@Override public Object plugin(Object target) { return Plugin.wrap(target, this); }
@Override public void setProperties(Properties properties) {}
}
註冊後,所有 selectList 自動獲得分頁能力,業務碼零侵入。
SqlMapConfig.xml → mybatis-config.xml,命名空間更新為 https://mybatis.org/schema/mybatis-3-config.xsd。isEqual→if test、iterate→foreach、isNotEmpty→if test="list != null and list.size()>0"。#param# → #{param},$param$ → ${param}(原樣輸出,慎用防注入)。TypeHandler<T>,註解 @MappedTypes、@MappedJdbcTypes,配置 <typeHandlers>。<cache> 標籤屬性調整,引入第三方緩存需實作 Cache 介面。MyBatis 不僅是 iBATIS 的改名版,更是針對 Java 現代開發(介面導向、註解驅動、插件擴展、OGNL 表達式)的全面重構。掌握介面綁定、嵌套結果映射、動態 SQL 與插件機制四大核心,即可發揮 MyBatis 最大價值:SQL 完全可控、物件映射零樣板、橫切關注點零侵入。