学习目标
第六章中已经学习了MyBatis框架和Spring框架,还讲到Spring框架是一个超级黏合平台,能够以高度的开放整合其他优秀的框架。(如果没有了解可以去我主页看看 第一至六章 的内容来学习)本章将带同学们学习使用Spring框架整合MyBatis框架的方法。
7.1基本整合方式
Spring框架整合其他框架的本质其实就是把其他框架交给Spring框架管理。Spring框架通过IOC、AOP等机制实现与其他框架的连接,最终建立一个低耦合的应用架构,这大大增强了系统的灵活性,便于功能扩展。在本章中,我们将利用Spring框架对MyBatis框架进行整合,在对组件实现解耦的同时,还能使MyBatis框架的使用变得更加方便和简单。
7.1.1 整合思路梳理
MyBatis框架主要是通过SqlSession实例实现对数据的操作,而SqlSession实例是通过SqlSessionFactory创建的,SqlSessionFactory实例又是由SqlSessionFactoryBuilder依据MyBatis配置文件中的数据源、SQL映射文件等信息构建的,这些对象之间的关系。
7.1.2 整合所需的依赖及配置
在Java项目中整合数据库事务管理通常涉及使用Spring框架的@Transactional注解以及配置数据源和事务管理器。这里,我将提供一个基本的示例,展示如何在Spring Boot项目中整合JPA(Java Persistence API)和Hibernate作为JPA的实现,并配置事务管理。
- 添加依赖
首先,你需要在你的pom.xml(如果你使用的是Maven)中添加Spring Boot的起步依赖以及JPA和数据库的依赖。以下是一个示例,它包含了Spring Boot的Web、JPA和H2数据库的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- 其他依赖 -->
</dependencies>
- 配置数据源和JPA
在Spring Boot中,你可以通过application.properties或application.yml文件来配置数据源和JPA。以下是一个application.properties的示例:
# 数据源配置
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
# JPA配置
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
这里配置了内存中的H2数据库,并设置了Hibernate的DDL生成策略为update(在启动时自动更新数据库结构),以及显示SQL语句。
- 实体类和仓库
你需要定义实体类和对应的JPA仓库。这里是一个简单的实体类示例:
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 构造器、getter和setter省略
}
仓库接口:
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// 你可以在这里定义查询方法,但JpaRepository已经提供了很多基本方法
}
- 服务层和事务管理
在服务层中,你可以使用@Transactional注解来声明事务的边界。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void addUser(User user) {
userRepository.save(user);
// 这里可以添加更多的数据库操作,它们都将在同一个事务中执行
}
}
- 控制器
最后,你可以创建一个控制器来触发服务层的方法。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/users")
public String addUser(@RequestBody User user) {
userService.addUser(user);
return "User added successfully";
}
}
这个简单的例子展示了如何在Spring Boot项目中整合JPA和事务管理。Spring Boot会自动配置数据源、JPA和事务管理器,你只需要关注你的业务逻辑即可。
7.1.3 使用Spring配置文件配置数据源
在Spring中,使用配置文件(如XML配置文件)来配置数据源是一种常见的做法,特别是在不使用Spring Boot的项目中。以下是一个简单的示例,展示了如何在Spring的XML配置文件中配置一个数据源(例如,使用HikariCP作为连接池),并配置JPA或Hibernate的SessionFactory(如果你使用的是Hibernate作为JPA实现)。
-
添加依赖
首先,确保你的项目中包含了数据库驱动、HikariCP连接池以及Hibernate(如果你使用Hibernate)的依赖。如果你使用Maven,可以在pom.xml中添加这些依赖。但请注意,下面的示例主要关注于Spring配置部分。 -
Spring XML配置文件
下面是一个Spring XML配置文件的示例,它配置了数据源和Hibernate的SessionFactory。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 数据源配置 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/yourdb"/>
<property name="username" value="youruser"/>
<property name="password" value="yourpassword"/>
<!-- 其他HikariCP配置 -->
<property name="maximumPoolSize" value="10"/>
<!-- 更多属性... -->
</bean>
<!-- JPA或Hibernate SessionFactory配置(示例使用Hibernate) -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.example.model"/>
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
<!-- 其他Hibernate配置 -->
</props>
</property>
</bean>
<!-- 事务管理器配置 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 开启注解事务支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- 其他bean定义... -->
</beans>
注意
- 请确保将数据库连接信息(如URL、用户名和密码)替换为你自己的信息。
- 如果你使用的是Spring Boot,通常不需要这样的XML配置,因为Spring Boot会自动配置很多内容,包括数据源和JPA/Hibernate。但是,在一些复杂的项目中,或者当你不想使用Spring Boot的自动配置时,使用XML配置文件可能是一个选择。
- 如果你使用的是Spring Boot,但仍然需要XML配置(比如,为了集成一些老旧的库),你可以将XML配置文件放在src/main/resources目录下,并通过@ImportResource注解或spring.config.import属性在Spring Boot的配置中引入它。
- 示例中的hibernate.dialect和hibernate.hbm2ddl.auto等属性应该根据你的数据库和Hibernate版本进行调整。
- 如果你的项目中同时使用了Spring Data JPA,你通常不需要直接配置SessionFactory,因为Spring Data JPA会为你处理这些。但是,上面的示例仍然展示了如何在不使用Spring Data JPA的情况下进行配置。
7.1.4 通过Spring配置文件创建SqlSessionFactory
在MyBatis框架中,配置完数据源之后,就可以使用SqlSessionFactoryBuilder创建SqlSessionFactory实例了。而MyBatis-Spring整合包中提供了SqlSessionFactoryBean类来实现同样的功能。
在Spring中,如果你正在使用MyBatis而不是Hibernate,并且想要通过Spring配置文件来创建SqlSessionFactory,你可以按照以下步骤进行配置。这里将展示一个基于XML的Spring配置文件示例,该配置文件会配置数据源并创建SqlSessionFactory。
首先,确保你的项目中包含了MyBatis和数据库驱动的依赖。如果你使用Maven,可以在pom.xml中添加相应的依赖。
然后,你可以创建一个Spring XML配置文件(例如applicationContext.xml),并在其中配置数据源和SqlSessionFactory。以下是一个示例配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 数据源配置 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource"
init-method="init" destroy-method="close">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/yourdb"/>
<property name="username" value="youruser"/>
<property name="password" value="yourpassword"/>
<!-- 其他HikariCP配置 -->
<property name="maximumPoolSize" value="10"/>
<!-- 更多属性... -->
</bean>
<!-- MyBatis SqlSessionFactory 配置 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 配置MyBatis的全局配置文件位置 -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!-- 扫描mapper接口 -->
<property name="mapperLocations" value="classpath*:mapper/**/*.xml"/>
</bean>
<!-- 其他bean定义... -->
<!-- 如果你需要扫描Mapper接口来创建Mapper的代理对象,可以添加以下配置 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.mapper"/>
<!-- 你可以配置其他MapperScannerConfigurer的属性,如sqlSessionFactoryRef等 -->
</bean>
</beans>
在这个配置文件中:
- dataSource bean配置了数据源,这里使用的是HikariCP作为连接池。
- sqlSessionFactory bean配置了MyBatis的SqlSessionFactory,它引用了上面定义的数据源,并指定了MyBatis的全局配置文件(mybatis-config.xml)的位置以及Mapper XML文件的扫描路径。
- MapperScannerConfigurer bean用于扫描指定包下的Mapper接口,并自动创建它们的代理对象,以便在Spring容器中注入和使用。
请注意,mybatis-config.xml是MyBatis的全局配置文件,你可以在其中配置一些MyBatis的通用设置,如别名、类型处理器、插件等。但是,在Spring和MyBatis集成的项目中,很多配置(如数据源和事务管理器)都已经在Spring配置文件中完成了,因此mybatis-config.xml可能只包含少量的配置。
最后,确保你的Mapper XML文件和Mapper接口都放在正确的位置,并且Mapper接口的包名与MapperScannerConfigurer中配置的basePackage相匹配。这样,Spring就会在启动时自动扫描这些Mapper接口,并创建它们的代理对象。
7.1.5 通过SqlSessionTemplate操作数据库
在Spring与MyBatis集成时,SqlSessionTemplate是MyBatis-Spring提供的一个核心类,它实现了SqlSession接口,并管理了MyBatis的SqlSession的生命周期。通过使用SqlSessionTemplate,你可以更方便地在Spring管理的bean中执行MyBatis的mapper操作。
以下是一个简单的示例,展示了如何在Spring应用中通过SqlSessionTemplate来操作数据库。但是,请注意,在实际应用中,你通常会直接注入Mapper接口,而不是直接使用SqlSessionTemplate,因为Mapper接口提供了更高级别的抽象和类型安全。然而,了解SqlSessionTemplate的用法仍然是有益的。
首先,确保你的Spring配置文件(如applicationContext.xml)已经正确配置了SqlSessionFactory和SqlSessionTemplate。然后,你可以在你的服务层(Service Layer)或DAO层(Data Access Object Layer)中使用SqlSessionTemplate。
示例
假设你有一个名为UserMapper的Mapper接口,它有一个方法selectUserById用于根据用户ID查询用户信息。
UserMapper.java
public interface UserMapper {
User selectUserById(Integer id);
}
UserMapper.xml(MyBatis的Mapper XML文件)
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserById" resultType="com.example.model.User">
SELECT * FROM users WHERE id = #{id}
</select>
</mapper>
尽管你通常会直接注入UserMapper接口,但以下是如何使用SqlSessionTemplate的示例:
UserService.java(使用SqlSessionTemplate)
import org.apache.ibatis.session.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
public User getUserById(Integer id) {
// 使用SqlSessionTemplate执行Mapper的SQL语句
// 注意:这里直接写SQL字符串和参数类型通常不是最佳实践,仅用于演示
// 在实际项目中,你应该使用Mapper接口
// 但为了演示SqlSessionTemplate的用法,我们手动构造SQL查询
String sql = "SELECT * FROM users WHERE id = #{id}";
// 注意:这里的MapperType和parameterType通常是自动处理的,这里仅为了说明如何手动调用
return sqlSessionTemplate.selectOne(sql, id);
// 如果使用Mapper接口,则应该这样做:
// UserMapper userMapper = sqlSessionTemplate.getMapper(UserMapper.class);
// return userMapper.selectUserById(id);
}
}
注意
在上面的UserService类中,我展示了如何使用SqlSessionTemplate直接执行SQL语句,但这并不是推荐的做法。通常,你会通过SqlSessionTemplate.getMapper(Class<T>
type)方法获取Mapper接口的代理对象,然后调用Mapper接口中的方法。这样做的好处是你可以利用MyBatis提供的强类型支持和更好的代码可读性。
因此,在实际应用中,你应该采用注释中提到的第二种方式,即先通过SqlSessionTemplate.getMapper(UserMapper.class)获取UserMapper的代理对象,然后调用selectUserById方法。
7.1.6 使用SqlSessionDaoSupport简化编码
除直接使用SqlSessionTemplate获取SqlSession实例处理数据的方式处,MyBatis-Spring还提供了SqlSessionDaoSupport类来简化SqlSessionTemplate的配置和获取方式。
在MyBatis与Spring集成时,SqlSessionDaoSupport是一个抽象支持类,它提供了对SqlSession的便捷访问,使得在DAO层(数据访问对象层)中编写代码更加简化。SqlSessionDaoSupport类通过模板方法模式封装了SqlSession的获取和关闭逻辑,从而允许你专注于编写数据库操作逻辑。
要使用SqlSessionDaoSupport简化编码,你需要做以下几步:
创建一个DAO类继承自SqlSessionDaoSupport: 这样你的DAO类就可以方便地访问SqlSession了。
在你的DAO类中注入SqlSessionFactory: 虽然SqlSessionDaoSupport类已经为你处理了SqlSession的获取和关闭,但它需要知道如何创建SqlSession。这通常是通过注入SqlSessionFactory来实现的。
编写你的数据库操作方法: 在DAO类中,你可以通过调用getSqlSession()方法来获取SqlSession,但更常见的是使用this.getMapper(YourMapperInterface.class)来获取Mapper接口的代理对象,然后直接调用Mapper接口中的方法。
下面是一个使用SqlSessionDaoSupport的DAO类示例:
UserDao.java
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import com.example.mapper.UserMapper;
import com.example.model.User;
public class UserDao extends SqlSessionDaoSupport {
// 由于我们继承了SqlSessionDaoSupport,因此可以直接使用getMapper方法来获取Mapper接口
public User selectUserById(Integer id) {
// 获取UserMapper的代理对象
UserMapper userMapper = this.getMapper(UserMapper.class);
// 调用Mapper接口中的方法
return userMapper.selectUserById(id);
}
// 其他数据库操作方法...
}
注意
- 在Spring配置文件中,你需要将UserDao配置为一个bean,并注入SqlSessionFactory。但是,如果你已经通过组件扫描(Component Scanning)和@Repository注解来管理DAO层,Spring会自动为你注入SqlSessionFactory。
- 示例中的UserMapper是一个MyBatis的Mapper接口,它定义了数据库操作方法。你需要确保MyBatis的配置(如Mapper XML文件或注解)与UserMapper接口中的方法相匹配。
- 使用SqlSessionDaoSupport可以简化SqlSession的管理,但它并不是必须的。从Spring 3.1开始,推荐的做法是直接注入Mapper接口,因为这样可以获得更好的类型安全和更简洁的代码。不过,在某些特定情况下,SqlSessionDaoSupport仍然有其用武之地。
Spring配置文件示例(如果你不使用组件扫描):
<bean id="userDao" class="com.example.dao.UserDao">
<!-- Spring会自动将SqlSessionFactory注入到SqlSessionDaoSupport中 -->
</bean>
但是,如果你使用Java配置或组件扫描,你通常不需要显式地在Spring配置文件中声明UserDao bean,因为Spring会自动为你做这件事。只需确保UserDao类上有@Repository注解,并且Spring的组件扫描覆盖了包含UserDao的包。
7.2 映射器整合方式
在7.1节中主要使用SqlSessionTemplate类实现DAO层的相关操作。但是,这种方式不仅需要编写Mapper接口的实现类,还需要使用字符串定义方法的位置,这样不仅代码繁多,还容易出错、不易维护,如果命名空间发生变化,改起来会更麻烦。
7.2.1 使用MapperFactoryBean注入映射器
在Spring与MyBatis集成时,MapperFactoryBean是MyBatis-Spring提供的一个类,用于创建Mapper接口的代理对象,并将其注入到Spring容器中。这样,你就可以在Spring管理的bean中直接注入Mapper接口,而无需显式地调用SqlSession来获取Mapper。
使用MapperFactoryBean来注入Mapper接口通常不是最直接或推荐的方式,因为Spring提供了更简单的集成方式,比如通过@Mapper注解和组件扫描自动发现Mapper接口。但是,了解MapperFactoryBean的用法仍然是有益的,尤其是在处理一些特殊配置时。
以下是一个使用MapperFactoryBean来注入Mapper接口的示例:
首先,确保你的Mapper接口和MyBatis的映射文件(XML或注解)已经正确配置。
UserMapper.java
package com.example.mapper;
import com.example.model.User;
public interface UserMapper {
User selectUserById(Integer id);
// 其他方法...
}
MyBatis配置文件(如果你使用的是XML映射文件)
确保你的MyBatis配置文件(如mybatis-config.xml)或Spring配置文件已经包含了Mapper XML文件的路径。
Spring配置文件(使用MapperFactoryBean)
<beans ...>
<!-- 配置SqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 其他配置,如mapperLocations等 -->
</bean>
<!-- 使用MapperFactoryBean注入UserMapper -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="com.example.mapper.UserMapper"/>
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
<!-- 其他bean配置... -->
</beans>
在这个配置中,MapperFactoryBean的mapperInterface属性被设置为UserMapper接口的全路径名,而sqlSessionFactory属性则引用了之前配置的SqlSessionFactory bean。
然而,如上所述,这种方式并不是最推荐的做法。在现代Spring应用中,你通常会使用@Mapper注解和组件扫描来自动发现和注册Mapper接口。这样做更加简洁和方便。
使用@Mapper注解和组件扫描
首先,在你的Mapper接口上添加@Mapper注解:
package com.example.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.example.model.User;
@Mapper
public interface UserMapper {
User selectUserById(Integer id);
// 其他方法...
}
然后,在Spring配置中启用组件扫描,以便Spring能够自动发现带有@Mapper注解的接口:
<beans ...>
<!-- 启用组件扫描,包括Mapper接口所在的包 -->
<context:component-scan base-package="com.example.mapper"/>
<!-- 配置SqlSessionFactory等其他bean... -->
</beans>
或者,如果你使用的是Java配置,可以使用@MapperScan注解来指定Mapper接口所在的包:
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {
// 配置SqlSessionFactory等其他bean...
}
这样,Spring就会自动为UserMapper接口创建一个代理对象,并将其注册为Spring容器中的一个bean,你可以像注入其他Spring管理的bean一样注入它。
7.2.2 使用MapperScannerConfigurer注入映射器
在Spring配置中,MapperScannerConfigurer 是一个Bean配置器,它用于扫描包路径下的接口,并动态地将它们注册为MyBatis的Mapper接口,而无需为每个Mapper接口手动编写定义。这使得MyBatis与Spring的集成更加简洁和方便。
以下是如何使用MapperScannerConfigurer来注入Mapper接口的Java配置示例(尽管你提到的是Java代码,但通常这种配置是在Spring的配置文件中完成的,不过我们可以通过Java配置类来实现相同的效果):
Java配置类
首先,确保你的Mapper接口上使用了@Mapper注解(尽管在使用MapperScannerConfigurer时这不是必需的,但它仍然是一个好习惯,因为它提供了额外的类型检查和IDE支持)。
package com.example.mapper;
import org.apache.ibatis.annotations.Mapper;
import com.example.model.User;
@Mapper
public interface UserMapper {
User selectUserById(Integer id);
// 其他Mapper方法...
}
然后,在Spring的Java配置类中配置MapperScannerConfigurer:
package com.example.config;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyBatisConfig {
// 其他Bean配置...
// 配置SqlSessionFactory(这里只是一个示例,具体实现取决于你的数据源和MyBatis配置)
@Bean
public SqlSessionFactory sqlSessionFactory(/* 需要的参数 */) {
// 创建并返回SqlSessionFactory的实例
// ...
return null; // 这里应该是有效的SqlSessionFactory实例
}
// 使用MapperScannerConfigurer扫描Mapper接口
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
// 设置Mapper接口所在的包路径
mapperScannerConfigurer.setBasePackage("com.example.mapper");
// (可选)设置SqlSessionFactoryBeanName,如果你的SqlSessionFactory bean名称不是默认的"sqlSessionFactory"
// mapperScannerConfigurer.setSqlSessionFactoryBeanName("customSqlSessionFactory");
return mapperScannerConfigurer;
}
// 其他Bean配置...
}
在这个Java配置类中,mapperScannerConfigurer()方法创建并配置了一个MapperScannerConfigurer实例。通过调用setBasePackage()方法,我们指定了Mapper接口所在的包路径,这样Spring就会扫描这个包及其子包下的所有接口,并将它们注册为MyBatis的Mapper接口。
注意
虽然示例中的sqlSessionFactory方法返回了null,但在实际应用中,你需要根据你的数据源和MyBatis的配置来创建并返回一个有效的SqlSessionFactory实例。
此外,如果你的SqlSessionFactory bean名称不是默认的sqlSessionFactory,你还需要通过setSqlSessionFactoryBeanName()方法来指定你的SqlSessionFactory bean名称。但是,在大多数情况下,使用默认的bean名称就足够了。
最后,请确保你的Spring配置类(或配置文件)被Spring容器加载,这样MapperScannerConfigurer才能正常工作。如果你使用的是基于Java的配置,那么通常意味着你的配置类上会有@Configuration注解,并且Spring容器会以某种方式(如通过AnnotationConfigApplicationContext)被初始化并加载这个配置类。
7.3 声明式事务
在企业管理系统开发中,事务处理是非常重要的一环,以往我们通过在业务方法中硬编码的方式进行事务控制,这样做的弊端显而易见:事务相关代码分散在业务方法中难以重用;复杂事务的编码难度较高,增加了开发难度等。
7.3.1 配置声明式事务
在Spring框架中,配置声明式事务主要依赖于@Transactional注解以及事务管理器(TransactionManager)的配置。下面我将提供一个基于Java配置的示例,展示如何设置声明式事务。
首先,确保你的项目中已经包含了Spring和Spring的JDBC或ORM(如Hibernate)相关的依赖。这里假设你使用的是Spring Boot,因为它大大简化了配置过程。不过,我也会提供一个不使用Spring Boot的Java配置示例。
使用Spring Boot
在Spring Boot中,你通常不需要显式配置事务管理器,因为Spring Boot的自动配置功能会为你做这件事。你只需要在需要事务管理的方法或类上添加@Transactional注解即可。
但是,如果你需要自定义事务管理器,你可以通过创建一个配置类来实现:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
在这个例子中,DataSource是自动配置的,所以你只需要将其注入到DataSourceTransactionManager的构造函数中即可。
不使用Spring Boot的Java配置
如果你不使用Spring Boot,你需要显式地配置数据源、事务管理器以及可能的事务属性源(虽然这不是必需的,但@Transactional注解的属性可以通过编程方式定制)。
首先,配置数据源:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
@Configuration
public class DataSourceConfig {
@Bean
public DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/yourdb");
dataSource.setUsername("user");
dataSource.setPassword("password");
return dataSource;
}
}
然后,配置事务管理器:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
@Configuration
public class TransactionManagerConfig {
@Autowired
private DataSource dataSource;
@Bean
public DataSourceTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
在这个配置中,DataSource bean被注入到TransactionManagerConfig中,并用于创建DataSourceTransactionManager实例。
最后,确保你的Spring配置类(或多个配置类)被Spring容器扫描到,这样所有的bean和配置都会被正确加载。
使用@Transactional
现在,你可以在服务层的方法或类上使用@Transactional注解来声明事务边界了。例如:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 数据库操作...
}
}
在上面的例子中,createUser方法被@Transactional注解标记,这意味着在执行这个方法时,Spring会启动一个新的事务(如果当前没有事务的话),并在方法结束时提交事务(如果方法正常结束的话),或者在方法抛出未检查异常时回滚事务。
7.3.2 使用注解实现声明式事务
在Spring框架中,使用注解实现声明式事务是一种非常流行且方便的方法。这主要依赖于@Transactional注解,该注解可以应用于类级别或方法级别,以声明性地指定事务的边界和属性。
以下是一个使用注解实现声明式事务的Java代码示例。在这个例子中,我们将创建一个简单的服务层类,并在其方法上使用@Transactional注解。
首先,确保你的Spring项目中包含了与事务管理相关的依赖。如果你使用的是Maven,你的pom.xml文件中应该包含类似下面的依赖(以Spring Boot为例,因为它会自动配置许多事务相关的bean):
<!-- Spring Boot Starter Data JPA,包含Hibernate和Spring Data JPA的依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 数据库连接池,如HikariCP -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- 数据库驱动,例如MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
然后,在你的服务层类中,你可以这样使用@Transactional注解:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository; // 假设你有一个UserRepository接口,它继承自JpaRepository或CrudRepository
// 使用@Transactional注解标记方法,以声明式地管理事务
@Transactional
public void createUser(User user) {
// 这里执行数据库操作,如保存用户
userRepository.save(user);
// 如果这里抛出运行时异常,则事务会自动回滚
// 例如:throw new RuntimeException("Something went wrong!");
}
// 你也可以将@Transactional注解放在类级别,这样类中的所有公共方法都将被事务管理
// 但请注意,这通常不是最佳实践,因为它可能导致不期望的事务行为
// @Transactional
// public class UserService {
// ...
// }
}
在上面的代码中,UserService类中的createUser方法被@Transactional注解标记,这意味着当这个方法被调用时,Spring会启动一个新的事务(如果当前没有事务的话),并在方法执行完成后提交事务。如果在方法执行过程中抛出了运行时异常(RuntimeException及其子类),则事务会自动回滚。
请注意,默认情况下,@Transactional注解仅对运行时异常(RuntimeException及其子类)和错误(Error及其子类)进行回滚。如果你希望对其他类型的异常也进行回滚,你可以通过@Transactional注解的rollbackFor属性来指定。
此外,Spring Boot的自动配置功能会为你配置一个合适的事务管理器(基于你的数据源配置),所以你通常不需要显式地配置事务管理器bean,除非你有特殊的需求。
最后,请确保你的Spring Boot应用已经启用了事务管理支持。在Spring Boot中,这通常是通过在@SpringBootApplication注解或配置类上添加@EnableTransactionManagement注解来实现的,但请注意,从Spring Boot 2.0开始,@EnableTransactionManagement注解不再是必需的,因为Spring Boot会自动为你配置它。
脏读
脏读(Dirty Read)在Spring框架中,尤其是在与数据库事务处理相关的上下文中,指的是一个事务读取了另一个事务未提交的数据,从而造成数据不一致或错误的情况。这是数据库并发事务处理时可能遇到的一个问题,与Spring框架本身没有直接关系,但Spring提供了事务管理的能力,可以帮助开发者在应用程序中控制事务的隔离级别,从而避免或减轻脏读等问题。
脏读的定义与示例
脏读具体指的是:
当一个事务(事务A)正在访问数据,并且对数据进行了修改,但这种修改还没有提交到数据库中时,
另一个事务(事务B)也访问这个数据,并使用了这个尚未提交的数据。
由于事务A的修改尚未提交,因此事务B读取到的数据是“脏”的,即这些数据可能最终不会被事务A提交到数据库中。如果事务B基于这个脏数据进行了进一步的操作,那么这些操作可能是基于错误的数据进行的,从而导致数据不一致或错误。
脏读的示例
假设有一个银行转账的场景,用户A向用户B转账100元。在这个过程中:
事务A(用户A的转账操作)开始,更新了用户A的账户余额,减少了100元,但尚未提交这个修改。
事务B(可能是用户B查询账户余额的操作)开始,读取了用户B的账户余额。由于事务A的修改尚未提交,事务B可能读取到了用户B账户余额增加前的旧值,或者更糟糕的是,如果数据库允许,事务B可能读取到了事务A已经修改但尚未提交的新值(即用户B的账户余额错误地增加了100元)。
如果事务A由于某种原因回滚(例如,因为用户A的账户余额不足),那么事务B读取到的数据就是“脏”的,因为它基于了一个最终不会被提交到数据库中的修改。
Spring中的事务隔离级别
Spring框架通过集成JDBC等数据库访问技术,提供了对事务管理的支持。在Spring中,可以通过设置事务的隔离级别来避免或减轻脏读等问题。事务的隔离级别定义了事务之间的隔离程度,从低到高依次为:
- TRANSACTION_NONE:JDBC驱动不支持事务。
- TRANSACTION_READ_UNCOMMITTED:允许脏读、不可重复读和幻读。
- TRANSACTION_READ_COMMITTED:禁止脏读,但允许不可重复读和幻读。
- TRANSACTION_REPEATABLE_READ:禁止脏读和不可重复读,但允许幻读。
- TRANSACTION_SERIALIZABLE:禁止脏读、不可重复读和幻读。
在实际应用中,通常会根据业务需求和数据库的性能考虑,选择合适的隔离级别来平衡数据一致性和系统性能。
不可重复读
在Spring框架中,不可重复读(Nonrepeatable Read)是数据库事务处理时可能遇到的一个问题,它特指一个事务内多次读取同一数据集合时,由于其他事务的介入(如更新、删除操作),导致第一次读取和后续读取的数据不一致的现象。这种现象违反了事务的隔离性要求,使得事务在处理过程中无法获得稳定的数据视图。
具体来说,不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据,导致第一个事务在两次读取之间看到的数据发生了变化。
在Spring中,事务管理是通过集成JDBC或JPA等持久化技术来实现的,而事务的隔离级别则是由数据库系统本身支持的。Spring提供了设置事务隔离级别的能力,以便开发者可以根据业务需求选择合适的隔离级别来避免或减轻不可重复读等问题。
Spring支持的事务隔离级别通常包括以下几种(从低到高):
读未提交(READ UNCOMMITTED): 这是最低的隔离级别,它允许事务读取未被其他事务提交的变更。这种隔离级别会导致脏读、不可重复读和幻读问题。
读已提交(READ COMMITTED): 在这个隔离级别下,一个事务只能读取到其他事务已经提交的数据。这可以避免脏读问题,但不可重复读和幻读仍然可能发生。
可重复读(REPEATABLE READ): 这是MySQL数据库的默认隔离级别。它确保在同一个事务中多次读取同样记录的结果是一致的,即在该事务开始执行后,其他事务的更新和删除操作对当前事务是不可见的。这可以防止脏读和不可重复读,但幻读仍然可能发生。
串行化(SERIALIZABLE): 这是最高的隔离级别,它通过强制事务串行执行来避免脏读、不可重复读和幻读问题。但这种隔离级别会严重影响数据库的并发性能。
在Spring中,可以通过在方法或类上使用@Transactional注解并设置isolation属性来指定事务的隔离级别。例如,使用@Transactional(isolation = Isolation.REPEATABLE_READ)可以指定事务的隔离级别为可重复读,从而避免不可重复读问题(尽管仍然需要注意幻读的可能性)。
需要注意的是,不同数据库系统对事务隔离级别的支持和实现可能有所不同。因此,在实际应用中,需要根据所使用的数据库系统来选择合适的隔离级别,并测试以确保满足业务需求。
幻读
幻读(Phantom Read)在Spring框架中,特别是在与数据库事务处理相关的上下文中,是一个重要的并发问题。幻读是指在一个事务内,当多次执行同一查询时,由于其他并发事务的插入操作,导致查询结果集不一致的现象。即,在第一个事务执行过程中,第二个事务插入了满足第一个事务查询条件的新数据行,当第一个事务再次查询时,会“看到”这些新插入的行,就好象发生了幻觉一样。
幻读的定义
幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行,一般特指事务执行中新增的其他行。幻读违反了事务的隔离性要求,使得事务在多次读取同一数据集时无法获得一致的结果。
幻读的原因
幻读的发生主要是因为数据库中的行锁(Record Lock)只能锁定已存在的数据行,而无法阻止其他事务向表中插入新的数据行。因此,当其他事务在第一个事务的两次查询之间插入了新行,并且这些新行满足第一个事务的查询条件时,就会导致幻读现象。
Spring中的幻读处理
在Spring框架中,处理幻读通常依赖于数据库事务的隔离级别设置。Spring通过集成JDBC或JPA等持久化技术,允许开发者在事务注解(如@Transactional)中指定事务的隔离级别。
READ UNCOMMITTED(读未提交): 此隔离级别下,事务可以读取到其他事务未提交的数据,因此无法避免幻读。
READ COMMITTED(读已提交): 此隔离级别下,事务只能读取到其他事务已经提交的数据,虽然可以避免脏读,但无法避免不可重复读和幻读。
REPEATABLE READ(可重复读): MySQL数据库的默认隔离级别。在此级别下,通过使用多版本并发控制(MVCC)和间隙锁(Gap Lock),InnoDB存储引擎可以避免不可重复读,但对于幻读的处理则依赖于具体的实现。在某些情况下,可能需要额外的措施来防止幻读。
SERIALIZABLE(串行化): 最高的隔离级别,它通过强制事务串行执行来避免脏读、不可重复读和幻读。但此隔离级别会严重影响数据库的并发性能。
解决幻读的方法
使用更高的隔离级别: 将事务的隔离级别设置为SERIALIZABLE可以彻底解决幻读问题,但会牺牲数据库的并发性能。
使用范围锁(Range Lock): 在查询时锁定一个范围的数据,防止其他事务在这个范围内插入新数据。但这种方法可能会增加锁的粒度,影响性能。
使用MVCC和间隙锁(Gap Lock): 如MySQL的InnoDB存储引擎,通过MVCC和间隙锁的结合使用,可以在一定程度上避免幻读。
总结
幻读是数据库事务处理中的一个重要并发问题,它违反了事务的隔离性要求。在Spring框架中,处理幻读通常依赖于数据库事务的隔离级别设置以及数据库本身的并发控制机制。开发者需要根据实际业务需求和数据一致性要求来选择合适的隔离级别和并发控制策略。