Spring框架整合MyBatis框架
整合Spring和MyBatis框架的关键在于将两个框架的优势结合起来,以实现高效的数据访问和事务管理
1. 整合思路
整合所需要的依赖以及配置
为了整合Spring和MyBatis,需要在pom.xml
文件中添加相关依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
使用Spring配置文件配置数据源
配置数据源是整合的第一步。使用Spring配置文件applicationContext.xml
:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/cvs_db?autoReconnect=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</bean>
通过Spring配置文件创建SqlSessionFactory
通过SqlSessionFactoryBean
创建SqlSessionFactory
:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations">
<list>
<value>classpath:mapper/*.xml</value>
</list>
</property>
</bean>
通过SqlSessionTemplate操作数据库
使用SqlSessionTemplate
进行数据库操作,它是SqlSession
接口的实现,负责管理数据库操作的会话:
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
使用SqlSessionDaoSupport简化代码
通过继承SqlSessionDaoSupport
简化DAO层代码,可以直接使用getSqlSession()
方法:
public class SupplierDao extends SqlSessionDaoSupport {
public Supplier getSupplierById(int id) {
return getSqlSession().selectOne("com.example.mapper.SupplierMapper.getSupplierById", id);
}
}
2. 映射器整合方式
使用MapperFactoryBean注入映射器
通过MapperFactoryBean
手动注入映射器:
<bean id="supplierMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.example.mapper.SupplierMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
使用MapperScannerConfigurer简化编码
使用MapperScannerConfigurer
自动扫描和注册映射器:
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
</bean>
3. 声明式事务
什么是声明式事务
声明式事务是通过配置或者注解的方式定义事务边界,而不是在代码中显式地控制事务的开始、提交和回滚。这种方式让开发者不需要在业务逻辑中嵌入事务管理代码,使代码更加简洁和清晰
声明式事务的主要作用包括:
-
简化事务管理:通过配置和注解,事务管理逻辑从业务代码中分离,使代码更简洁、易读、易维护
-
增强代码可维护性:事务管理逻辑集中在配置文件或注解中,可以更方便地修改和管理事务相关的配置,而无需更改业务逻辑代码
-
提高代码可读性:业务代码不再混杂事务处理逻辑,开发者可以更专注于业务逻辑,实现更高的代码可读性
-
减少出错风险:声明式事务减少了手动管理事务的复杂性,降低了出错的可能性,比如忘记提交或回滚事务
事务的隔离级别
-
READ UNCOMMITTED:
- 读操作不加任何锁,可以读取未提交的修改
- 会导致脏读、不可重复读和幻读
-
READ COMMITTED:
- 读操作加读锁,读完立即释放;写操作加写锁,直到事务结束
- 防止脏读,但不能防止不可重复读和幻读
-
REPEATABLE READ:
- 读操作加读锁,保持到事务结束;写操作加写锁
- 使用MVCC机制,防止脏读和不可重复读,但不能完全防止幻读
-
SERIALIZABLE:
- 所有操作加写锁,确保事务完全串行化执行
- 防止脏读、不可重复读和幻读,但性能最差
1. 脏读(Dirty Read)
定义:脏读指一个事务读到了另一个未提交事务修改的数据。
出现原因:脏读的出现是因为读写操作没有进行适当的隔离。在READ UNCOMMITTED隔离级别,事务可以读取其他事务未提交的修改。这种情况下,没有任何锁机制来保护数据的一致性
示例:
- 事务A开始并修改一条记录,将其值从100改为200
- 事务B在事务A提交之前读取了该记录,得到了值200
- 如果事务A随后回滚,记录值恢复为100,那么事务B读到了一个不存在的中间状态的值,这就是脏读
2. 不可重复读(Non-Repeatable Read)
定义:不可重复读指在同一个事务中多次读取同一行数据,每次读取结果可能不同
出现原因:不可重复读发生在READ COMMITTED隔离级别。虽然该级别防止了脏读,但没有确保在同一事务中读到的数据是一致的。原因在于读取操作间没有保持一致性版本
示例:
- 事务A读取记录X,值为100
- 事务B在事务A还没有结束前修改了记录X的值为200,并提交了事务
- 事务A再次读取记录X,值变成了200
- 事务A在同一事务中两次读取记录X,得到不同的值,这就是不可重复读
3. 幻读(Phantom Read)
定义:幻读指在同一个事务中,执行相同的查询条件,结果集的行数可能不同
出现原因:幻读发生在REPEATABLE READ隔离级别。该级别虽然防止了不可重复读,但没有防止在范围查询时数据的插入和删除。幻读的原因在于在事务期间对记录集合的插入或删除导致结果集变化
示例:
- 事务A查询所有年龄大于30的用户,结果有10条记录
- 事务B插入一条新记录,年龄为35,并提交事务
- 事务A再次查询年龄大于30的用户,结果有11条记录
- 事务A在同一事务中两次执行相同的查询,结果集的行数不同,这就是幻读
底层机制解析
-
锁机制:
- 读锁(共享锁,S锁):允许多个事务同时读取数据,但不允许修改
- 写锁(排它锁,X锁):允许一个事务修改数据,禁止其他事务读或写该数据
-
多版本并发控制(MVCC):
MVCC:通过维护数据的多个版本,实现数据读取和修改的并发控制。事务开始时会获取一个快照,保证在事务期间读取到的都是快照中的数据,从而实现可重复读
配置声明式事
在Spring中通过<tx:advice>
和<aop:config>
配置声明式事务:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" propagation="SUPPORTS"/>
<tx:method name="add*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethod" expression="execution(* com.example.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethod"/>
</aop:config>
使用注解实现声明式事务
通过注解简化声明式事务的配置:
在Spring配置文件中启用事务注解:
<tx:annotation-driven transaction-manager="transactionManager"/>
在服务层使用@Transactional
注解:
@Transactional
@Service
public class SupplierService {
@Autowired
private SupplierMapper supplierMapper;
public void updateSupplier(Supplier supplier, List<Integer> ids) {
supplierMapper.updateSupplier(supplier, ids);
}
}