文章目录
收藏这三篇笔记,完整回顾Spring常见问题及使用方式速查:
0. 基本概念
- Spring 框架提供的JDBC支持主要由四个包组成,分别是 core(核心包)、object(对象包)、dataSource(数据源包)和 support(支持包)。
<!-- JDBC模板 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- 事务控制 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<!-- mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
1. JDBCTemplate使用示例
1.1 数据源及配置
数据源的基本类即为 org.springframework.jdbc.dataSource.DriverManagerDataSource
,主要功能是获取数据库连接,还可以引入缓冲池、分布式事务等,需要进行以下配置:
<!-- 配置数据源 -->
<bean id="dataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<!-- 数据库驱动-->
<!-- 注意:com.mysql.jdbc.Driver 在`8.0.x`的mysql驱动中已废弃 -->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<!-- 连接数据库的url,需要同步时区-->
<property name= "url" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai" />
<!-- 连接数据库的用户名 -->
<property name="username" value="$root" />
<!-- 连接数据库的密码 -->
<property name="password" value="$password" />
<!-- 连接池的配置 -->
<property name="initialPoolSize" value="3"></property>
<property name="maxPoolSize" value="10"></property>
<property name="maxStatements" value="100"></property>
<property name="acquireIncrement" value="2"></property>
</bean>
核心类是 org.springframework.jdbc.core.JDBCTemplate
,需要载入数据源进行实例化:
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.jdbcTemplate">
<!--默认必须使用数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
也可以使用配置文件类完成以上配置:
@Bean
public DriverManagerDataSource dataSource(){
String url = "jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai";
String username = "root";
String password = "$password";
DriverManagerDataSource d = new DriverManagerDataSource(url, username, password);
d.setDriverClassName("com.mysql.cj.jdbc.Driver");
return d;
}
@Bean
public JdbcTemplate jdbcTemplate(){
return new JdbcTemplate(dataSource());
}
1.2 数据库配置
假设数据库有表 student
:
CREATE TABLE student (
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO student (id, name, age, email) VALUES
(1, 'Jack', 28, 'Jack@outlook.com'),
(2, 'Louis', 20, 'Louis@outlook.com'),
(3, 'Tom', 24, 'Tom@outlook.com'),
(4, 'Sandy', 12, 'Sandy@outlook.com'),
(5, 'Lily', 85, 'Lily@outlook.com');
以及对应的Model:
@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private String email;
}
1.3 查询语句示例
JdbcTemplate jdbcTemplate = context.getBean("jdbcTemplate", JdbcTemplate.class);
jdbcTemplate.query(
"SELECT * FROM student;",
(resultSet, i) -> new Student(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3), resultSet.getString(4))
).forEach(System.out::println);
2. Spring事务控制
- 事务控制一般在Service层,分为“编程式事务”和“声明式事务”;Sring提供的是后者,基于AOP实现。
- Spring基于JDBC的事务管理器为
DataSourceTransactionManager
。
2.1 配置文件
由于Spring的事务控制是基于AOP实现的,因此也需要引入AOP的命名空间。
<beans xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">
</beans>
2.2 事务接口
Spring-tx
(即Spring Transaction)中的三个核心接口为:
PlatformTransactionManager
:用于管理事务。TransactionDefinition
:用于定义事务的的相关信息。TransactionStatus
:描述事务的状态。
2.2.1 事务管理:PlatformTransactionManager
TransactionStatus getTransaction(TransactionDefinition definition)
:用于获取事务状态信息。void commit(TransactionStatus status)
:用于提交事务。void rollback(TransactionStatus status)
:用于回滚事务。
2.2.2 事务信息:TransactionDefinition
String getName()
:获取事务对象名称。int getIsolationLevel()
:获取事务的隔离级别。int getPropagationBehavior()
:获取事务的传播行为。int getTimeout()
:获取事务的超时时间。boolean isReadOnly()
:获取事务是否只读。
其中,事务的传播行为被定义为:
属性名称 | 描述 | 的值 |
---|---|---|
PROPAGATION_REQUIRED | 如果当前有事务环境就加入当前正在执行的事务环境,否则就新建一个事务。【默认】 | REQUIRED |
PROPAGATION_SUPPORTS | 指定当前方法加入当前事务环境,如果当前没有事务,就以非事务方式执行。 | SUPPORTS |
PROPAGATION_MANDATORY | 指定当前方法必须加入当前的事务环境,如果当前没有事务,则抛出异常。 | MANDATORY |
PROPAGATION_REQUIRES_NEW | 将创建新的事务,如果当前方法已经在事务中,则将当前新建的事务挂起等待执行。 | REQUIRES_NEW |
PROPAGATION_NOT_SUPPORTED | 不支持当前事务,以非事务状态执行。如果当前存在事务环境,则将其挂起等待当前方法先执行。 | NOT_SUPPORTED |
PROPAGATION_NEVER | 不支持当前事务,如果当前方法在事务中,则抛出异常。 | NEVER |
PROPAGATION_NESTED | 指定当前方法执行时,如果已经有一个事务存在,则运行在这个嵌套的事务中。如果当前环境没有运行的事务,就新建并与父事务相互独立的新事务,这个事务拥有多个可以回滚的保存点——内部事务回滚不会对外部事务造成影响(只对 DataSourceTransactionManager 事务管理器起效)。 | NESTED |
2.2.3 事务状态:TransactionStatus
void flush()
:刷新事务。boolean hasSavepoint()
:获取是否存在保存点。boolean isCompleted()
:获取事务是否完成。boolean isNewTransaction()
:获取是否是新事务。boolean isRollbackOnly()
:获取是否回滚。void setRollbackOnly()
:设置事务回滚。
2.3 Spring的声明式事务管理
构建一个转账的数据库事务场景,则数据库表如下:
CREATE TABLE account (
id INT (11) PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(20) NOT NULL COMMENT '姓名',
money INT DEFAULT NULL COMMENT '账户余额'
);
/* 初始数据 */
INSERT INTO account VALUES (1, '张三', 2000);
INSERT INTO account VALUES (2, '李四', 1000);
对应的Model类:
@Data
@AllArgsConstructor
public class Account {
private Integer id;
private String username;
private Integer money;
}
出具持久层:
@Repository(value = "accountDao")
public class AccountDaoImpl implements AccountDaoInterface{
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void send(int money, Account sbPaid) {
val sql = "UPDATE account SET money=money-? WHERE username=?;";
this.jdbcTemplate.update(sql, money, sbPaid.getUsername());
}
@Override
public void receive(int money, Account sbReceived) {
val sql = "UPDATE account SET money=money+? WHERE username=?;";
this.jdbcTemplate.update(sql, money, sbReceived.getUsername());
}
@Override
public Account query(Account account){
val sql = "SELECT * FROM account WHERE username=?";
return this.jdbcTemplate.query(sql,
(res, index) -> new Account(res.getInt(1),
res.getString(2),
res.getInt(3)
),
account.getUsername()).get(0);
}
}
业务逻辑为:
@Service(value = "accountService")
public class AccountServiceImpl implements AccountServiceInterface {
private AccountDaoInterface accountDao;
@Autowired
public void setAccountDao(AccountDaoInterface accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Account from, Account to, int money) throws Throwable {
this.accountDao.send(money, from);
if(money >= 1000) throw new Exception("转账超过限额");
this.accountDao.receive(money, to);
}
@Override
public Account query(Account account){
return this.accountDao.query(account);
}
}
测试类:
AccountServiceInterface accountService = (AccountServiceInterface)context.getBean("accountService");
System.out.println(accountService.query(new Account(1, "张三", -1)));
System.out.println(accountService.query(new Account(2, "李四", -1)));
accountService.transfer(
new Account(1, "张三", -1),
new Account(2, "李四", -1), 500); // 改成1000之后将会中断转账
System.out.println(accountService.query(new Account(1, "张三", -1)));
System.out.println(accountService.query(new Account(2, "李四", -1)));
2.3.1 示例①:基于XML的声明式事务管理
<!-- 编写通知:对事务进行增强(通知),需要编写切入点和具体执行事务的细节 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<!-- 给切入点方法添加事务详情:
name :表示方法名称(*表示任意方法名称)
propagation :用于设置传播行为
isolation :表示隔离级别
rollback-for :需要回滚的异常类 -->
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
<!-- aop编写,让Spring自动对目标生成代理,需要使用AspectJ的表达式 -->
<aop:config>
<!-- 切入点 -->
<aop:pointcut expression="execution(* MVC.Service.AccountServiceImpl.transfer(..))" id="txPointCut" />
<!-- 通知器:将切入点与通知整合 -->
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
</aop:config>
2.3.2 tx:method的属性详解
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
propagation | propagation枚举 | REQUIRED | 事务传播属性(详见2.2.2) |
isolation | isolation枚举 | DEFAULT(所用数据库默认级别) | 事务隔离级别 |
read-only | boolean | false | 是否才用优化的只读事务 |
timeout | int | -1 | 超时(秒) |
rollbackFor | Class[] | {} | 需要回滚的异常类 |
rollbackForClassName | String[] | {} | 需要回滚的异常类名 |
noRollbackFor | Class[] | {} | 不需要回滚的异常类 |
noRollbackForClassName | String[] | {} | 不需要回滚的异常类名 |
- 若
read-only
的值为true
,则为只读事务,即连接点内无INSERT
等写操作。 - 隔离等级共有五级:
DEFAULT
:【默认】采用数据库默认隔离级别。SERIALIZABLE
:最严格的级别,事务串行执行,资源消耗最大。REPEATABLE_READ
:保证了一个事务不会修改已经由另一个事务读取但未提交的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。READ_COMMITTED
:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。READ_UNCOMMITTED
:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。
2.3.3 示例②:基于注解的声明式事务管理
在配置文件中开启事务注解的驱动及注册事务管理器:
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 注册事务管理驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>
修改业务层:
@Service(value = "accountService")
// 添加此注释,参数与 2.3.1 & 2.3.2 小节中一致
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = {Exception.class})
public class AccountServiceImpl implements AccountServiceInterface {
private AccountDaoInterface accountDao;
@Autowired
public void setAccountDao(AccountDaoInterface accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Account from, Account to, int money) throws Throwable {
this.accountDao.send(money, from);
if(money >= 1000) throw new Exception("转账超过限额");
this.accountDao.receive(money, to);
}
@Override
public Account query(Account account){
return this.accountDao.query(account);
}
}
附录 学习笔记①~③代码及工程文件
- (下载后修改为zip文件) —— springtest.pdf
- 想看更多?——SpringBoot、Restful、文件上传…