<dependencies>
<!-- MyBatis 核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
<!-- 數據庫驅動 (示例 MySQL) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.33</version>
</dependency>
<!-- 日志 (Log4j2 + SLF4J) -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j2-impl</artifactId>
<version>2.20.0</version>
</dependency>
<!-- 單元測試 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mybatis_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8 jdbc.username=root jdbc.password=123456
<?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>
<properties resource="db.properties"/>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 駝峰映射 -->
<setting name="logImpl" value="SLF4J"/> <!-- 日志實現 -->
<setting name="cacheEnabled" value="true"/> <!-- 二級緩存 -->
</settings>
<typeAliases>
<package name="com.demo.entity"/> <!-- 別名掃描 -->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<package name="com.demo.mapper"/> <!-- 接口掃描 -->
</mappers>
</configuration>
// User.java
package com.demo.entity;
public class User {
private Long id;
private String username;
private String password;
private String email;
// getter/setter/toString 省略
}
// UserMapper.java
package com.demo.mapper;
import com.demo.entity.User;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface UserMapper {
@Insert("INSERT INTO t_user(username,password,email) VALUES(#{username},#{password},#{email})")
@Options(useGeneratedKeys = true, keyProperty = "id") // 回寫自增 ID
int insert(User user);
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
@Select("SELECT * FROM t_user")
List selectAll();
@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);
}
// MyBatisApp.java
package com.demo;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.*;
import java.io.IOException;
import java.io.Reader;
public class MyBatisApp {
private static SqlSessionFactory sqlSessionFactory;
static {
try (Reader reader = Resources.getResourceAsReader("SqlMapConfig.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) { throw new RuntimeException(e); }
}
public static SqlSession openSession() { return sqlSessionFactory.openSession(); }
}
// UserMapperTest.java
package com.demo.mapper;
import com.demo.MyBatisApp;
import com.demo.entity.User;
import org.junit.jupiter.api.*;
import java.util.List;
class UserMapperTest {
private SqlSession sqlSession;
private UserMapper mapper;
@BeforeEach void init() {
sqlSession = MyBatisApp.openSession();
mapper = sqlSession.getMapper(UserMapper.class);
}
@AfterEach void close() { if (sqlSession != null) sqlSession.close(); }
@Test void crud() {
User u = new User(); u.setUsername("test"); u.setPassword("123"); u.setEmail("test@qq.com");
Assertions.assertEquals(1, mapper.insert(u));
Assertions.assertNotNull(u.getId()); // 回寫 ID
sqlSession.commit();
User saved = mapper.selectById(u.getId());
Assertions.assertEquals("test", saved.getUsername());
saved.setEmail("new@qq.com");
Assertions.assertEquals(1, mapper.update(saved));
sqlSession.commit();
List all = mapper.selectAll();
Assertions.assertTrue(all.size() >= 1);
Assertions.assertEquals(1, mapper.deleteById(u.getId()));
sqlSession.commit();
}
}
複雜 SQL 建議用 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.demo.mapper.UserMapper">
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="email" column="email"/>
</resultMap>
<sql id="cols">id, username, password, email</sql>
<select id="selectById" resultMap="userMap">
SELECT <include refid="cols"/> FROM t_user WHERE id = #{id}
</select>
<select id="selectAll" resultMap="userMap">
SELECT <include refid="cols"/> FROM t_user
</select>
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username,password,email) VALUES(#{username},#{password},#{email})
</insert>
<update id="update">
UPDATE t_user SET username=#{username}, email=#{email} WHERE id=#{id}
</update>
<delete id="deleteById">
DELETE FROM t_user WHERE id=#{id}
</delete>
</mapper>
此時 SqlMapConfig.xml 中 <mappers> 可改為 <mapper resource="com/demo/mapper/UserMapper.xml"/> 或保持 <package> 掃描 (需命名規約一致)。
| 現象 | 原因 | 解決 |
|---|---|---|
| BindingException: Invalid bound statement | Mapper 接口方法與 XML id/參數不匹配 | 檢查 namespace、id、parameterType/resultType |
| 自增 ID 未回寫 | 缺少 @Options(useGeneratedKeys=true, keyProperty="id") 或 XML useGeneratedKeys | 補全配置 |
| 駝峰映射失效 | 未開啟 mapUnderscoreToCamelCase | settings 中設為 true |
| 事務未提交 | 默認非自動提交,需手動 sqlSession.commit() | 業務結束調用 commit() |
| 連接泄漏 | 未關閉 SqlSession | try-with-resources 或 finally 關閉 |
MyBatis 以「SQL 為中心」設計,配置文件 + Mapper 接口 + 實體類 三件套構建輕量級持久層。掌握 SqlSessionFactory 構建、SqlSession 生命周期、resultMap 高級映射、動態 SQL (<if>/<where>/<foreach>) 是進階必經之路。建議從註解入門,複雜查詢轉 XML,享受「寫原生 SQL 的可控感」與「對象映射的便利性」雙重紅利。