环境说明
- JDK 17
- Spring 6.0.6
- MySQL 8.0.32
- Mybatis 3.5.10
环境准备
添加 Spring 、MySQL 和 Mybatis 相关依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!-- Mybatis针对于Spring专门编写的支持框架 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.2</version>
</dependency>
<!-- Spring的JDBC支持框架 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>6.0.6</version>
</dependency>
数据源
JDBC给每个被访问的数据源指定唯一的数据源名(Data Source Name)。在连接中,用数据源名来代表用户名、服务器名、所连接的数据库名等。数据源包括数据来源的一系列相关信息。每一个数据源必须对应一个具体的数据库(一个数据库也可以创建多个数据源。原因是连接时候的说明可能不一样)。
一般比较常用的数据源
实现,都是采用池化技术,就是在一开始就创建好N个连接(同一数据源),这样之后使用就无需再次进行连接,而是直接使用现成的Connection
对象操作数据库。
SqlSessionTemplate类
mybatis-spring依赖提供了SqlSessionTemplate类,它其实就是官方封装的一个工具类,我们可以将其注册为Bean,这样我们随时都可以向IOC容器索要对象,而不用自己再去编写一个工具类了,我们可以直接在配置类中创建。对于这种别人编写的类型,如果要注册为Bean,只能在配置类中完成:
@ComponentScan("com.test.entity")
@Configuration
public class MainConfig {
@Bean
public SqlSessionTemplate SqlSessionTemplate() throws IOException {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsReader("mybatis-config.xml"));
return new SqlSessionTemplate(sqlSessionFactory);
}
}
创建一个实体类并编写mapper映射文件:
@Data
public class Meal {
private String name;
private String calorie;
private String category;
private String major;
}
public interface MealMapper {
@Select("select * from meal")
List<Meal> selectMeals();
}
接着修改一下 mybatis 配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="application.properties"/>
<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>
<!--<mapper class="com.test.mapper.MealMapper"/>-->
<package name="com.test.mapper"/>
</mappers>
</configuration>
application.properties
jdbc.driver = com.mysql.cj.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/test
jdbc.username = root
jdbc.password = root
最后测试
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
SqlSessionTemplate template = context.getBean(SqlSessionTemplate.class);
MealMapper mapper = template.getMapper(MealMapper.class);
List<Meal> mealList = mapper.selectMeals();
mealList.forEach(System.out::println);
}
前述操作依然需要手动获取mapper对象,可以在配置类上添加 @MapperScan 注解,这样,Mybatis就会自动扫描对应包下所有的接口,并直接被注册为对应的Mapper作为Bean管理(注意这里不能直接在mapper接口上添加@Mapper注解注册mapper):
@ComponentScan("com.test.entity")
@MapperScan("com.test.mapper")
@Configuration
public class MainConfig {
...
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
MealMapper mapper = context.getBean(MealMapper.class);
List<Meal> mealList = mapper.selectMeals();
mealList.forEach(System.out::println);
}
注意虽然这里没有直接用到 SqlSessionTemplate 的Bean,但是必须存在SqlSessionTemplate
或是SqlSessionFactoryBean
的Bean,否则会无法初始化(毕竟要数据库的连接信息)。
使用注解实现
如果需要完全取代mybatis配置文件,首先创建一个数据源的实现类,因为这是数据库最基本的信息,然后再给到SqlSessionFactoryBean
实例,相当于直接在一开始通过IOC容器配置了SqlSessionFactory
,只需传入一个DataSource
的实现即可,这里采用池化数据源。
@ComponentScan("com.test.entity")
@MapperScan("com.test.mapper")
@PropertySource("classpath:application.properties")
@Configuration
public class MainConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
return new PooledDataSource(driver, url, username, password);
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}
}
注意:Mybatis-Plus 使用动态代理实现了新的SqlSessionFactoryBean,因此不能使用Mybatis 的方式来整合Mybatis-Plus。
使用HikariCP连接池
HikariCP是由日本程序员开源的一个数据库连接池组件,代码非常轻量,并且速度非常的快。在SpringBoot 3.0中,官方推荐使用HikariCP。
首先导入 HikariCP 依赖:
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.1</version>
</dependency>
然后直接声明一个 HikariCP的数据源:
@Bean
public DataSource dataSource(){
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
直接运行即可。
由于该数据源实际上采用了SLF4J日志门面打印日志,但是现在没有任何的日志实现,因此可以导入一个对应的日志实现(这时使用JUL,注意日志实现和日志门面的版本对应关系):
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
Mybatis 事务管理
Mybatis对于数据库的事务管理,也有着相应的封装。一个事务无非就是创建、提交、回滚、关闭,因此这些操作被Mybatis抽象为一个接口:
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
对于此接口的实现,MyBatis的事务管理分为两种形式:
- 使用JDBC的事务管理机制:即利用对应数据库的驱动生成的
Connection
对象完成对事务的提交、回滚、关闭等,对应的实现类为JdbcTransaction
- 使用MANAGED的事务管理机制:这种机制MyBatis自身不会去实现事务管理,而是让程序的容器(比如Spring)来实现对事务的管理,对应的实现类为
ManagedTransaction
- 如果需要自定义,那么得实现
org.apache.ibatis.transaction.Transaction
接口,然后在type
属性中指定其类名。使用自定义的事务管理器可以根据具体需求来实现一些特定的事务管理行为。
之前使用就是JDBC的事务,相当于直接使用Connection
对象进行事务操作,并没有额外的管理机制。
使用 Spring 事务管理
Spring事务管理分为编程式事务和声明式事务,但是编程式事务过于复杂并且具有高度耦合性,违背了Spring框架的设计初衷,这里只涉及声明式事务,声明式事务是基于AOP实现的。
首先在配置类添加@EnableTransactionManagement
注解开启Spring的事务支持,这里只需要把一个事务要做的所有事情封装到Service层的一个方法中即可,然后需要在配置文件中注册一个新的事务管理器Bean,事务需要执行必须有一个事务管理器:
@EnableTransactionManagement
@ComponentScans({
@ComponentScan("com.test.entity"),
@ComponentScan("com.test.service")
})
@MapperScan("com.test.mapper")
@Configuration
public class MainConfig {
@Bean
public DataSource dataSource(){
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(url);
dataSource.setDriverClassName(driver);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}
@Bean(name = "tx")
public TransactionManager transactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
...
接着编写一个mapper:
public interface MealMapper {
@Insert("insert into meal(name, calorie, category, major) values(#{name}, #{calorie}, #{category}, #{major})")
void insertMeal(Meal meal);
编写service层接口和实现(在方法上添加@Transactional
注解表示此方法执行的是一个事务操作,在调用此方法时,Spring会通过AOP机制为其进行增强,一旦出现异常,事务会自动回滚。):
public interface MealService {
void insertMeal();
@Component
public class MealServiceImpl implements MealService {
@Resource
MealMapper mealMapper;
@Override
@Transactional(transactionManager = "tx") //此注解表示事务,之后执行的所有方法都会在同一个事务中执行
public void insertMeal() {
mealMapper.insertMeal(new Meal("1", "1", "1", "1"));
if(true){ throw new RuntimeException("抛出异常"); }
mealMapper.insertMeal(new Meal("2", "2", "2", "2"));
}
}
最后调用目标方法:
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
MealService mealService = context.getBean(MealService.class);
mealService.insertMeal();
}
@Transactional 的几个关键的属性:
- transactionManager:指定事务管理器
- propagation:事务传播规则,一个事务可以包括N个子事务
- isolation:事务隔离级别
- timeout:事务超时时间
- readOnly:是否为只读事务,不同的数据库会根据只读属性进行优化,比如MySQL一旦声明事务为只读,那么久不允许增删改操作了。
- rollbackFor和noRollbackFor:发生指定异常时回滚或是不回滚,默认发生任何异常都回滚。
Spring 事务传播的七种级别(事务方法 A 调用 事务方法 B):
- REQUIRED(默认):当前存在事务,则加入当前事务 ,如果当前没有事务,则自己新建⼀个事务(方法B的事务)
- SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,就以非事务方法执行
- MANDATORY:当前存在事务,则加入当前事务,如果当前没有事务,则抛出异常
- REQUIRES_NEW:如果当前存在事务,则挂起当前事务。 创建⼀个新事务
- NOT_SUPPORTED:如果当前存在事务,则挂起当前事务。以非事务方式执行
- NEVER:如果当前存在事务,则抛出异常 。不使用事务
- NESTED:如果当前存在事务,则创建一个事务作为嵌套事务
集成 JUnit 测试
由于每次测试都创建一个 ApplicationContext 较为麻烦,因此可以使用Spring 提供的test模块,它会自动集成 JUnit 进行测试。
首先导入依赖:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>6.0.6</version>
</dependency>
然后在测试类上添加 @ExtendWith(指定要使用spring框架测试功能) 和 @ContextConfiguration(指定要加载哪一个配置文件) 注解:
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = MainConfig.class)
public class TestMain {
@Resource
MealService mealService;
@Test
public void test(){
mealService.insertMeal();
}
}
最后注入 service 的 Bean 直接测试即可。