MyBatis 以「SQL 映射」聞名,靈活、透明、易調優。本教程從零開始,涵蓋環境搭建、接口式開發、CRUD 實戰、多表關聯查詢、Spring/Spring MVC 整合、物理分頁插件開發、動態 SQL 掌握、代碼生成器應用、Dao 層擴展九大模塊,構建生產級 MyBatis 應用知識體系。
mybatis-x.x.jar, mysql-connector-java.jar, log4j(日誌)Configuration.xml<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<typeAlias alias="User" type="com.example.model.User"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/example/mapper/UserMapper.xml"/>
</mappers>
</configuration>
// User.java
public class User {
private int id; private String userName; private int userAge; private String userAddress;
// getter/setter 省略
}
// UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" parameterType="int" resultType="User">
select * from `user` where id = #{id}
</select>
</mapper>
Reader reader = Resources.getResourceAsReader("Configuration.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sqlSessionFactory.openSession();
User user = session.selectOne("com.example.mapper.UserMapper.selectUserById", 1);
session.close();
定義 Mapper 接口,方法名與映射文件 id 一致,參數與結果類型對應:
public interface UserMapper {
User selectUserById(int id);
List<User> selectUsers(String name);
void insertUser(User user);
void updateUser(User user);
void deleteUser(int id);
}
映射文件 namespace 指向接口全限定名,調用方式:
UserMapper mapper = session.getMapper(UserMapper.class);
User user = mapper.selectUserById(1); // 類型安全、IDE 支援、重構友好
<sql id="Base_Column_List">
id, user_name, user_age, user_address
</sql>
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
insert into `user` (user_name, user_age, user_address)
values (#{userName}, #{userAge}, #{userAddress})
</insert>
<update id="updateUser" parameterType="User">
update `user` set user_name=#{userName}, user_age=#{userAge}, user_address=#{userAddress}
where id=#{id}
</update>
<delete id="deleteUser" parameterType="int">
delete from `user` where id=#{id}
</delete>
事務控制:增刪改必須 session.commit() 提交,異常時 session.rollback(),finally 中 session.close()。
// Article.java
public class Article {
private int id; private User user; private String title; private String content;
// getter/setter
}
<association> 多對一(文章 → 用戶)<resultMap id="articleUserMap" type="Article">
<id property="id" column="aid"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
<association property="user" javaType="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<result property="userAddress" column="user_address"/>
</association>
</resultMap>
<select id="selectArticlesWithUser" resultMap="articleUserMap">
select u.id, u.user_name, u.user_address, a.id aid, a.title, a.content
from `user` u, `article` a where u.id = a.user_id
</select>
<collection> 一對多(用戶 → 文章列表)<resultMap id="userArticlesMap" type="User">
<id property="id" column="id"/>
<result property="userName" column="user_name"/>
<collection property="articles" ofType="Article">
<id property="id" column="aid"/>
<result property="title" column="title"/>
<result property="content" column="content"/>
</collection>
</resultMap>
<select id="selectUserWithArticles" parameterType="int" resultMap="userArticlesMap">
select u.id, u.user_name, a.id aid, a.title, a.content
from `user` u left join `article` a on u.id = a.user_id
where u.id = #{id}
</select>
<resultMap id="userMap" type="User"> ... </resultMap>
<resultMap id="articleUserMap2" type="Article">
...
<association property="user" resultMap="userMap"/> <!-- 直接引用已定義 resultMap -->
</resultMap>
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource"> ... </bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:config/Configuration.xml"/>
<property name="mapperLocations" value="classpath*:com/example/mapper/*.xml"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
Service 層注入 Mapper 接口,@Transactional 統一管理事務。
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired private UserMapper userMapper;
@GetMapping("/{id}")
public User getUser(@PathVariable int id) { return userMapper.selectUserById(id); }
@GetMapping
public List<User> listUsers(@RequestParam(defaultValue = "") String name) {
return userMapper.selectUsers("%" + name + "%");
}
@PostMapping
public void createUser(@RequestBody User user) { userMapper.insertUser(user); }
}
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
實現 org.apache.ibatis.plugin.Interceptor,攔截 StatementHandler.prepare,重寫 SQL 追加分頁子句(MySQL LIMIT、Oracle ROWNUM),並自動執行 COUNT(*) 獲取總記錄數。
@Intercepts(@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}))
public class PaginationInterceptor implements Interceptor {
private String dialect = "mysql"; private String pageSqlIdPattern = ".*Page.*";
@Override
public Object intercept(Invocation invocation) throws Throwable {
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
BaseStatementHandler delegate = (BaseStatementHandler) ReflectUtil.getFieldValue(handler, "delegate");
MappedStatement ms = (MappedStatement) ReflectUtil.getFieldValue(delegate, "mappedStatement");
if (ms.getId().matches(pageSqlIdPattern)) {
BoundSql boundSql = delegate.getBoundSql();
Object param = boundSql.getParameterObject();
Page page = extractPage(param); // 從參數提取 Page 對象
// 1. 執行 COUNT 查詢
String countSql = "select count(0) from (" + boundSql.getSql() + ") tmp_count";
// ... 設置參數、執行、設置 page.totalCount
// 2. 改寫分頁 SQL
String pageSql = buildPageSql(boundSql.getSql(), page, dialect);
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
// ... buildPageSql, extractPage 等輔助方法
}
<plugins>
<plugin interceptor="com.example.plugin.PaginationInterceptor">
<property name="dialect" value="mysql"/>
<property name="pageSqlId" value=".*Page.*"/> <!-- 只有 id 匹配的語句才分頁 -->
</plugin>
</plugins>
@GetMapping("/page")
public PageResult<Article> pageArticles(@RequestParam int pageNum, @RequestParam int pageSize) {
Page page = new Page((pageNum-1)*pageSize, pageSize);
List<Article> list = articleMapper.selectArticlePage(page, userId); // Mapper 方法 id 含 "Page"
return new PageResult<>(page.getTotalCount(), list);
}
| 標籤 | 用途 | 典型場景 |
|---|---|---|
<if> | 條件判斷 | 參數非空時追加 WHERE 條件 |
<choose>/<when>/<otherwise> | 互斥分支 | 多條件僅取一(如優先按 ID 查,否則按名稱) |
<trim> | 前後綴處理 | 動態構建 SET/WHERE 子句,自動去除多餘逗號/AND |
<where> | 智能 WHERE | 自動補全 WHERE、去除首個 AND/OR |
<set> | 智能 SET | UPDATE 時自動補全 SET、去除尾部逗號 |
<foreach> | 集合遍歷 | IN 子句、批量 INSERT VALUES |
<foreach> 批量插入範例<insert id="batchInsert" parameterType="java.util.List">
insert into student (name, sex, address, telephone, tid) values
<foreach collection="list" item="item" separator=",">
(#{item.name}, #{item.sex}, #{item.address}, #{item.telephone}, #{item.tId})
</foreach>
</insert>
generatorConfig.xml 關鍵點<context id="mysql" targetRuntime="MyBatis3">
<commentGenerator>
<property name="suppressAllComments" value="true"/> <!-- 簡潔代碼 -->
</commentGenerator>
<jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://..." userId="root" password="pwd"/>
<javaModelGenerator targetPackage="com.example.model" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.example.mapper" targetProject="src/main/resources">
<property name="enableSubPackages" value="true"/>
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.example.mapper" targetProject="src/main/java">
<property name="enableSubPackages" value="true"/>
</javaClientGenerator>
<table tableName="category" domainObjectName="Category">
<property name="enableCountByExample" value="false"/> <!-- 禁用 Example 查詢 -->
</table>
</context>
public class GeneratorMain {
public static void main(String[] args) throws Exception {
List<String> warnings = new ArrayList<>;
ConfigurationParser cp = new ConfigurationParser(warnings);
Configuration config = cp.parseConfiguration(new File("src/main/resources/generatorConfig.xml"));
DefaultShellCallback callback = new DefaultShellCallback(true);
MyBatisGenerator generator = new MyBatisGenerator(config, callback, warnings);
generator.generate(null);
}
}
生成內容:Model、Mapper 接口、Mapper XML(含動態 WHERE Example)、Example 類(可選)。
當 Mapper 接口無法滿足複雜邏輯(多表事務、存儲過程、動態數據源),自定義 Dao:
@Repository
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
@Override
public List<Article> getUserArticles(int userId) {
return getSqlSession().selectList("com.example.mapper.UserMapper.selectUserArticles", userId);
}
}
配置掃描 <context:component-scan base-package="com.example.dao"/>,Service 注入 UserDao 介面。
MyBatis 實戰核心在於:接口式開發消除字符串硬編碼、動態 SQL 構建複雜查詢、結果映射解決關聯對象、分頁插件實現物理分頁、Generator 釋放重複勞動、Spring 整合統一事務管理。掌握這套體系,即可應對從簡單 CRUD 到複雜報表、高併發寫入的全谱系持久層需求。