ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

day72(Spring JDBC的事务管理,添加类别:(业务逻辑层,控制层),根据父级类别查询其所有子级类别:(持久层,业务逻辑层,控制层))

2022-06-13 22:03:53  阅读:185  来源: 互联网

标签:事务管理 逻辑 JsonResult String param state 添加 类别 public


day72(Spring JDBC的事务管理,添加类别:(业务逻辑层,控制层),根据父级类别查询其所有子级类别:(持久层,业务逻辑层,控制层))

1.基于Spring JDBC的事务管理

1.事务

是一种能够保证同一个业务中多个写(增删改)操作要么全部成功,要么失败的机制!

2.实现

在业务方法上添加@Transactional即可保证此方法是业务性(要么全部成功,要么全部失败)的。

3.机制

在Spring JDBC中,处理事务的机制大致是:

开启事务:Begin
try {
   你的业务方法
   提交:Commit
} catch (RuntimeException e) {
   回滚:Rollback
}

4.注意

  • 为保证事务性,所有的写操作在执行之后,必须有某个判定为失败的标准,且判断定为失败后,必须抛出RuntimeException或其子孙类异常!

  • pring JDBC默认对RuntimeException进行回滚处理,有必要的话,也可以配置为其它异常类型

5.注解添加位置(@Transactional)

  1. 接口

    • 会使得此接口的实现类的所有实现方法都是事务性的

  2. 接口上的抽象方法

    • 会使得此接口的实现类中,重写的此方法是事务性的

    • 只作用于当前方法

    • 如果接口上也配置了此注解,并且接口和抽象方法的注解均配置了参数,以方法上的配置为准

  3. 业务实现类

    • 会使得当前类中所有重写的方法都是事务性

      • 自定义的方法不会是事务性的

  4. 业务实现类中的方法

    • 不可以添加在自定义的(不是重写的接口的)方法上

      • 语法上,可以添加,但执行时,不允许

Spring JDBC是通过接口代理的方式进行事务管理,所以,只对接口中声明的方法有效!

通常,应该将@Transactional添加在接口中的抽象方法上(如果偷懒,或为了避免遗漏,也可以直接添加在接口上)。

6.添加依赖:csmall-product-service的@Transactional

<!-- Mybatis Spring Boot:Mybatis及对Spring Boot的支持 -->
<!-- 仅需要保留spring-jdbc,使得业务接口可以使用@Transactional注解 -->
<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <exclusions>
       <exclusion>
           <groupId>org.mybatis.spring.boot</groupId>
           <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis-spring</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </exclusion>
       <exclusion>
           <groupId>com.zaxxer</groupId>
           <artifactId>HikariCP</artifactId>
       </exclusion>
   </exclusions>
</dependency>

2.类别管理--添加类别--业务逻辑层(续)

1.处理异常:

1.原因:

  • 目前,在业务实现中,视为“错误”时始终抛出ServiceException,且没有任何异常信息,是不合理的!

  • 在略大规模的项目中,“错误”的种类可能较大,如果为每一种“错误”都创建一个对应的异常,则需要创建的异常类型就比较多,但是,这些异常类除了名称不一样以外,几乎没有不同,所以,存在不利于管理和维护的问题。

  • 其实,也可以只使用1个异常类型(或者少量异常类型),但是,每次抛出时,也需要明确的表示“是哪一种错误”,则可以在异常类型中添加“业务状态码”。

2.解决步骤:

1.添加状态码类型

package cn.tedu.csmall.common.web;

public enum State {

   OK(20000),
   ERR_CATEGORY_NAME_DUPLICATE(40100), // 客户端引起的--类别--名称冲突(被占用)
   ERR_CATEGORY_NOT_FOUND(40101), // 客户端引起的--类别--数据不存在(查询参数值不正确)
   ERR_INSERT(50000), // 服务端引起的--插入数据错误
   ERR_UPDATE(50001); // 服务端引起的--更新数据错误

   private Integer value;

