Spring boot学习笔记之五:SpringBoot之事务管理@Transactional
1. 前言
以前学ssh ssm都有事务管理service层,通过applicationContext.xml配置,在service类或所有service方法都加上事务操作;用来保证一致性,即service方法里的多个dao操作,要么同时成功,要么同时失败;
springboot下的话 搞一个@Transactional即可,无需再进行配置
2.编码
我们这里搞一个实例,转账实例,A用户转账给B用户xx元
Account类
package com.jd.entity; import javax.persistence.*; name="t_account") (public class Account { /** * id 编号 */ private Integer id; /** * 用户名 */ length=50) ( private String userName; /** * 余额 */ private float balance; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public float getBalance() { return balance; } public void setBalance(float balance) { this.balance = balance; } }
启动启动类,会自动创建表t_account,我们插入两条数据(这里我练习还是使用之前 db_book 库,可以自己切换其他库)
insert into t_account values(1,700,'zhangsan'); insert into t_account values(2,300,'lisi');
代码请建立对应的包,可以参考我建立的包
2.新建AccountDao接口
package com.jd.dao; import com.jd.entity.Account; import org.springframework.data.jpa.repository.JpaRepository; /** * Created by ${HeJD} on 2018/6/27. */ public interface AccountDao extends JpaRepository<Account, Integer> { }
3.AccountService接口
package com.jd.service; /** * Created by ${HeJD} on 2018/6/27. */ public interface AccountService { /** * 转账 * @param fromUserId * @param toUserId * @param account */ public void transferAccounts(int fromUserId,int toUserId,float account); }
4.AccountServiceImpl接口实现类
package com.jd.service.serviceImp; import com.jd.dao.AccountDao; import com.jd.entity.Account; import org.springframework.stereotype.Service; import com.jd.service.AccountService; import javax.annotation.Resource; /** * Created by ${HeJD} on 2018/6/27. */ "accountService") (public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void transferAccounts(int fromUserId,int toUserId,float account){ Account fromUserAccount=accountDao.getOne(fromUserId); fromUserAccount.setBalance(fromUserAccount.getBalance()-account); accountDao.save(fromUserAccount); // fromUser扣钱 Account toUserAccount=accountDao.getOne(toUserId); toUserAccount.setBalance(toUserAccount.getBalance()+account); //假设转账的时候假如出现异常,业务类或业务方法中没有使用@Transactional控制事务,则会出现钱转出了,收钱人没有收到的情况 // int zero = 1/0; 这里我们先把这个异常注释掉,稍后我们再打开 accountDao.save(toUserAccount); // toUser加钱 } }
5.AccountController类
package com.jd.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.jd.service.AccountService; import javax.annotation.Resource; /** * Created by ${HeJD} on 2018/6/27. */ "/account") (public class AccountController { private AccountService accountService; "/transfer") ( public String transferAccounts(){ try{ //1号zhsnasan 给2号lisi 转账200元 accountService.transferAccounts(1, 2, 200); return "ok"; }catch(Exception e){ return "no"; } } }
6.我们执行启动类
浏览器输入:http://localhost:8080/account/transfer
运行OK,没有事务控制的时候,没有异常的时候,转出的前与入账的钱是一致的,没有出现丢失的情况。
OK 我们先把数据恢复到700 300
现在我们把service层方法改下,抛出一个异常
import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.transaction.Transactional; "accountService") (public class AccountServiceImpl implements AccountService { private AccountDao accountDao; /** * 转账 * * @param fromUserId * @param toUserId * @param account */ public void transferAccounts(int fromUserId, int toUserId, float account) { Account fromUserAccount=accountDao.getOne(fromUserId); fromUserAccount.setBalance(fromUserAccount.getBalance()-account); accountDao.save(fromUserAccount); // fromUser扣钱 Account toUserAccount=accountDao.getOne(toUserId); toUserAccount.setBalance(toUserAccount.getBalance()+account); //假设转账的时候假如出现异常,业务类或业务方法中没有使用@Transactional控制事务,则会出现钱转出了,收钱人没有收到的情况 int zero = 1/0; //打开这个异常 accountDao.save(toUserAccount); // toUser加钱 } }
这时候 扣钱dao能执行成功 加钱操作执行不了了 因为前面会报错。
我们重启启动类
浏览器输入:http://localhost:8080/account/transfer
运行NO
这时候 钱扣了 但是 没加钱 导致了数据不一致性!!!!
3.事务的使用
这时候 我们需要用上事务
在service类或方法上加上@Transactional即可,无需xml文件的配置,这也是springboot的优点
@Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。
package com.jd.service.serviceImp; import com.jd.dao.AccountDao; import com.jd.entity.Account; import org.springframework.stereotype.Service; import com.jd.service.AccountService; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; /** * Created by ${HeJD} on 2018/6/27. */ //添加事务注解,异常的时候能保证事务的一致性 "accountService") (public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void transferAccounts(int fromUserId,int toUserId,float account){ Account fromUserAccount=accountDao.getOne(fromUserId); fromUserAccount.setBalance(fromUserAccount.getBalance()-account); accountDao.save(fromUserAccount); // fromUser扣钱 Account toUserAccount=accountDao.getOne(toUserId); toUserAccount.setBalance(toUserAccount.getBalance()+account); //假设转账的时候假如出现异常,业务类或业务方法中没有使用@Transactional控制事务,则会出现钱转出了,收钱人没有收到的情况 int zero = 1/0; //这个异常照样是打开 accountDao.save(toUserAccount); // toUser加钱 } }
我们恢复下数据700 300
然后再重启启动类,
浏览器输入:http://localhost:8080/account/transfer
运行NO
但是数据库数据没变化 说明启动作用了。