MyBatis-Spring 整合包提供了多種 DAO 實現模式,本文系統講解三大核心方案的配置原理、代碼結構、優缺點對比及選型建議,基於 MyBatis-Spring 2.0+、Spring 5.x/6.x 環境驗證。
<dependencies>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.30</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.30</version>
</dependency>
<!-- MyBatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.16</version>
</dependency>
<!-- 整合包(關鍵) -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 數據源/連接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
</dependencies>
@Configuration
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource() {
HikariConfig config = new HikariConfig();
config.setDriverClassName(driver);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
return new HikariDataSource(config);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
// 可選:指定 MyBatis 總配置文件
factoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml"));
// 可選:指定 Mapper 掃描路徑
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/**/*.xml"));
// 可選:類型別名包
factoryBean.setTypeAliasesPackage("com.example.entity");
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
// 掃描 Mapper 接口(推薦方式,替代方案一的手動註冊)
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer configurer = new MapperScannerConfigurer();
configurer.setBasePackage("com.example.mapper");
configurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return configurer;
}
}
特點:無需寫 XML,使用註解定義 SQL,適合簡單 CRUD。
@Mapper // 或由 MapperScannerConfigurer 掃描
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(@Param("id") Long id);
@Insert("INSERT INTO users(username, email) VALUES(#{username}, #{email})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(User user);
@Update("UPDATE users SET username=#{username}, email=#{email} WHERE id=#{id}")
int update(User user);
@Delete("DELETE FROM users WHERE id = #{id}")
int deleteById(Long id);
@Select("SELECT * FROM users WHERE username LIKE CONCAT('%', #{keyword}, '%')")
List searchByName(@Param("keyword") String keyword);
}
@Service
@Transactional
public class UserServiceImpl implements UserService {
private final UserMapper userMapper; // 構造器注入
public UserServiceImpl(UserMapper userMapper) {
this.userMapper = userMapper;
}
@Override
public User getUser(Long id) {
return userMapper.findById(id);
}
@Override
public User createUser(User user) {
userMapper.insert(user);
return user; // 回填自增 ID
}
}
| 優點 | 缺點 |
|---|---|
| 無 XML 文件,代碼集中 | 複雜 SQL 可讀性差,難以維護 |
| 重構友好,IDE 支持跳轉 | 動態 SQL 編寫繁瑣(需用 Script 標籤) |
| 適合微服務輕量級服務 | DBA 難以獨立優化 SQL |
特點:傳統 XML Mapper + 手動調用 SqlSession 方法,靈活控制。
<!-- resources/mapper/UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<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>
<select id="findById" resultMap="userMap">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(username, email) VALUES(#{username}, #{email})
</insert>
</mapper>
@Repository
public class UserDaoImpl implements UserDao {
private final SqlSessionTemplate sqlSession;
public UserDaoImpl(SqlSessionTemplate sqlSession) {
this.sqlSession = sqlSession;
}
@Override
public User findById(Long id) {
// 完全限定名:namespace + id
return sqlSession.selectOne("com.example.mapper.UserMapper.findById", id);
}
@Override
public void insert(User user) {
sqlSession.insert("com.example.mapper.UserMapper.insert", user);
}
@Override
public List findByCondition(UserQuery query) {
return sqlSession.selectList("com.example.mapper.UserMapper.findByCondition", query);
}
}
| 優點 | 缺點 |
|---|---|
| SQL 與代碼分離,DBA 友好 | 字符串硬編碼 namespace+id,重構易出錯 |
| 支持複雜動態 SQL | 樣板代碼多(selectOne/insert/update/delete) |
| 可精確控制事務邊界 | 需手動管理 SqlSession 生命周期(模板已處理) |
特點:繼承框架基類,通過 getSqlSession() 獲取模板,最傳統方式。
@Repository
public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
// 注入 SqlSessionFactory 或 SqlSessionTemplate
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
@Override
public User findById(Long id) {
return getSqlSession().selectOne("com.example.mapper.UserMapper.findById", id);
}
@Override
public void insert(User user) {
getSqlSession().insert("com.example.mapper.UserMapper.insert", user);
}
}
<bean id="userDao" class="com.example.dao.UserDaoImpl">
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
推薦配置:
@Configuration
@MapperScan(basePackages = "com.example.mapper",
sqlSessionFactoryRef = "sqlSessionFactory")
public class MyBatisConfig {
// SqlSessionFactory 配置同上
}
@Select/@Insert 等)<where>/<if>/<choose>)@SelectProvider 類
@Mapper
public interface UserMapper {
// 簡單查詢用註解
@Select("SELECT * FROM users WHERE id = #{id}")
User findById(Long id);
// 複雜查詢對應 XML:UserMapper.xml 中定義 findActiveUsers
List findActiveUsers(@Param("since") LocalDateTime since);
// 批量插入用註解
@Insert("")
int batchInsert(@Param("list") List users);
}
@Configuration
@EnableTransactionManagement
public class TxConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// Service 使用
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl implements UserService {
// MyBatis-Spring 自動參與 Spring 事務
// SqlSessionTemplate 綁定當前線程事務
}
| 場景 | 推薦方案 | 關鍵配置 |
|---|---|---|
| 新項目、微服務、簡單業務 | 方案一 + MapperScan | 註解為主,XML 為輔 |
| 傳統項目遷移、DBA 主導 SQL | 方案二/三 + XML | SqlSessionTemplate 注入 |
| 複雜報表、動態 SQL 多 | 方案二 + XML | 手動調用 selectList |
| 需精確控制 SqlSession 生命周期 | 方案二 | sqlSessionFactory.openSession() |
| 現代 Spring Boot 項目 | MyBatis-Spring-Boot-Starter | 自動配置,零 XML |
@MapperScan 包路徑、接口是否有 @Mapper、編譯輸出目錄mapperLocations 路徑匹配、namespace 全限定名一致、resources 目錄打包@EnableTransactionManagement、DataSourceTransactionManager、方法非 private、異常類型匹配SqlSessionFactory、MapperScan 指定 sqlSessionFactoryRef理解三種方案的本質區別:方案一是接口代理(JDK 動態代理),方案二/三是模板委託。現代開發推薦 @MapperScan + 註解/XML 混合,兼顧開發效率與 SQL 可維護性。