SqlSession 事務| 組件 | 最低版本 | 建議版本 (2024) |
|---|---|---|
| MyBatis | 3.0+ | 3.5.x |
| Spring | 3.0+ | 5.3.x / 6.1.x |
| MyBatis-Spring | 1.0+ | 3.0.x |
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="txManager" proxy-target-class="true"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
<property name="typeAliasesPackage" value="com.example.dto"/>
<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
</bean>
configLocation:MyBatis 原生配置 (緩存、設定等)typeAliasesPackage:自動掃描別名,省去 XML <typeAlias>mapperLocations:批量掃描映射文件,替代手動 <mapper><!-- 單模板掃描 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!-- 多模板區分 (SIMPLE vs BATCH) -->
<bean id="sqlSessionTemplateSimple" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
<constructor-arg value="SIMPLE"/>
</bean>
<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg ref="sqlSessionFactory"/>
<constructor-arg value="BATCH"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao"/>
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplateSimple"/>
<property name="markerInterface" value="com.example.dao.SimpleDao"/>
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao"/>
<property name="sqlSessionTemplateBeanName" value="sqlSessionTemplateBatch"/>
<property name="annotationClass" value="com.example.dao.BatchAnnotation"/>
</bean>
關鍵屬性:
markerInterface:實現該接口的 DAO 才被掃描 (接口標記)annotationClass:標註該註解的 DAO 被掃描 (註解標記)@Configuration
@MapperScan(basePackages = "com.example.dao",
sqlSessionTemplateRef = "sqlSessionTemplate")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(new PathMatchingResourcePatternResolver()
.getResources("classpath*:mapper/**/*.xml"));
bean.setTypeAliasesPackage("com.example.dto");
return bean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory factory) {
return new SqlSessionTemplate(factory, ExecutorType.SIMPLE);
}
@Bean
public DataSourceTransactionManager txManager(DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
// Emp.java
public class Emp {
private String id, name, sex, phone;
private int age;
// getter/setter
}
// EmpMapper.java
public interface EmpMapper {
void insertEmp(Emp emp);
List getAllEmp();
Emp getById(String id);
void deleteEmp(String id);
void updateEmp(Map map);
}
// EmpMapper.xml
<mapper namespace="com.example.mapper.EmpMapper">
<resultMap id="empMap" type="Emp">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="sex" column="sex"/>
<result property="age" column="age"/>
<result property="phone" column="phone"/>
</resultMap>
<insert id="insertEmp" parameterType="Emp">
INSERT INTO emp(id,name,sex,age,phone) VALUES(#{id},#{name},#{sex},#{age},#{phone})
</insert>
<select id="getAllEmp" resultMap="empMap">SELECT * FROM emp</select>
<select id="getById" parameterType="String" resultMap="empMap">
SELECT * FROM emp WHERE id=#{id}
</select>
<delete id="deleteEmp" parameterType="String">DELETE FROM emp WHERE id=#{id}</delete>
<update id="updateEmp" parameterType="Map">
UPDATE emp SET name=#{name},sex=#{sex},age=#{age},phone=#{phone} WHERE id=#{id}
</update>
</mapper>
@Service
public class EmpServiceImpl implements EmpService {
@Autowired EmpMapper empMapper; // 直接注入 Mapper 接口
@Override
@Transactional(rollbackFor = Exception.class)
public void insertEmp(Emp emp) {
empMapper.insertEmp(emp);
// 模擬異常驗證回滾
if ("rollback".equals(emp.getName())) throw new RuntimeException("測試回滾");
}
}
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
<tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="find*" propagation="SUPPORTS" read-only="true"/>
<tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="servicePc" expression="execution(* com.example.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="servicePc"/>
</aop:config>
| 方式 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
@Transactional 註解 | 現代項目、Spring Boot | 簡潔、類/方法級靈活控制 | 需開啟代理、自我調用失效 |
AOP XML (tx:advice) | 傳統 XML 配置項目 | 集中管理、無侵入代碼 | 配置冗長、重構不便 |
| TransactionTemplate 編程式 | 動態事務邏輯、非 Spring 管理 Bean | 精確控制邊界、無代理限制 | 代碼侵入、樣板代碼多 |
txManager 與 sqlSessionFactory 必須引用同一個 dataSource,否則事務不生效。@Transactional 方法不生效 (繞過代理),解決:注入自身 @Autowired SelfService self 或拆分類。catch (Exception e) 未重拋導致事務提交,必須 throw e 或標註 rollbackFor。MapperScannerConfigurer 重複掃描:多實例需互斥 markerInterface/annotationClass,否則 Bean 衝突。ExecutorType.BATCH 需手動 sqlSession.commit() 或配置 Spring 事務管理,否則數據不入庫。MyBatis-Spring 整合核心在於:SqlSessionFactoryBean 解析配置、MapperFactoryBean 生成 Mapper 代理、MapperScannerConfigurer 批量註冊、SqlSessionTemplate 適配 Spring 事務。掌握「單模板/多模板掃描」、「註解/XML 事務三選一」、「數據源一致性」三大核心點,即可構建穩健、可維護的持久層架構。新項目強烈建議採用 Spring Boot + @MapperScan + @Transactional 註解驅動模式。