Resources.getResourceAsReader("mybatis-config.xml")
↓
SqlSessionFactoryBuilder().build(reader) → SqlSessionFactory
↓
sqlSessionFactory.openSession() → SqlSession
↓
session.getMapper(Mapper接口) → Mapper代理對象
↓
mapper.xxx() → 執行 SQL → 返回結果
↓
session.close() / session.commit()
mybatis-config.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>
<properties resource="jdbc.properties"/>
<typeAliases>
<package name="com.example.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.example.mapper"/>
</mappers>
</configuration>
UserMapper.javapublic interface UserMapper {
User selectById(@Param("id") Long id);
List selectAll();
int insert(User user);
int update(User user);
int deleteById(Long id);
}
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">
<resultMap id="userMap" type="User">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="email" column="email"/>
</resultMap>
<select id="selectById" resultMap="userMap">
SELECT id, username, email FROM t_user WHERE id = #{id}
</select>
<select id="selectAll" resultMap="userMap">
SELECT id, username, email FROM t_user
</select>
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO t_user(username, email) VALUES(#{username}, #{email})
</insert>
<update id="update" parameterType="User">
UPDATE t_user SET username=#{username}, email=#{email} WHERE id=#{id}
</update>
<delete id="deleteById">
DELETE FROM t_user WHERE id = #{id}
</delete>
</mapper>
public class MyBatisXmlTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
}
}
@Test
public void testCrud() {
try (SqlSession session = sqlSessionFactory.openSession(true)) { // autoCommit=true
UserMapper mapper = session.getMapper(UserMapper.class);
// 插入
User user = new User(null, "alice", "alice@example.com");
mapper.insert(user);
System.out.println("生成 ID: " + user.getId());
// 查詢
User found = mapper.selectById(user.getId());
assertEquals("alice", found.getUsername());
// 更新
found.setEmail("alice_new@example.com");
mapper.update(found);
// 刪除
mapper.deleteById(user.getId());
}
}
}
public interface UserMapper {
@Select("SELECT id, username, email FROM t_user WHERE id = #{id}")
@Results(id = "userMap", value = {
@Result(property = "id", column = "id", id = true),
@Result(property = "username", column = "username"),
@Result(property = "email", column = "email")
})
User selectById(Long id);
@Select("SELECT id, username, email FROM t_user")
@ResultMap("userMap")
List selectAll();
@Insert("INSERT INTO t_user(username, email) VALUES(#{username}, #{email})")
@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);
}
註解模式啟動:Configuration 掃描包 <package name="com.example.mapper"/> 即可自動解析註解,無需 XML 文件。
// ❌ 錯誤:單例共享
@Component
public class BadDao {
@Autowired SqlSession session; // 併發下數據錯亂
}
// ✅ 正確:每次業務獲取新會話
@Repository
public class GoodDao {
@Autowired SqlSessionFactory factory;
public User getUser(Long id) {
try (SqlSession session = factory.openSession()) {
return session.getMapper(UserMapper.class).selectById(id);
}
}
}
| 模式 | 獲取方式 | 提交/回滾 | 適用場景 |
|---|---|---|---|
| 手動事務 | openSession(false) | session.commit() / session.rollback() | 批量導入、複雜業務邏輯 |
| 自動提交 | openSession(true) | 每條語句自動提交 | 簡單查詢、測試代碼 |
| Spring 管理 | @Transactional + Mapper 注入 | 框架自動控制 | 企業級應用 (推薦) |
// SIMPLE: 預設,每條語句創建 PreparedStatement
factory.openSession(ExecutorType.SIMPLE);
// REUSE: 重用 PreparedStatement
factory.openSession(ExecutorType.REUSE);
// BATCH: 批量更新,需手動 commit/flush
factory.openSession(ExecutorType.BATCH);
// 1. Mapper 掃描 (無需手動獲取 SqlSession)
@MapperScan("com.example.mapper")
@Configuration
public class MyBatisConfig {}
// 2. Service 直接注入 Mapper
@Service
@RequiredArgsConstructor
public class UserService {
private final UserMapper userMapper; // Spring 自動注入代理
@Transactional
public void register(User user) {
userMapper.insert(user); // 同一事務內
// 其他業務...
}
}
| 現象 | 原因 | 解決 |
|---|---|---|
BindingException: Invalid bound statement | Namespace/ID 拼寫錯誤、XML 未掃描 | 核對 namespace = 接口全限定名、id = 方法名、mapperLocations 路徑 |
| 插入後 ID 為 null | 缺 useGeneratedKeys="true" keyProperty="id" | XML/註解添加自增主鍵回填配置 |
| 關聯查詢字段為 null | 列名與屬性名不匹配 | 用 <resultMap> 或 SQL 別名 AS 映射 |
| 併發下數據錯亂 | SqlSession 被多線程共享 | 每請求一個 Session,或用 Spring 代理 Mapper |
| 事務不生效 | 未開啟 @EnableTransactionManagement 或調用非公共方法 | 檢查事務配置、方法可見性、異常回滾規則 |
獲取 SqlSession 核心三步:Resources → SqlSessionFactoryBuilder → SqlSessionFactory → openSession()。XML 模式適合複雜 SQL、動態條件、存儲過程;註解模式適合簡單 CRUD、微服務輕量化。生產環境強烈建議整合 Spring,由容器管理 SqlSession 生命週期與事務,直接注入 Mapper 接口使用,避免手動管理帶來的線程安全與事務邊界問題。