   State(Integer value) {
       this.value = value;
  }

   public Integer getValue() {
       return value;
  }

}

2.自定义方法(state,message)

在ServiceException中,自定义构造方法,强制要求传入State stateString message参数,并且,为State类型参数提供公有的获取值的方法:

public class ServiceException extends RuntimeException {
   private State state;
   
   public ServiceException(State state, String message) {
       super(message);
       if (state == null) {
           throw new IllegalArgumentException("使用ServiceException必须指定错误时的业务状态码!");
      }
       this.state  = state;
  }
   
   public State getState() {
       return state;
  }
}

3.应用:在后续抛异常时,应传入state与message.例如:

throw new ServiceException(State.ERR_CATEGORY_NOT_FOUND, "添加类别失败,父级类别不存在!");

4.测试(保证修改后依旧能正常运行)

3.类别管理--添加类别--控制器层

1.处理跨域(一次性配置)

方法:

csmall-product-webapi的根包下config包下创建SpringMvcConfiguration类,实现WebMvcConfigururer接口,重写其中的方法,以解决跨域问题:

package cn.tedu.csmall.product.webapi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SpringMvcConfiguration implements WebMvcConfigurer {

   @Override
   public void addCorsMappings(CorsRegistry registry) {
       registry.addMapping("/**")
              .allowedOriginPatterns("*")
              .allowedMethods("*")
              .allowedHeaders("*")
              .allowCredentials(true)
              .maxAge(3600);
  }

}

2.控制器类与处理请求的方法

1.创建JsonResult类,并在csmall-common中补充依赖lombok

package cn.tedu.csmall.common.web;

import lombok.Data;

import java.io.Serializable;

@Data
public class JsonResult<T> implements Serializable {

   // 状态码,例如:200
   private Integer state;

   // 消息,例如:"登录失败,用户名不存在"
   private String message;

   // 数据
   private T data;

   private JsonResult() {}

   public static JsonResult<Void> ok() {
       // JsonResult jsonResult = new JsonResult();
       // jsonResult.setState(1);
       // return jsonResult;
       return ok(null);
  }

   public static <T> JsonResult<T> ok(T data) {
       JsonResult<T> jsonResult = new JsonResult<>();
       jsonResult.setState(State.OK.getValue());
       jsonResult.setData(data);
       return jsonResult;
  }

   public static JsonResult<Void> fail(State state, String message) {
       JsonResult<Void> jsonResult = new JsonResult<>();
       jsonResult.setState(state.getValue());
       jsonResult.setMessage(message);
       return jsonResult;
  }
   
}

2.创建CategoryController类(csmall-product-webapi的根包下创建controller)

1.添加注解

  • @RestController

  • @RequestMapping(value = "/categories", produces = "application/json; charset=utf-8")

    @RestController
    @RequestMapping(value = "/categories", produces = "application/json; charset=utf-8")
    public class CategoryController {
       
    }

2.添加请求的方法

@Autowired
private ICategoryService categoryService;

@PostMapping("/add-new")
public JsonResult<Void> addNew(CategoryAddNewDTO categoryAddNewDTO) {
   categoryService.addNew(categoryAddNewDTO);
   return JsonResult.ok();
}

3.控制器层测试

1.创建测试类

csmall-product-webapi的测试的根包下创建controller.CategoryControllerTests测试类,编写并执行测试:

@SpringBootTest
@AutoConfigureMockMvc
public class CategoryControllerTests {
   
   @Autowired
   MockMvc mockMvc;
   
