关键点:1.在service层使用、2.注解@Transactional注解来自org.springframework.transaction.annotation包,而不是javax.transaction、.3.事物注解上指定回滚的异常类型

1. 引入依赖

    <!--依赖管理 -->
    <dependencies>
        <dependency> <!--添加Web依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency> <!--添加Mybatis依赖 -->
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency><!--添加MySQL驱动依赖 -->
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency><!--添加Test依赖 -->
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2. 添加配置

主要是配置数据源和开启Mybatis的自动驼峰映射
@SpringBootApplication
public class MybatisTransactionApplication {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{
    <span class="hljs-comment">//1.初始化</span>
    SpringApplication application=  <span class="hljs-keyword">new</span> SpringApplication(MybatisTransactionApplication.class);

    <span class="hljs-comment">//2.添加数据源</span>
    Map&lt;String,Object&gt; <span class="hljs-built_in">map</span> = <span class="hljs-keyword">new</span> HashMap&lt;&gt;();
    <span class="hljs-built_in">map</span>.put(<span class="hljs-string">"spring.datasource.url"</span>,<span class="hljs-string">"jdbc:mysql://localhost:3306/socks?useSSL=false"</span>);
    <span class="hljs-built_in">map</span>.put(<span class="hljs-string">"spring.datasource.username"</span>,<span class="hljs-string">"root"</span>);
    <span class="hljs-built_in">map</span>.put(<span class="hljs-string">"spring.datasource.password"</span>,<span class="hljs-string">"root"</span>);

    <span class="hljs-comment">//3.开启驼峰映射 (Such as account_id ==&gt; accountId)</span>
    <span class="hljs-built_in">map</span>.put(<span class="hljs-string">"mybatis.configuration.map-underscore-to-camel-case"</span>,<span class="hljs-literal">true</span>);
    application.setDefaultProperties(<span class="hljs-built_in">map</span>);

    <span class="hljs-comment">//4.启动应用</span>
    application.run(args);
}

}

3. 添加数据库记录

打开 Navicat 的查询窗口,然后执行以下SQL:
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');

执行完毕后,可以查询到账户数据,如图:

4. 编写代码

以操作账户金额为例,模拟正常操作金额提交事务,以及发生异常回滚事务。 其中控制层代码如下:
package com.hehe.controller;

@RestController public class AccountController {

<span class="hljs-meta">@SuppressWarnings</span>(<span class="hljs-string">"all"</span>)
<span class="hljs-meta">@Autowired</span>
AccountService accountService;

<span class="hljs-meta">@GetMapping</span>(<span class="hljs-string">"/"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> Account <span class="hljs-title">getAccount</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-comment">//查询账户</span>
    <span class="hljs-keyword">return</span> accountService.getAccount();
}

<span class="hljs-meta">@GetMapping</span>(<span class="hljs-string">"/add"</span>)
<span class="hljs-function"><span class="hljs-keyword">public</span> Object <span class="hljs-title">addMoney</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">try</span> {
        accountService.addMoney();
    } <span class="hljs-keyword">catch</span> (Exception e) {
        <span class="hljs-keyword">return</span> <span class="hljs-string">"发生异常了:"</span> + accountService.getAccount();
    }
    <span class="hljs-keyword">return</span> getAccount();
}

}

在业务层使用 @Transactional 开启事务,执行数据库操作后抛出异常。具体代码如下:

package com.hehe.service;

@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("发生异常了..");
    }
}

数据库层就很简单了,我们通过注解来实现账户数据的查询,具体如下:

package com.hehe.mapper;

@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 实体对象如下:

package com.hehe.pojo;

public class Account {

    private String accountId;
    private String accountName;
    private BigDecimal balance;

    // Override toString Method ..
    // Getter & Setters  ..
}

5. 测试事务

启动应用,访问 http://localhost:8080 ,可以看到账户数据,如下:
然后访问 http://localhost:8080/add ,可以看到账户余额并没有增加,如下: 也就是说事务开启成功,数据得到回滚。

6. 常见坑点

使用事务注解@Transactional 之前,应该先了解它的相关属性,避免在实际项目中踩中各种各样的坑点。

常见坑点1:遇到非检测异常时,事务不开启,也无法回滚。

例如下面这段代码,账户余额依旧增加成功,并没有因为后面遇到检测异常而回滚!!
   @Transactional
    public void addMoney() throws Exception {
        //先增加余额
        accountMapper.addMoney();
        //然后遇到故障
        throw new SQLException("发生异常了..");
    }
原因分析:因为Spring的默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。如果想针对非检测异常进行事务回滚,可以在@Transactional 注解里使用 rollbackFor 属性明确指定异常。例如下面这样,就可以正常回滚:
  @Transactional(rollbackFor = 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();
        }
    }
不要小瞧了这些细节,往前暴露异常很大程度上很能够帮我们快速定位问题,而不是经常在项目上线后出现问题,却无法刨根知道哪里报错。
推荐做法:在业务层统一抛出异常,然后在控制层统一处理。
    @Transactional
    public void addMoney() throws Exception {
        //先增加余额
        accountMapper.addMoney();
        //推荐:在业务层将异常抛出
        throw new RuntimeException("发生异常了..");
    }
作者:yizhiwazi

來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

转载自:https://www.jianshu.com/p/380a9d980ca5