org.springframework.boot
spring-boot-starter-web
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
mysql
mysql-connector-java
runtime
org.springframework.boot
spring-boot-starter-test
test
why:
事务的配置,就是在mybatis的基础上加上两个注解。
1、需要的注解为@EnableTransactionManagement(加在启动类上的,springboot默认支持,可以不写) 和@Transactional 两个,它们来自于下边这个包:
spring-tx.jar
该包其实在前边配置mybatis引入依赖时,已自动引入,就是下边这个:
事务常见的问题:
3.Spring对事务的控制
Spring框架针对事务提供了很多事务管理解决方案。我们这里只说常用的:声明式事务。声明式事务通过传播行为、隔离级别、只读提示、事务超时及回滚规则来进行定义。我们这里讲用Spring提供的注解式事务方法:@Transaction
。
使用注解式事务的优点:开发团队达到一致的约定,明确标注事务方法的编程风格。
使用事务控制需要注意:
注意
Spring默认只对运行期异常(RuntimeException)进行事务回滚操作,对于编译异常Spring是不进行回滚的,所以对于需要进行事务控制的方法尽量将可能抛出的异常都转换成运行期异常,将编译时异常转为运行期异常。
这也是我们我什么要在Service接口中手动封装一些RuntimeException信息的一个重要原因。
/**
* 转换为运行时异常父类
*
*/
public class MyException extends RuntimeException {
public MyException(String message) {
super(message);
}
public MyException(String message, Throwable cause) {
super(message, cause);
}
}
public class RepeatAddException extends MyException{
public RepeatAddException(String message) {
super(message);
}
public RepeatAddException(String message,Throwable cause) {
super(message,cause);
}
}
主要是配置数据源还有驼峰映射
Spring:
datasource:
url: jdbc:mysql://localhost:3306/gamemanage?useUnicode=true&characterEncoding=UTF-8&useSSL=true
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
mybatis:
configuration:
map-underscore-to-camel-case: true
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`account_id` varchar(30) ,
`account_name` varchar(30),
`balance` decimal(20,2),
PRIMARY KEY (`account_id`)
);
insert into account values ('1','admin','1000.25');
以操作账户金额为例,模拟正常操作金额提交事务,以及发生异常回滚事务。
其中控制层代码如下:
@RestController
public class AccountController {
@SuppressWarnings("all")
@Autowired
AccountService accountService;
@GetMapping("/")
public Account getAccount() {
//查询账户
return accountService.getAccount();
}
@GetMapping("/add")
public Object addMoney() {
try {
accountService.addMoney();
} catch (Exception e) {
return "发生异常了:" + accountService.getAccount();
}
return getAccount();
}
}
在业务层在方法前使用 @Transactional 开启事务,执行数据库操作后抛出异常。增、删、改等方法才会需要事务。
@Service
public class AccountService {
@SuppressWarnings("all")
@Autowired
AccountMapper accountMapper;
public Account getAccount() {
return accountMapper.getAccount();
}
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//然后遇到故障
throw new RuntimeException("发生异常了..");
}
}
数据库层就很简单了,注解方式
@Mapper
public interface AccountMapper {
@Select("select * from account where account_id=1")
Account getAccount();
@Update("update account set balance = balance+100 where account_id=1")
void addMoney();
}
Account 实体对象
public class Account {
private String accountId;
private String accountName;
private BigDecimal balance;
// Override toString Method ..
// Getter & Setters ..
}
可以看到账户余额并没有增加,如下: 也就是说事务开启成功,数据得到回滚。
坑点1:遇到非检测异常时,事务不开启,也无法回滚。
例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到检测异常而回滚!!
//改成SQLException,不会回滚,金额照常增加
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("发生异常了..");
}
原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对非检测异常进行事务回滚,可以在@Transactional 注解里使用
rollbackFor 属性明确指定异常。例如下面这样,就可以正常回滚:
@Transactional(rollbackFor = SQLException.class)//写Exception.class也可以
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//然后遇到故障
throw new SQLException("发生异常了..");
}
常见坑点2: 在业务层捕捉异常后,发现事务不生效。
这是许多新手都会犯的一个错误,在业务层手工捕捉并处理了异常,你都把异常“吃”掉了,Spring自然不知道这里有错,更不会主动去回滚数据。例如:下面这段代码直接导致增加余额的事务回滚没有生效。
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//谨慎:尽量不要在业务层捕捉异常并处理
try {
throw new SQLException("发生异常了..");
} catch (Exception e) {
e.printStackTrace();
}
}
实际上,使用IDEA编辑器可以避免这些错误,会提醒
@Transactional
public void addMoney() throws Exception {
//先增加余额
accountMapper.addMoney();
//推荐:在业务层将异常抛出
throw new RuntimeException("发生异常了..");
}
用法 public 方法
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常,方法上注解属性会覆盖类注解上的相同属性
默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。
@Transactional(rollbackFor = Exception.class)
public boolean insert(String arr1, String arr2) {
try {
String id = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 11);
Ques ques = JSONObject.parseObject(arr2, Ques.class);
ques.setId(id);
if (iQuesDao.insert(ques) > 0) {
List quesOptionList = JSONObject.parseArray(arr1, QuesOption.class);
for (QuesOption quesOption : quesOptionList) {
quesOption.setQuescontentid(id);
}
if (iQuesOptionDao.insertList(quesOptionList) > 0) {
return true;
}
}
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return false;
}
参考:http://www.cnblogs.com/linkstar/p/7372329.html
https://blog.csdn.net/wkl305268748/article/details/77619367