day72(Spring JDBC的事务管理,添加类别:(业务逻辑层,控制层),根据父级类别查询其所有子级类别:(持久层,业务逻辑层,控制层))
2022/6/13 23:22:19
本文主要是介绍day72(Spring JDBC的事务管理,添加类别:(业务逻辑层,控制层),根据父级类别查询其所有子级类别:(持久层,业务逻辑层,控制层)),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
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)
-
接口
-
会使得此接口的实现类的所有实现方法都是事务性的
-
-
接口上的抽象方法
-
会使得此接口的实现类中,重写的此方法是事务性的
-
只作用于当前方法
-
如果接口上也配置了此注解,并且接口和抽象方法的注解均配置了参数,以方法上的配置为准
-
-
业务实现类
-
会使得当前类中所有重写的方法都是事务性
-
自定义的方法不会是事务性的
-
-
-
业务实现类中的方法
-
不可以添加在自定义的(不是重写的接口的)方法上
-
语法上,可以添加,但执行时,不允许
-
-
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 state
和String 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
中测试:
这篇关于day72(Spring JDBC的事务管理,添加类别:(业务逻辑层,控制层),根据父级类别查询其所有子级类别:(持久层,业务逻辑层,控制层))的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-28一步到位:购买适合 SEO 的域名全攻略
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign服务间调用学习入门
- 2024-12-27OpenFeign学习入门:轻松掌握微服务通信
- 2024-12-27OpenFeign学习入门:轻松掌握微服务间的HTTP请求
- 2024-12-27JDK17新特性学习入门:简洁教程带你轻松上手
- 2024-12-27JMeter传递token学习入门教程
- 2024-12-27JMeter压测学习入门指南
- 2024-12-27JWT单点登录学习入门指南
- 2024-12-27JWT单点登录原理学习入门