关于代码中事务的常用写法
在平常代码中,要保证功能的有效性和数据安全性,通常会对有关联的业务代码进行事务控制,常见的事务控制方法如下
1 事务控制的引入
在日常操作中,对于一些特定场景,需要保持数据安全和有效性,通过添加事务的控制,避免脏数据的产生,从而避免影响用户体验,以及整体项目的数据逻辑错误.
以常见的电商购物场景为例.
用户点击下单购买商品,其中涉及到几个关联操作.即用户余额扣减,商品数量减少,订单增加.上述三个操作彼此间是独立进行的,但是三个操作,要么都成功,即用户购买商品成功; 或者都失败,即用户购买商品失败(失败原因赞不说明,但是三张数据表中都没有新增数据)
所以,对于上述应用场景,需要使用事务来控制,整个业务的完成.保证数据的正确,从而减少脏数据以及系统bug的产生.
2 事务控制案例
1 环境准备
准备一个简单的SpringBoot项目环境.
1 application.yml文件
spring:
datasource:
name: test #数据库名
url: jdbc:mysql://localhost:3306/test #url
username: root #用户名
password: root #密码
2 pom.xml文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
</dependency>
3 启动类
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
4 Controller控制器
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/saveData")
public String saveData() throws Exception {
userService.saveEntity();
return "新增成功";
}
@GetMapping("/saveData2")
public String saveData2() throws Exception {
userService.saveEntity2();
return "新增成功";
}
@GetMapping("/saveData3")
public String saveData3() throws Exception {
userService.saveEntity3();
return "新增成功";
}
@GetMapping("/saveData4")
public String saveData4() throws Exception {
userService.saveEntity4();
return "新增成功";
}
}
5 Service层
接口
public interface UserService {
void saveEntity() throws Exception;
void saveEntity2() throws Exception;
void saveEntity3() throws Exception;
void saveEntity4() throws Exception;
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Autowired
private UserInfoDao userInfoDao;
@Override
public void saveEntity() throws Exception {
userDao.saveUser();
userInfoDao.saveUserInfo();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveEntity2() throws Exception {
userDao.saveUser();
userInfoDao.saveUserInfo();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void saveEntity3() throws Exception {
//设置回滚点
Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
try {
userDao.saveUser();
userInfoDao.saveUserInfo();
} catch (Exception e) {
System.out.println("异常了=====" + e);
//手工回滚异常
TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
}
}
@Autowired
DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
TransactionDefinition transactionDefinition;
@Override
public void saveEntity4() throws Exception {
// 手动开启事务
TransactionStatus transactionStatus = dataSourceTransactionManager
.getTransaction(transactionDefinition);
try {
userDao.saveUser();
userInfoDao.saveUserInfo();
} catch (Exception e) {
e.printStackTrace();
// 手动回滚事务
dataSourceTransactionManager.rollback(transactionStatus);
}
// 手动提交事物
dataSourceTransactionManager.commit(transactionStatus);//提交
}
}
6 Dao层
接口
public interface UserDao {
void saveUser();
}
public interface UserInfoDao {
void saveUserInfo();
}
实现类
@Repository
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveUser() {
String sql = "insert into user(id,name, age) values(?,?,?)";
List<Object> sqlParamList = new ArrayList<Object>();
sqlParamList.add("No-"+ new Random().nextInt(10));
sqlParamList.add("杜甫"+new Random().nextInt(10));
sqlParamList.add(20);
jdbcTemplate.update(sql, sqlParamList.toArray(new Object[sqlParamList.size()]));
}
}
@Repository
public class UserInfoDaoImpl implements UserInfoDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void saveUserInfo() {
// 手动添加错误 isnert 打错成insertr
String sql = "insertr into userinfo(id,name, age) values(?,?,?)";
List<Object> sqlParamList = new ArrayList<Object>();
sqlParamList.add("No-" + new Random().nextInt(10));
sqlParamList.add("李白" + new Random().nextInt(10));
sqlParamList.add(20);
jdbcTemplate.update(sql, sqlParamList.toArray(new Object[sqlParamList.size()]));
}
}
7 数据库脚本
CREATE TABLE `user` (
`id` varchar(20) NOT NULL,
`name` varchar(200) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `userinfo` (
`id` varchar(20) NOT NULL,
`name` varchar(200) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
2 结果分析
启动项目
访问:
1 不加事务控制
user表中插入数据,userinfo表中没有插入数据,查看程序,因sql语法报错
java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insertr into userinfo(id,name, age) values('No-6','李白2',20)' at line 1
2 添加事务注释
user表中没有插入数据,userinfo表中没有插入数据,查看程序,因sql语法报错
java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insertr into userinfo(id,name, age) values('No-3','李白6',20)' at line 1
3 手动添加事务
user表中没有插入数据,userinfo表中没有插入数据,没有抛出异常查看程序,因sql语法报错
异常了=====org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [insert into user(id,name, age) values(?,?,?)]; Duplicate entry 'No-9' for key 'PRIMARY'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'No-9' for key 'PRIMARY'
4 手动添加事务2
user表中没有插入数据,userinfo表中没有插入数据,查看程序,因sql语法报错
org.springframework.dao.DuplicateKeyException: PreparedStatementCallback; SQL [insert into user(id,name, age) values(?,?,?)]; Duplicate entry 'No-9' for key 'PRIMARY'; nested exception is java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'No-9' for key 'PRIMARY'