收藏这三篇笔记,完整回顾Spring常见问题及使用方式速查:
- Spring 学习笔记①:IoC容器、Bean与注入
- Spring 学习笔记②:动态代理及面向切面编程
- Spring 学习笔记③:JDBC与事务管理(即本篇)
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>5.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.2.6.RELEASEversion>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.20version>
dependency>
数据源的基本类即为 org.springframework.jdbc.dataSource.DriverManagerDataSource
,主要功能是获取数据库连接,还可以引入缓冲池、分布式事务等,需要进行以下配置:
<bean id="dataSource" class="org.springframework.jdbc.dataSource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<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());
}
假设数据库有表 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, '[email protected]'),
(2, 'Louis', 20, '[email protected]'),
(3, 'Tom', 24, '[email protected]'),
(4, 'Sandy', 12, '[email protected]'),
(5, 'Lily', 85, '[email protected]');
以及对应的Model:
@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
private String email;
}
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);
DataSourceTransactionManager
。由于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>
Spring-tx
(即Spring Transaction)中的三个核心接口为:
PlatformTransactionManager
:用于管理事务。TransactionDefinition
:用于定义事务的的相关信息。TransactionStatus
:描述事务的状态。TransactionStatus getTransaction(TransactionDefinition definition)
:用于获取事务状态信息。void commit(TransactionStatus status)
:用于提交事务。void rollback(TransactionStatus status)
:用于回滚事务。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 |
void flush()
:刷新事务。boolean hasSavepoint()
:获取是否存在保存点。boolean isCompleted()
:获取事务是否完成。boolean isNewTransaction()
:获取是否是新事务。boolean isRollbackOnly()
:获取是否回滚。void setRollbackOnly()
:设置事务回滚。构建一个转账的数据库事务场景,则数据库表如下:
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)));
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception"/>
tx:attributes>
tx:advice>
<aop:config>
<aop:pointcut expression="execution(* MVC.Service.AccountServiceImpl.transfer(..))" id="txPointCut" />
<aop:advisor pointcut-ref="txPointCut" advice-ref="txAdvice" />
aop:config>
属性 | 类型 | 默认值 | 说明 |
---|---|---|---|
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
:保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题。在配置文件中开启事务注解的驱动及注册事务管理器:
<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、文件上传…