   @Test
   @Sql("classpath:truncate.sql")
   public void testAddNewSuccessfully() throw Exception {
       // 准备测试数据,不需要封装,应该全部声明为String类型
       String name = "水果";
       String parentId = "0"; // 即使目标类型是Long,参数值也不要加L
       String keywords = "水果的关键字是啥";
       String sort = "66";
       String icon = "图标待定";
       String isDisplay = "1";
       // 请求路径,不需要写协议、服务器主机和端口号
       String url = "/categories/add-new";
       // 执行测试
       // 以下代码相对比较固定
       mockMvc.perform( // 执行发出请求
               MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
              .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
              .param("name", name) // 请求参数,有多个时,多次调用param()方法
              .param("parentId", parentId)
              .param("keywords", keywords)
              .param("icon", icon)
              .param("sort", sort)
          .param("isDisplay", isDisplay)
              .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
              .andExpect( // 预判结果,类似断言
                       MockMvcResultMatchers
                              .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                              .value(20000)) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
              .andDo( // 需要执行某任务
                       MockMvcResultHandlers.print()); // 打印日志
  }
   
}

4.处理异常

1.添加依赖项:(csmall-common)

<!-- Spring Boot Web:支持Spring MVC -->
<!-- 需要使用到@RestControllerAdvice等注解 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-json</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
       </exclusion>
   </exclusions>
</dependency>

2.创建全局异常类(GlobalExceptionHandler):csmall-common根包下的ex包下创建handler.GlobalExceptionHandler,并在此类中处理异常:

@RestControllerAdvice
public class GlobalExceptionHandler {
   
   @ExceptionHandler(ServiceException.class)
   public JsonResult<Void> handleServiceException(ServiceException ex) {
       return JsonResult.fail(ex.getState(), ex.getMessage());
  }

}

3.创建配置类(config.CsmallCommonConfiguration)

完成后,使用错误的测试数据时,会发现根本不会处理异常,是因为在csmall-product-webapi中默认执行的组件扫描不会扫描到以上GlobalExceptionHandler所在的包,为了解决此问题,应该先在csmall-common的根包下创建config.CsmallCommonConfiguration类,此类应该是配置类,且配置组件扫描:

package cn.tedu.csmall.common.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@ComponentScan("cn.tedu.csmall.common.ex.handler")
public class CsmallCommonConfiguration {
}

4.在启动类(csmall-product-webapi)引用上述配置类

package cn.tedu.csmall.product.webapi;

@SpringBootApplication
@Import({CsmallCommonConfiguration.class}) // 新增
public class CsmallProductWebapiApplication {

