Springboot中的数据库事务
Springboot中的数据库事务
对于⼀些业务⽹站⽽⾔,产品库存的扣减、交易记录以及账户都必须是要么同时成功,要么同时失败,这便是⼀种事务机制,⽽在⼀些特殊的场景下,如⼀个批处理,它将处理多个交易,但是在⼀些交易中发⽣了异常,这个时候则不能将所有的交易都回滚。如果所有的交易都回渎,那么那些本能够正常处理的业务也⽆端地被回滚。通过 Spring 的数据库事务传播⾏为,可以很⽅便地处理这样的场景。
⾸先配置数据库信息
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.sql.jdbc.Driver
at.max-idle=10
at.max-active=50
at.max-wait=10000
at.initial-size=5
⼀、JDBC数据库事务
package com.demo.servicee.impl
@Service
public class JdbcServiceImpl implements JdbcService{
@Autowired
private DataSource dataSource=null;
@Override
public int insertUser(String name,String note){
Connection conn=null;
int result=0;
try{
//获取连接
Connection();
//开启事务
conn.setAutoCommit(false);
//设置隔离级别
conn.setTransactionIsolation(TransactionIsolationLevel.Level());
//执⾏SQL
PreparedStatement ps=conn.prepareStatement("insert into t_user(user_name,note)values(?,?)");
ps.setString(1,userName);
ps,setString(2,note);
uteUpdate();
//提交事务
connmit();
}catch(Exception e){
//回滚事务
if(conn !=null){
try{
}catch(SqlException e1)
e1.printStackTrace();
}
}
e.printStackTrace();
}finally{
try{
if(conn !=null && !conn.isClosed()){
conn.close()
}
}catch(SQLException e){
e.printStackTrace();
}
}
return result;
}
}
使⽤JDBC需要使⽤⼤量的语句,和关于连接的获取关闭,事务的提交和回滚。使⽤Hibernate、myBatis可以减少finally的使⽤,但是依旧不能减少开闭数据库连接和事务控制的代码。⽽AOP可以解决这样的问题。
⼆、Spring 声明式事务的使⽤
Spring AOP 会把我们的代码织⼊到约定的流程中,同样,同样执⾏的SQL的代码也可以织⼊的哦Spring 约定的数据库事务的流程中。⾸先
要掌握这个约定
1.Spring 声明式数据库事务约定
对于事务需要通过标注告诉Spring在什么地⽅启⽤数据库事务功能,对于声明式数据库,是使⽤@Transactional进⾏标注的。
@Transactional 这个注解可以标注类和⽅法上,当它标注在类上时,代表这个类所有公共(public)⾮静态的⽅法东将启⽤事务功能。在@Transactonal 中还可以进⾏事务的隔离级别和传播⾏为,异常类型的配置。这些配置,是在Sprng IoC容器在加载时就会将这些配置信息解析出来,然后帮这些喜喜存储到事务定义器(TransactonDefinition接⼝实现的类)⾥,并且记录了那些类或者⽅法需要启动事务的功能,采取什么策略去执⾏事务。在这个过程中我们所需要做的就是给需要事务的类和⽅法标注
@Transactional并配置属性
spring的事务处理机制
Spring 通过对注解@Transactional 属性配置去设置数据库事务,跟着 Spring 就会
开始调⽤开发者编写的业务代码。执⾏开发者的业务代码,可能发⽣异常,也可能不发⽣异常。在Spring 数据库事务的流程中,它会根据是否发⽣异常采取不同的策略如果都没有发⽣异常, Spring 数据库就会帮助我们提交事务,这点也并不需要我们⼲预。如果发⽣异常,就要判断⼀次事务定
义器内的配置,如果事务定义器⼰经约定了该类型的异常不回段事务就提交事务,如果没有任何配置或者不是配置不回滚事务的异常,则会回滚事务,并且将异常抛出,这步也是由事务完成的。论发⽣异常与否, Spring 都会释放事务资源,这样就可以保证数据库连接池正常可⽤了,这
也是由 Spring 事务完成的内容。
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao=null;
@Override
@Transactional
public int insertUser(User user){
return userDao.insertUser(user);
}
}
2.@Transactional配置项
spring中关于数据库属性是由@Transactional 来配置的,源码如下所⽰
package ansaction.annotation;
/**** imports****/
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetenttionPolicy.RUNTIME)
@InHerited
@Documented
public @interface Transactional{
//通过bean name指定事务管理器
@ALiasFor("transactionManager")
String value() default"";
//同value属性
@ALiasFor(value)
String transactionManager() default"";
//指定传播⾏为
Propagation propagation() default Propagetion.REQUIRED;
//指定隔离级别
Isolation isolation() default IsoLation.DEFAULT
//指定超时时间(单位为秒)
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
/
/时候只读事务
boolean readOnly() default false;
//⽅法发⽣指定异常时回滚,默认是所有的异常都回滚
Class<? extends Throwable>[] rollbackFor() default();
//⽅法发⽣指定异常名称时回滚,默认是所有异常都回滚
String[] rollbackForClassName() default();
//⽅法发⽣指定异常时不回滚,默认是所有的异常都回滚
Class<? extends Throwable>[] noRollBackFor() default();
//⽅法在发⽣指定异常名称时不回滚,默认所有异常都回滚
String noRollbackForClassName() default();
}
value和transactionManager属性是配置⼀个Spring的事务管理器
timeout是事务允许存在的时间戳
read Only 属性定义的是事务是否是只读事务;
rollbackFor 、 rollbackForClassName 、 noRollbackFor 和 noRollbackForClassName 都是指定异常,我们从流程中可以
看到在带有事务的⽅法时,可能发⽣异常,通过这些属性的设置可以指定在什么异常的情况下依旧提交事务,在什么异常
的情况下回滚事务,这些可以根据⾃⼰的需要进⾏指定
propagation 传播⾏为(重点)
isolation隔离级别(重点)
@Transactional可以放在接⼝上也可以放在实现类上,spring推荐放在实现类上。
3.Spring 事务管理器
在spring的事务流程中事务的打开回滚和提交是由事务管理器来完成的。事务管理器的顶层接⼝是PlatformTransactionManager.
当我们使⽤myBatis框架时最常⽤的的是DataSourceTransactionManager它实现了PlatformTransactionManager接⼝,关于PlatformTransactionManager的源码如下
package org.springframework transaction;
public interface PlatformTransactionManager{
//获取事务,它还会设置数据属性
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
//提交事务
void commit(TransactionStatus status) throws TransactionException;
//回滚事务
void rollback(TransactionStatus status) throws TransactionException;
}
Spring在事务管理时,就是将这些⽅法按照约定织⼊对应的流程,其中getTransaction⽅法的参数是⼀个事务定义器,它依赖于我们配置的@Transactional的配置项⽣成的。于是通过它就能够设置事务的属性了
在 Spring Boot 中,当你依赖于 mybatis-spring-boot-starter 之后,它会⾃动创建⼀个 DataSourceTransactionManager 对象,作为事务管理器,如果依赖于 spring-boot-starter-data-j pa ,则它会⾃动创建JpaTransactionManager 对象作为事务管理器,所以我们⼀般不需要⾃⼰创建事务管理器⽽直接使⽤它们即可。
4.事务的使⽤
创建⼀张表
create table t_user(
id int(12) auto_increment,
user_name varchar(60) not null,
note varchar(512),
primary key(id)
);
创建POJO
package com.demo.pojo
@Alias("user")
public class User{
private Long id;
private String userName;
private String note;
/**** setter and getter****/
}
myBatis接⼝
package com.demo.dao
@Repository
public interface UserDao{
User getUser(Long id);
int intsertUser(User user);
}
mapper映射⽂件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "- //mybatis . org//DTD Mapper 3 . 0//EN"
" http : //mybatis . org/dtd/mybat ⼯ s - 3 - mapper . dtd ">
<mapper namespace="com.demo.dao.UserDao">
<select id="getUser" parameterType="long" resultType="user">
select id,user_name user,note from t_user where id= #{id}
</select>
<insert id="insertUser" useGeneratedKays="true" resulType="user">
insert into t_user(user_name,note)value(#{userName},#{note})
</insert>
</mapper>
服务接⼝
package com.demo.service
public interface UserService{
//获取⽤户信息
public User getUser(Long id);
//新增⽤户
public int inserUser(User user);
}
服务接⼝实现类
package com.demo.service.demo
@Service
public class UserServiceImpl implements UserService{
@Autowired
private UserDao userDao=null;
@Override
@Transactional(isolation=Isolation.READ_COMMITTED,timeout=1)
public int insertUser(User user){
return userDao.insertUser(user);
}
@Override
@Transactional(isolation=Isolation.READ_COMMITTED,timeout=1)
public User insertUser(id){
User(id);
}
}
代码中的⽅法上标注了注解@Transactional ,意味着这两个⽅法将启⽤ Spring 数据库事务机制。在事务配置中,采⽤了读写提交的隔离级别
编写Controller
spring ioc注解package com.ller;
@Controller
@RequestMapping("/user")
public class UserController{
@AutoWired
private UserSevice userService=null;
@RequestMapping("/getUser")
@ResposeBody
public user getUser(Long id){
User(id);
}
@RequestMapping("/insertUser")
@ResponseBody
public Mapping<String,Object> insertUser(String userName,String note){
User user=new User();
user.setUserName(userName);
user.setNote(note);
int update = userService.insertUser(user);
Map<String,Object> result =new HaspMap();
result.put("success",update==1);
result.put("user",user);
return result;
}
}
在application.properties中添加配置
mybatis.mapper-locations=classpath:com/demo.mapper/*xml
依赖于 mybatis叩ring-boot”starter 之后, Spring Boot 会⾃动创建事务管理器、 MyBatis 的 SqlSessionFactory 和 SqlSessionTemplate 等
内容。下⾯我们需要配置 SpringBoot 的运⾏⽂件,
package com.demo.main
@MapperScan(
basePackages="com.demo",
annotationClass=Repository.class
)
@SpringBootApplication(scanBasePackage="com.demo")
public class DemoApplication{
public static void main(String[] args)throws Exception{
SpringApplication.run(DemoApplication.class,args);
}
}
先这⾥使⽤了@MapperScan 扫描对应的包,并限定了只有被注解@Repository 标注的接⼝,这样就可以把 MyBati s 对应的接⼝⽂件扫描到 Spring IoC 容器中了
三、隔离级别
Spring事务机制中最重要的两个配置项,隔离级别和传播⾏为。
对于隔离级别,因为互联⽹应⽤时刻⾯对着⾼并发的环境,时刻都是多个线程共
享的数据。对于数据库⽽⾔,就会出现多个事务同时访问同⼀记录的情况,这样引起数据出现不⼀致的情况,便是数据库的丢失更新(Lost Update )问题。应该说,隔离级别是数据库的概念。
1.数据库事务相关知识
数据库的的4个基本特征,ACID
Atomic(原⼦性):事务中包含的操作被看作⼀个整体的业务单元,这个业务单元中的操作要么全部成功、要么全部失败,不
会出现部分失败,部分成功的场景。
Consistency(⼀致性):在事务完成时,必须使所有的数据保持⼀致状态
Isolation(隔离性):由于多个引⽤程序线程访问同⼀数据,这样数据库的数据就会在各个不同的事务中被访问。这样会产⽣
丢失更新。为了压制丢失更新的产⽣,数据库定义了隔离级别的概念,通过它的选择,可以在不同程度上压制丢失更新的
发⽣。
Durability(持久性):事务结束后,所有的数据会固化到⼀个地⽅,如保存到磁盘中。
下⾯深⼊讨论隔离性
在多个事务同时操作数据库的情况下会引发丢失更新的场景,例如,电商有⼀种商品电商有⼀种商品,在疯狂抢购中,会出现多个事务同时访问商品库存的场景,这样就会产⽣丢失更新。⼀般⽽⾔,存在两种类型的丢失更新,让我们了解下它们。下⾯假设⼀种商品的库存数量还有 100 ,每次抢购都只能抢购 l 件商品,那么在抢购中就可能出现如下的场景。
时刻事务1事务2
T1初始库存100初始库存100
T2库存-1,余99......
<库存-1余99
T4提交事务,库存变为99
T5回滚事务,库存100
对于这样⼀个事务回滚⼀个事务提交引发的数据不⼀致的情况,我们称为第⼀类丢失更新。现今⼤部分的数据库都已经克服了第⼀类丢失更新的问题
时刻事务1事务2
T1初始库存100初始库存100
T2库存-1,余99......
<库存-1余99
<提交事务,库存变为99
T5提交事务,库存变为99
对于这样多个事务都提交引发的丢失更新称为第⼆类丢失更新
2.隔离级别详解
我们主要针对第⼆种丢失更新,为了压制丢失更新,数据库标准提出了4类不同的隔离级别,分别为未提交读(read uncommitted)、读写提交(read commited)、可重复读和串⾏化。提出4种不同的隔离级别是出于性能的考虑
未提交读(read uncommitted)会产⽣脏读
未提交读是最低的隔离级别,其含义是允许⼀个事务读取另外⼀个事务没有提交的数据。未提交读是⼀种危险的隔离级别,所以⼀般在我们
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论