• JSD-2204-异常处理-SpringJDBC事务管理-Day14


    1.处理异常

    在登录时,可能出现:

    • 用户名错误:BadCredentialsException
    • 密码错误:BadCredentialsException
    • 账号被禁用:DisabledException

    在访问时,可能出现:

    • 无此权限:AccessDeniedException

    以上异常都可以由统一处理异常的机制进行处理,则先在ServiceCode中添加对应的业务状态码:

    1. /**
    2. * 未授权的访问
    3. */
    4. Integer ERR_UNAUTHORIZED = 40100;
    5. /**
    6. * 未授权的访问:账号禁用
    7. */
    8. Integer ERR_UNAUTHORIZED_DISABLED = 40101;
    9. /**
    10. * 禁止访问,通常是已登录,但无权限
    11. */
    12. Integer ERR_FORBIDDEN = 40300;

    然后,在统一处理异常的类中,添加对相关异常的处理:

    1. @ExceptionHandler
    2. public JsonResult handleBadCredentialsException(BadCredentialsException e) {
    3. String message = "登录失败,用户名或密码错误!";
    4. log.debug("处理BadCredentialsException:{}", message);
    5. return JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED, message);
    6. }
    7. @ExceptionHandler
    8. public JsonResult handleDisabledException(DisabledException e) {
    9. String message = "登录失败,此账号已禁用!";
    10. log.debug("处理DisabledException:{}", message);
    11. return JsonResult.fail(ServiceCode.ERR_UNAUTHORIZED_DISABLED, message);
    12. }
    13. @ExceptionHandler
    14. public JsonResult handleAccessDeniedException(AccessDeniedException e) {
    15. String message = "访问失败,当前登录的账号无此权限!";
    16. log.debug("处理AccessDeniedException:{}", message);
    17. return JsonResult.fail(ServiceCode.ERR_FORBIDDEN, message);
    18. }

    另外,在解析JWT的过程中,也可能出现异常,由于解析JWT是在过滤器中进行的,如果出现异常,不会被统一处理异常的机制获取得到(因为过滤器执行的时间点太早),所以,只能在过滤器中自行处理异常,例如:

    1. // 尝试解析JWT
    2. Claims claims = null;
    3. try {
    4. claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
    5. } catch (MalformedJwtException e) {
    6. log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
    7. JsonResult jsonResult = JsonResult.fail(
    8. ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
    9. String jsonResultString = JSON.toJSONString(jsonResult);
    10. PrintWriter writer = response.getWriter();
    11. writer.println(jsonResultString);
    12. writer.close();
    13. return;
    14. } catch (SignatureException e) {
    15. log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
    16. JsonResult jsonResult = JsonResult.fail(
    17. ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
    18. String jsonResultString = JSON.toJSONString(jsonResult);
    19. PrintWriter writer = response.getWriter();
    20. writer.println(jsonResultString);
    21. writer.close();
    22. return;
    23. } catch (ExpiredJwtException e) {
    24. log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
    25. JsonResult jsonResult = JsonResult.fail(
    26. ServiceCode.ERR_JWT_EXPIRED, "登录信息已过期,请重新登录!");
    27. String jsonResultString = JSON.toJSONString(jsonResult);
    28. PrintWriter writer = response.getWriter();
    29. writer.println(jsonResultString);
    30. writer.close();
    31. return;
    32. } catch (Throwable e) {
    33. log.warn("解析JWT失败:{}:{}", e.getClass().getName(), e.getMessage());
    34. JsonResult jsonResult = JsonResult.fail(
    35. ServiceCode.ERR_JWT_PARSE, "无法获取到有效的登录信息,请重新登录!");
    36. String jsonResultString = JSON.toJSONString(jsonResult);
    37. PrintWriter writer = response.getWriter();
    38. writer.println(jsonResultString);
    39. writer.close();
    40. return;
    41. }

    1.1添加管理员时分配角色

    目前,当执行添加管理员时,并没有处理管理员的“角色”,以至于这些新添加的管理员没有权限!所以,添加管理员时,必须为管理员分配角色!

    需要执行的任务:

    • 修改AdminAddNewDTO,增加Long[]属性,用于表示客户端选择的若干种角色的id,使得Controller能接收客户端在“添加管理员”时选择的若干种角色
    • 在Service中,需要调用Mapper实现“向管理员与角色的关联表中批量插入数据”
    • 在Mapper层,需要实现“向管理员与角色的关联表中批量插入数据”

    首先,实现Mapper层,需要执行的SQL语句大致是:

    insert into ams_admin_role (admin_id, role_id) values (?,?), (?,?) ... (?,?);
    

    在根包下创建mapper.AdminRoleMapper接口(如果不存在,则创建,如果已存在,不需要重复创建),并在接口中添加抽象方法:

    int insertBatch(AdminRole[] adminRoleList);
    

    AdminRoleMapper.xml中配置以上抽象方法映射的SQL语句:

    1. <insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
    2. INSERT INTO ams_admin_role
    3. (admin_id, role_id, gmt_create, gmt_modified)
    4. VALUES
    5. <foreach collection="array" item="adminRole" separator=",">
    6. (#{adminRole.adminId}, #{adminRole.roleId},
    7. #{adminRole.gmtCreate}, #{adminRole.gmtModified})
    8. foreach>
    9. insert>

    完成后,在AdminRoleMapperTests中编写并执行测试:

    1. @Test
    2. public void testInsertBatch() {
    3. Long adminId = 5L;
    4. Long[] roleIds = {2L, 3L, 4L};
    5. LocalDateTime now = LocalDateTime.now();
    6. AdminRole[] adminRoleList = new AdminRole[roleIds.length];
    7. for (int i = 0; i < roleIds.length; i++) {
    8. AdminRole adminRole = new AdminRole();
    9. adminRole.setAdminId(adminId);
    10. adminRole.setRoleId(roleIds[i]);
    11. adminRole.setGmtCreate(now);
    12. adminRole.setGmtModified(now);
    13. adminRoleList[i] = adminRole;
    14. }
    15. int rows = mapper.insertBatch(adminRoleList);
    16. log.debug("批量插入管理员与角色的关联数据成功,受影响的行数={}", rows);
    17. }

    测试通过后,在AdminAddNewDTO类中补充private Long[] roleIds;属性:

    1. /**
    2. * 管理员关联到的若干个角色的id
    3. */
    4. @ApiModelProperty("管理员关联到的若干个角色的id")
    5. private Long[] roleIds;

    然后,在AdminServiceImpl中,先自动装配AdminRoleMapper对象:

    1. @Autowired
    2. private AdminRoleMapper adminRoleMapper;

    并在“添加管理员”的业务的最后,补充向管理员与角色关联的表中插入数据:

    1. // 向管理员与角色关联的表中插入数据
    2. log.debug("准备向管理员与角色关联的表中插入数据");
    3. Long adminId = admin.getId();
    4. Long[] roleIds = adminAddNewDTO.getRoleIds();
    5. LocalDateTime now = LocalDateTime.now();
    6. AdminRole[] adminRoleList = new AdminRole[roleIds.length];
    7. for (int i = 0; i < roleIds.length; i++) {
    8. AdminRole adminRole = new AdminRole();
    9. adminRole.setAdminId(adminId);
    10. adminRole.setRoleId(roleIds[i]);
    11. adminRole.setGmtCreate(now);
    12. adminRole.setGmtModified(now);
    13. adminRoleList[i] = adminRole;
    14. }
    15. rows = adminRoleMapper.insertBatch(adminRoleList);
    16. // 判断受影响的行数是否小于1(可能插入多条数据,所以,大于或等于1的值均视为正确)
    17. if (rows < 1) {
    18. // 是:日志,抛出ServiceException
    19. String message = "添加管理员失败,服务器忙,请稍后再次尝试![错误代码:2]";
    20. log.warn(message);
    21. throw new ServiceException(ServiceCode.ERR_INSERT, message);
    22. }

    完成后,修改原有的AdminServiceTests中添加管理员的测试方法,为测试数据补充Long[] roleIds属性的值,再进行测试:

    1. @Test
    2. void testAddNew() {
    3. AdminAddNewDTO adminAddNewDTO = new AdminAddNewDTO();
    4. adminAddNewDTO.setUsername("test-admin-109");
    5. adminAddNewDTO.setPassword("123456");
    6. Long[] roleIds = {2L, 4L}; // 新增代码
    7. adminAddNewDTO.setRoleIds(roleIds); // 新增代码
    8. try {
    9. service.addNew(adminAddNewDTO);
    10. log.debug("添加管理员成功!");
    11. } catch (ServiceException e) {
    12. log.debug(e.getMessage());
    13. }
    14. }

    2.基于Spring JDBC的事务管理

    事务:Transaction,是数据库中可以保障多次写(增删改)操作要么全部成功,要么全部失败的机制。

    在基于Spring JDBC的数据库编程(包括使用Mybatis框架实现数据库编程)中,在处理业务的方法上添加@Transactional注解,即可保证此业务方法是事务性的。

    关于事务的相关概念:

    • 开启事务:Begin
    • 提交事务:Commit
    • 回滚事务:Rollback

    在Spring JDBC的事务管理中,其实现大概是:

    1. 开启事务
    2. try {
    3. 执行业务方法
    4. 提交事务
    5. } catch (RuntimeException e) {
    6. 回滚事务
    7. }

    可以看到,当执行业务方法时,如果出现了RuntimeException(含其子孙类异常),都会回滚事务,这是Spring JDBC事务管理的默认处理方式!

    在使用@Transactional注解时,可以通过配置rollbackFor及相关属性,来指定回滚的异常类型,还可以通过配置noRollbackFor及相关属性,来指定不执行回滚的异常类型。

    关于@Transactional注解,可以添加在:

    • 接口上
    • 接口的实现类上
    • 接口的抽象方法上
    • 实现类的实现方法上

    如果将此注解添加在“接口”或“接口的实现类”上,则表示对应的所有方法都是事务性的!

    如果“接口”或“接口的实现类”上添加了此注解,并配置了注解的某属性,同时,“接口的抽象方法”或“实现类的实现方法上”也添加了此注解,也配置了注解的同样的属性,却是不同的值,则以方法上的配置值为准!

    通常,推荐将注解添加在接口上,或接口中的抽象方法上!

    本质上,Spring JDBC是通过接口代理模式来实现的事务管理,如果将@Transactional注解添加在业务实现类中的自定义方法上(未在接口中声明的方法),会是错误的!

    通常,如果某个业务涉及2次或以上次数的写(增删改)操作,就必须使其是事务性的!另外,如果某个业务中涉及多次查询,使用@Transactional可以使得这些查询共用同一个数据库连接对象,可提高查询效率。

    另外:建议学习“事务的ACID特性”、“事务的传播”、“事务的隔离”。

  • 相关阅读:
    一周技术学习笔记(第78期)-顺序结构、循环结构、分支转移几十年未变也不会变...
    基于节点分层的配网潮流前推回代方法matlab程序(IEEE33节点潮流计算)
    什么是Spring的loC和Dl?
    XML快速入门
    动态内存管理
    体验一下 Claude 3.5 Sonnet
    环境主题静态HTML网页作业作品 大学生环保网页设计制作成品 简单DIV CSS布局网站
    在ubuntu中搭建Java开发环境
    新测试人和转行测试的同学看过来 你需要知道的这几点
    传奇开服教程完整版GOM引擎超详细的单机架设图文教程(小白一看就会)
  • 原文地址:https://blog.csdn.net/TheNewSystrm/article/details/126272675