   public static void main(String[] args) {
       SpringApplication.run(CsmallProductWebapiApplication.class, args);
  }

}

5.测试

@Test
@Sql({"classpath:truncate.sql", "classpath:insert_data.sql"})
public void testAddNewFailBecauseNameDuplicate() throws Exception {
   // 准备测试数据,不需要封装,应该全部声明为String类型
   String name = "类别001";
   String parentId = "0"; // 即使目标类型是Long,参数值也不要加L
   String keywords = "水果的关键字是啥";
   String sort = "66";
   String icon = "图标待定";
   String isDisplay = "1";
   // 请求路径,不需要写协议、服务器主机和端口号
   String url = "/categories/add-new";
   // 执行测试
   // 以下代码相对比较固定
   mockMvc.perform( // 执行发出请求
           MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                  .param("name", name) // 请求参数,有多个时,多次调用param()方法
                  .param("parentId", parentId)
                  .param("keywords", keywords)
                  .param("icon", icon)
                  .param("sort", sort)
                  .param("isDisplay", isDisplay)
                  .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
          .andExpect( // 预判结果,类似断言
                   MockMvcResultMatchers
                          .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                          .value(State.ERR_CATEGORY_NAME_DUPLICATE.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
          .andDo( // 需要执行某任务
                   MockMvcResultHandlers.print()); // 打印日志
}

@Test
@Sql({"classpath:truncate.sql"})
public void testAddNewFailBecauseParentNotFound() throws Exception {
   // 准备测试数据,不需要封装,应该全部声明为String类型
   String name = "类别001";
   String parentId = "-1"; // 即使目标类型是Long,参数值也不要加L
   String keywords = "水果的关键字是啥";
   String sort = "66";
   String icon = "图标待定";
   String isDisplay = "1";
   // 请求路径,不需要写协议、服务器主机和端口号
   String url = "/categories/add-new";
   // 执行测试
   // 以下代码相对比较固定
   mockMvc.perform( // 执行发出请求
           MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                  .param("name", name) // 请求参数,有多个时,多次调用param()方法
                  .param("parentId", parentId)
                  .param("keywords", keywords)
                  .param("icon", icon)
                  .param("sort", sort)
                  .param("isDisplay", isDisplay)
                  .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
          .andExpect( // 预判结果,类似断言
                   MockMvcResultMatchers
                          .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                          .value(State.ERR_CATEGORY_NOT_FOUND.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
          .andDo( // 需要执行某任务
                   MockMvcResultHandlers.print()); // 打印日志
}

5.验证请求参数格式的基本有效性

1.Validation框架的基本使用:

  • 添加依赖

  • 在控制器类中处理请求的方法的被验证的参数(封装的对象)之前添加@Validated / @Valid

  • 在参数的类型(封装的类型)的属性之前添加验证注解

  • 在统一处理异常的类中对BindException进行处理

1.添加依赖

1.验证依赖(Validation)
<!-- Spring Boot Validation:验证请求参数的基本格式 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
2.@NotNull依赖

由于CategoryAddNewDTO等类在csmall-pojo模块中的,要在此类中添加@NotNull等注解,则必须在csmall-pojo中添加依赖:

<!-- Spring Boot Validation:验证请求参数的基本格式 -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
   <scope>provided</scope>
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter</artifactId>
       </exclusion>
       <exclusion>
           <groupId>org.apache.tomcat.embed</groupId>
           <artifactId>tomcat-embed-el</artifactId>
       </exclusion>
   </exclusions>
</dependency>

 

2.在控制器类中处理请求的方法的被验证的参数(封装的对象)之前添加@Validated / @Valid

CategoryController处理请求的方法的参数之前添加@Validated / @Valid注解:

@PostMapping("/add-new")
// ===== 在以下方法的参数前添加@Validated / @Valid注解 =====
public JsonResult<Void> addNew(@Validated CategoryAddNewDTO categoryAddNewDTO) {
   categoryService.addNew(categoryAddNewDTO);
   return JsonResult.ok();
}

3.在参数的类型(封装的类型)的属性之前添加验证注解

CategoryAddNewDTO的name属性上添加@NotNull约束(其它的约束等到测试通过之后再补充):

@Data
public class CategoryAddNewDTO implements Serializable {

   @NotNull(message = "添加类别失败,必须填写类别名称!") // 新增
   private String name;
   
   // ===== 其它原有代码 =====
   
}

4.State中添加对应“请求参数格式错误”的枚举值:

public enum State {

   OK(20000),
   // ===== 下行为新增 ======
   ERR_BAD_REQUEST(40000), // 客户端引起的--请求参数格式错误
   
   // ===== 其它原有代码 =====
}

 

5.在统一处理异常的类中对BindException进行处理

@ExceptionHandler(BindException.class)
public JsonResult<Void> handleBindException(BindException ex) {
   List<FieldError> fieldErrors = ex.getBindingResult().getFieldErrors();
   StringBuilder stringBuilder = new StringBuilder();
   for (FieldError fieldError : fieldErrors) {
       stringBuilder.append(";");
       stringBuilder.append(fieldError.getDefaultMessage());
  }
   String message = stringBuilder.substring(1);
   return JsonResult.fail(State.ERR_BAD_REQUEST, message);
}

6.测试

@Test
@Sql({"classpath:truncate.sql"})
public void testAddNewFailBecauseBadRequest() throws Exception {
   // 准备测试数据,注意:此次没有提交必要的name属性值
   String parentId = "0"; // 即使目标类型是Long,参数值也不要加L
   String keywords = "水果的关键字是啥";
   String sort = "66";
   String icon = "图标待定";
   String isDisplay = "1";
   // 请求路径,不需要写协议、服务器主机和端口号
   String url = "/categories/add-new";
   // 执行测试
   // 以下代码相对比较固定
   mockMvc.perform( // 执行发出请求
           MockMvcRequestBuilders.post(url) // 根据请求方式决定调用的方法
                  .contentType(MediaType.APPLICATION_FORM_URLENCODED) // 请求数据的文档类型,例如:application/json; charset=utf-8
                   // .param("name", name) // 注意:此处不提交必要的name属性
                  .param("parentId", parentId)
                  .param("keywords", keywords)
                  .param("icon", icon)
                  .param("sort", sort)
                  .param("isDisplay", isDisplay)
                  .accept(MediaType.APPLICATION_JSON)) // 接收的响应结果的文档类型,注意:perform()方法到此结束
          .andExpect( // 预判结果,类似断言
                   MockMvcResultMatchers
                          .jsonPath("state") // 预判响应的JSON结果中将有名为state的属性
                          .value(State.ERR_BAD_REQUEST.getValue())) // 预判响应的JSON结果中名为state的属性的值,注意:andExpect()方法到此结束
          .andDo( // 需要执行某任务
                   MockMvcResultHandlers.print()); // 打印日志
}

7.测试成功后,应该在CategoryAddNewDTO的各属性中补充更多的、必要的注解进行约束,并且,添加更多约束后,还应该编写更多的测试

4.类别管理--根据父级类别查询其所有子级类别--持久层

1.规划SQL语句

1.SQL语句

select * from pms_category where parent_id=? and enable=1 and is_display=1 order by sort desc, gmt_modified desc;

2.字段列表

id, name, sort, icon, is_parent

2.抽象方法(可能需要创建VO类)

1.创建VO类封装以上5个属性(CategorySimpleListItemVO)

csmall-pojo的根包下的vo包下创建CategorySimpleListItemVO类,封装以上设计的5个字段对应的属性:

package cn.tedu.csmall.pojo.vo;

import lombok.Data;

import java.io.Serializable;

@Data
public class CategorySimpleListItemVO implements Serializable {
   private Long id;
   private String name;
   private String icon;
   private Integer sort;
   private Integer isParent;
}

2.在CategoryMapper接口中添加:

List<CategorySimpleListItemVO> listByParentId(Long parentId);

 

3.在XML中配置SQL

添加配置(CategoryMapper.xml)

   <!--    List<CategorySimpleListItemVO> listByParentId(Long parentId)-->
   <select id="listByParentId" resultMap="SimpleListResultMap">
      select
       <include refid="SimpleListQueryFields"/>
      from pms_category where parent_id=#{parentId} and enable=1 and is_display=1 order by sort desc, gmt_modified
      desc;
   </select>
   <sql id="SimpleListQueryFields">
       <if test="true">
          id, name, sort, icon, is_parent
       </if>
   </sql>
   <resultMap id="SimpleListResultMap" type="cn.tedu.csmall.pojo.vo.CategorySimpleListItemVO">
       <id column="id" property="id"/>
       <result column="name" property="name"/>
       <result column="sort" property="sort"/>
       <result column="icon" property="icon"/>
       <result column="is_parent" property="isParent"/>
   </resultMap>

 

4.测试

本次测试推荐使用人工检查查询结果。

5. 类别管理--根据父级类别查询其所有子级类别--业务逻辑层

1. 接口和抽象方法

ICategoryService中添加:

List<CategorySimpleListItemVO> listByParentId(Long parentId);

2. 实现

CategoryServiceImpl中直接调用categoryMapper执行查询并返回即可。

3. 测试

与持久层测试类似。

6. 类别管理--根据父级类别查询其所有子级类别--控制器层

CategoryController中添加:

@GetMapping("/list-by-parent")
public JsonResult<List<CategorySimpleListItemVO>> listByParentId(Long parentId) {
   // 调用service并将结果封装到JsonResult中
}

CategoryControllerTests中测试:

标签:事务管理,逻辑,JsonResult,String,param,state,添加,类别,public
来源: https://www.cnblogs.com/xiaoyezilei/p/16372473.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有