MyBatis 作為半自動 ORM 框架,以 SQL 可控、映射靈活、學習曲線平緩深受國內開發者喜愛。本文系統講解 Spring (4.x/5.x) 整合 MyBatis (3.4+) 的完整配置體系,涵蓋 XML 與註解雙模式、分頁插件、類型處理器、事務管理等生產級最佳實踐。
| 維度 | JDBC Template | Hibernate | MyBatis |
|---|---|---|---|
| 配置複雜度 | 低 | 高 | 中 |
| SQL 可控性 | 完全手寫 | HQL/Criteria | XML/註解映射 |
| 複雜查詢 | 字符串拼接痛苦 | 難以優化 | 動態 SQL 強大 |
| 多表關聯 | 手寫映射 | 關係映射複雜 | ResultMap 靈活 |
| 學習曲線 | 平 | 陡峭 | 緩和 |
| 適用場景 | 簡單增刪改查 | 領域模型豐富 | SQL 驅動、報表、遷移 |
<properties>
<spring.version>5.3.21</spring.version>
<mybatis.version>3.5.13</mybatis.version>
<mybatis-spring.version>2.0.7</mybatis-spring.version>
<mysql.version>8.0.33</mysql.version>
<druid.version>1.2.18</druid.version>
</properties>
<dependencies>
<!-- Spring Core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<!-- Database -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 1. 數據源: Druid 高性能連接池 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
<property name="minIdle" value="5"/>
<property name="maxWait" value="60000"/>
<property name="validationQuery" value="SELECT 1"/>
<property name="testWhileIdle" value="true"/>
<property name="timeBetweenEvictionRunsMillis" value="60000"/>
</bean>
<!-- 2. SqlSessionFactory: MyBatis 核心工廠 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath:mapper/**/*Mapper.xml"/>
<property name="typeAliasesPackage" value="com.example.entity"/>
<property name="configurationProperties">
<props>
<prop key="mapUnderscoreToCamelCase">true</prop>
<prop key="cacheEnabled">true</prop>
<prop key="lazyLoadingEnabled">true</prop>
<prop key="aggressiveLazyLoading">false</prop>
</props>
</property>
<!-- 分頁插件 PageHelper -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<props>
<prop key="helperDialect">mysql</prop>
<prop key="reasonable">true</prop>
<prop key="supportMethodsArguments">true</prop>
<prop key="params">pageNum=pageNum;pageSize=pageSize</prop>
</props>
</property>
</bean>
</array>
</property>
</bean>
<!-- 3. Mapper 掃描器: 自動掃描 Dao 接口生成代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 事務管理 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
mapperLocations:Ant 路徑匹配 Mapper XML,支持多模塊 classpath*:mapper/**/*Mapper.xmltypeAliasesPackage:實體類別名簡寫,XML 中直接用 resultType="User"configurationProperties:覆蓋 MyBatis 全局配置 (駝峰映射、緩存、懶加載等)plugins:攔截器鏈,集成 PageHelper、通用 Mapper、加密插件等basePackage 下所有接口MapperFactoryBean 註冊到 Spring 容器SqlSessionTemplate@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
}
@Repository
public interface UserDao {
@Select("SELECT * FROM t_user WHERE id = #{id}")
User getById(Long id);
@Insert("INSERT INTO t_user(username, email, create_time) VALUES(#{username}, #{email}, NOW())")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE t_user SET username=#{username}, email=#{email} WHERE id=#{id}")
int update(User user);
@Delete("DELETE FROM t_user WHERE id=#{id}")
int deleteById(Long id);
// 複雜查詢建議用 XML
List<User> findByCondition(@Param("username") String username,
@Param("email") String email,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
<?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.dao.UserDao">
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
<result property="createTime" column="create_time"/>
</resultMap>
<sql id="baseColumns">
id, username, email, create_time
</sql>
<select id="findByCondition" resultMap="userMap">
SELECT <include refid="baseColumns"/>
FROM t_user
<where>
<if test="username != null and username != ''">
AND username LIKE CONCAT('%', #{username}, '%')
</if>
<if test="email != null and email != ''">
AND email LIKE CONCAT('%', #{email}, '%')
</if>
</where>
ORDER BY create_time DESC
</select>
</mapper>
public interface UserDao extends BaseMapper<User> {
// 自動擁有: selectById, insert, updateById, deleteById, selectList, selectPage...
}
@MappedTypes(UserStatus.class)
public class UserStatusTypeHandler extends BaseTypeHandler<UserStatus> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, UserStatus param, JdbcType jdbcType) {
ps.setInt(i, param.getCode());
}
@Override
public UserStatus getNullableResult(ResultSet rs, String colName) {
return UserStatus.of(rs.getInt(colName));
}
// 其他 getNullableResult 重載...
}
// 註冊: configuration.getTypeHandlerRegistry().register(UserStatus.class, UserStatusTypeHandler.class);
@Primary
@Bean
public DataSource masterDataSource() { ... }
@Bean
public DataSource slaveDataSource() { ... }
@Bean
public DynamicDataSource dataSource() {
DynamicDataSource ds = new DynamicDataSource();
ds.setTargetDataSources(Map.of("master", masterDataSource(), "slave", slaveDataSource()));
ds.setDefaultTargetDataSource(masterDataSource());
return ds;
}
// AOP 切換: @Master / @Slave 註解 + AbstractRoutingDataSource
@Service
@Transactional(rollbackFor = Exception.class)
public class UserService {
@Autowired UserDao userDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertWithLog(User user) {
userDao.insert(user);
logDao.insert(new Log("INSERT", user.getId()));
}
}
| 現象 | 原因 | 解決 |
|---|---|---|
| BindingException: Invalid bound statement | Mapper 接口方法與 XML id 不匹配 / XML 未掃描 | 檢查 namespace、id、mapperLocations 路徑 |
| 駝峰映射失效 | mapUnderscoreToCamelCase 未生效 | SqlSessionFactoryBean configurationProperties 設置 |
| 分頁插件不生效 | PageInterceptor 未註冊 / 參數名不匹配 | plugins 配置 + params=pageNum=pageNum;pageSize=pageSize |
| 事務不回滾 | 未掃描 @Transactional / 方法非 public / 捕獲異常未拋出 | tx:annotation-driven + rollbackFor=Exception.class |
Spring + MyBatis 整合核心三件套:DataSource + SqlSessionFactory + MapperScanner。掌握 XML 動態 SQL、註解簡單映射混用、分頁插件、類型處理器、事務傳播、多數據源 等進階模式,即可應對 95% 的持久層開發場景。建議新項目優先考慮 MyBatis-Plus 以獲得開箱即用的 CRUD、分頁、條件構造器等增強能力。