持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
一、什么是事务
把一组业务当成一个业务来做;要么都成功,要么都失败,保证业务操作完整性的一种数据库机
制。比如常常被拿来举例的转账。
下面我们来看一下事务的相关核心内容,因为事务需要实现对数据库的操作,我们先使用spring jdbTemplate来进行测试,下面我们搭建一个spring jdbcTemplate环境。
二、spring jdbcTemplate的搭建
在spring中为了更加方便的操作JDBC,在JDBC的基础之上定义了一个抽象层,此设计的目的是为不同类型的JDBC操作提供模板方法,每个模板方法都能控制整个过程,并允许覆盖过程中的特定任务,通过这种方式,可以尽可能保留灵活性,将数据库存取的工作量降到最低。
2-1、添加相关maven的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jony</groupId>
<artifactId>spring_transaction</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!--spring -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.19.RELEASE</version>
</dependency>
<!--junit 测试包-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--阿里的数据库连接池-->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<!--mysql 包-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--spring aop+transaction-->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.8</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-orm -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.3.16</version>
</dependency>
</dependencies>
</project>
2-2、mysql数据源配置
mysql.username=root
mysql.password=root
mysql.url=jdbc:mysql://localhost:3306/jony
mysql.driverClassName=com.mysql.jdbc.Driver
2-3、使用applicationContext.xml 配置
配置加载数据源文件以及配置数据库连接
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--引入外部属性资源文件-->
<context:property-placeholder location="db.properties"></context:property-placeholder>
<!--配置第三方bean-->
<bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource">
<property name="username" value="${mysql.username}"></property>
<property name="password" value="${mysql.password}"></property>
<property name="url" value="${mysql.url}"></property>
<property name="driverClassName" value="${mysql.driverClassName}"></property>
</bean>
</beans>
2-4、测试数据库连接
package com.jony.test;
import com.alibaba.druid.pool.DruidDataSource;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.sql.SQLException;
public class MyTest {
ClassPathXmlApplicationContext ioc;
@Before
public void init(){
ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
}
@Test
public void test() throws SQLException {
DruidDataSource dataSource = ioc.getBean("dataSource", DruidDataSource.class);
System.out.println(dataSource);
System.out.println(dataSource.getConnection());
}
}
2-5、在配置文件中添加jdbcTemplate
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
2-5-1、测试jdbcTemplate
可以看到已经成功获取jdbcTemplate对象
三、使用jdbcTemplate CURD
3-1基础准备工作
3-1-1、创建MVC三层代码
3-2、将jdbcTemplate 注入到 UserDaoImpl中
3-2、保存
@Override
public void saveUser(User user) {
String sql="insert into user(name,score) values (?,?)";
jdbcTemplate.update(sql,user.getName(),user.getScore());
}
3-2-1、测试
@Test
public void test02(){
User user=new User();
user.setName("张三");
user.setScore(100);
UserService userService=ioc.getBean(UserService.class);
userService.saveUser(user);
}
3-2-2、执行结果
3-3、查询某个值,并以对象返回
这个地方需要注意的是,如果对象字段名称和数据库字段名称一样,我们就可以使用new BeanPropertyRowMapper<>(User.class)来告诉jdbcTemplate 要返回什么类型,否则就只能挨个字段进行接收
//查询对象
@Override
public User getUser(int id) {
String sql="select * from user where id=?";
User user=jdbcTemplate.queryForObject(sql, new RowMapper<User>() {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
User user=new User();
user.setName(rs.getString("name"));
user.setScore(rs.getInt("score"));
return user;
}
},id);
//如果数据库字段名称和对象名称一样,可以使用BeanPropertyRowMapper
// User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class),id);
return user;
}
3-3-1、测试
@Test
public void test03(){
UserService userService=ioc.getBean(UserService.class);
User user=userService.getUser(1);
System.out.println(user);
}
3-3-2、执行结果
3-4、批量保存
//批量保存
public int[] saveBatch(List<User> userlist){
String sql="insert into user (name,score) values(?,?)";
List<Object[]> listObj=new ArrayList<>();
for(User user:userlist){
listObj.add(new Object[]{user.getName(),user.getScore()});
}
int[] result=jdbcTemplate.batchUpdate(sql,listObj);
return result;
}
3-4-1、测试
//批量保存
@Test
public void test04(){
UserService userService=ioc.getBean(UserService.class);
List<User> userList=new ArrayList<>();
userList.add(new User(2,"李四",99));
userList.add(new User(3,"王五",80));
userList.add(new User(4,"赵六",95));
int[] ints = userService.saveBatch(userList);
for(int i:ints){
System.out.println(i);
}
}
3-4-2、执行结果
3-5、查询对象集合
//根据条件批量查询对象
public List<User> getUserList(int score){
String sql="select * from user where score>?";
List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class), score);
return query;
}
3-5-1、测试
//根据条件查询对象集合
@Test
public void getUserList(){
UserService userService=ioc.getBean(UserService.class);
List<User> userList = userService.getUserList(80);
System.out.println(userList);
}
3-5-2、执行结果
3-6、查询函数
//查询最大值
public int getMaxScore(){
String sql="select max(score) from user";
return jdbcTemplate.queryForObject(sql,int.class);
}
3-6-1、测试
//查询最大分值
@Test
public void getMaxScore(){
UserService userService=ioc.getBean(UserService.class);
int maxScore = userService.getMaxScore();
System.out.println(maxScore);
}
3-6-2、执行结果
3-7、删除
//删除对象
public int delUserById(int id){
String sql="delete from user where id=?";
int update = jdbcTemplate.update(sql, id);
return update;
}
3-7-1、测试
//根据ID删除用户
@Test
public void deleUser(){
UserService userService=ioc.getBean(UserService.class);
int num=userService.delUserById(1);
System.out.println(num);
}
3-7-2、执行结果
3-8、更新
//根据ID更新分值
public void updateUserById(int id,int scoreNum){
String sql="update user set score=score+? where id=?";
jdbcTemplate.update(sql,scoreNum,id);
}
3-8-1、测试
@Test
public void updateUser(){
UserService userService=ioc.getBean(UserService.class);
userService.updateUserById(2,10);
}
四、声明事务
ACID 四大特性
1、原子性(Atomicity): 事务中所有操作是不可再分割的原子单元。事务中所有操作要么都执行成功,要么都执行失败。
2、一致性(Consistency): 事务执行后,数据库状态与其他业务规则保持一致。如转账业务,无论事务执行成功与否,参与转账的两个账户余额之和应该保持不变。
3、隔离性(Isolation): 隔离性是指在并发操作中,不同事务之间应该隔离开来,使每个并发中的事务不会互相干扰。
4、持久性(Durability): 一旦事务提交成功,事务中所有的数据操作都必须被持久化保存到数据库中,即使提交事务后,数据库崩溃,在数据库重启时,也必须能保证通过某种机制恢复数据。
在事务控制方面,主要有两大类: 编程式事务:我们需要在代码中添加一些事务相关代码,beginTransaction()、commit()、rollback()等事务相关方法。
声明式事务:在方法的外部添加注解或者直接在配置文件中定义,将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。spring的AOP恰好可以完成此功能:事务管理代码的固定模式作为一种横切关注点,通过AOP方法模块化,进而实现声明式事务。
4-1、声明式事务的简单配置
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
4-1-1、配置文件中添加事务管理器
<!--事务控制-->
<!--配置事务管理器的bean-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
配置了以上事务的管理,我们就可以使用@Transaction注解了。
4-4-2、@Transactional注解应该写在哪:
@Transactional 可以标记在类上面(当前类所有的方法都运用上了事务)
@Transactional 标记在方法则只是当前方法运用事务
也可以类和方法上面同时都存在, 如果类和方法都存在@Transactional会以方法的为准。
如果方法上面没有@Transactional会以类上面的为准
建议:@Transactional写在方法上面,控制粒度更细, 建议@Transactional写在业务逻
辑层上,因为只有业务逻辑层才会有嵌套调用的情况。
4-2、事务配置的属性
isolation:设置事务的隔离级别
propagation:事务的传播行为
noRollbackFor:那些异常事务可以不回滚
noRollbackForClassName:填写的参数是全类名
rollbackFor:哪些异常事务需要回滚
rollbackForClassName:填写的参数是全类名
readOnly:设置事务是否为只读事务
timeout:事务超出指定执行时长后自动终止并回滚,单位是秒
4-2-1、设置隔离级别(isolation)
在并发情况下,对同一个数据(变量、对象)进行读写操作才会产生并发问题。
4-2-1-1、脏读
如上图,最开始score=90,然后事务1,事务2,开始执行,事务2优先执行,将数据库score=100,这时候事务1开始查询得到的score是100,但是后面事务2进行了回滚,score又回归到最开始的90,那这时候事务1还以为score=100,这就是脏读。
4-2-1-1-1、解决方案
@Transactional(isolation = Isolation.READ_COMMITTED)
读已提交:READ_COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
REQUIRES_NEW 内外部方法需要在不同的类里面,内部的类声明REQUIRES_NEW,才可以成功执行,自己会成功提交,不管外部事务是否成功