Java阶段四Day07
关于MyBatis分页
对于分页采取简单原则,先分再说,将查询的所有字段都进行分页,对其中的不需分页操作进行单独设置,将其分页值调大,除非非常确定字段是否添加分页
引入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.1</version>
</dependency>
编写测试类
void listTagType(){
int pagNum = 2; //页码
int pagSize = 2; // 每页记录数
PageHelper.startPage(pagNum, pagSize); //设置分页参数
List<?> list = tagMapper.listTagType(); //【注意】必须紧随其后
System.out.println("查询完成,列表类型" + list.getClass().getName());
System.out.println("查询列表完成,列表项的数量:" + list.size());
System.out.println(list);
for (Object item : list) {
System.out.println("列表项:" + item);
}
System.out.println("______________");
PageInfo<?> pageInfo = new PageInfo<>(list);
System.out.println(pageInfo);
}
对于接口中返回值的设计
-
采用
Page
- 使用
Page
,没有使用PageInfo
更精彩,可能有些信息就没被显示
- 使用
-
采用
PageInfo
- 设计大于需求,不管是否用到都先设计出来,且和
Page
比,编码难度低,返回数据更多,但是存在依赖框架import com.github.pagehelper.PageInfo;
,当以后项目中换了其他的分页依赖,Service
也需要跟着修改
- 设计大于需求,不管是否用到都先设计出来,且和
-
采用自定义
PageData
-
基于上面的问题,仿写一个自定义的
PageInfo
,起名PageData
,摆脱依赖框架。import io.swagger.models.auth.In; import lombok.Data; import lombok.experimental.Accessors; import java.io.Serializable; import java.util.List; /** * 分页数据类 */ @Data @Accessors(chain = true) public class PageData<T> implements Serializable { /** * 每页记录数 */ private Integer pageSize; /** * 当前页码 */ private Integer currentPage; /** * 最大页码 */ private Integer maxPage; /** * 数据列表 */ private List<T> list; /** * 记录总数 */ private Long total; }
-
链式写法
在书写查询标签类别列表时,有一方法如下:
@Override
public PageData<TagTypeListItemVO> listTagType(Integer pageNum,Integer pageSize) {
log.debug("开始执行【查询标签类别列表】,页码:1,每页记录数:{}",pageNum,pageSize);
PageHelper.startPage(pagNum, pagSize); //设置分页参数
List<TagTypeListItemVO> list = tagMapper.listTagType();
PageInfo<TagTypeListItemVO> pageInfo = new PageInfo<>(list);
PageDataTagTypeListItemVO> pageData = new PageData<>();
pageData.setTotal(pageInfo.getTotal());
pageData.setMaxPage(pageInfo.getPages());
pageData.setCurrentPage(pageInfo.getPageNum());
pageData.setPageSize(pageInfo.getPageSize());
pageData.setList(pageInfo.getList());
return pageData;
}
上述方法虽然没有问题,但是setter
方法过于多,导致不是很简洁,可以使用来链式写法,在PageData
的类上加@Accessors(chain = true)
,代码可变成在一个setter
方法后不用;
结束,而是可以继续.
下一个setter
方法
@Override
public PageData<TagTypeListItemVO> listTagType(Integer pageNum,Integer pageSize) {
log.debug("开始执行【查询标签类别列表】,页码:1,每页记录数:{}",pageNum,pageSize);
PageHelper.startPage(pagNum, pagSize); //设置分页参数
List<TagTypeListItemVO> list = tagMapper.listTagType();
PageInfo<TagTypeListItemVO> pageInfo = new PageInfo<>(list);
PageDataTagTypeListItemVO> pageData = new PageData<>();
pageData.setTotal(pageInfo.getTotal())
.setMaxPage(pageInfo.getPages())
.setCurrentPage(pageInfo.getPageNum())
.setPageSize(pageInfo.getPageSize())
.setList(pageInfo.getList());
return pageData;
}
其原理是未加链式注解时,setter
方法为
public PageData setTotal(Long total){
this.total = total;
}
加上@Accessors(chain = true)
后,setter
方法变为
public PageData setTotal(Long total){
this.total = total;
return this;
}
即每个方法返回自己的对象,就又可以继续调用方法。
最后将PageInfo
转换成PageData
的转换器写成工具类,以后可以多次复用
import com.github.pagehelper.PageInfo;
/**
* 将PageInfo转换成PageData的转换器工具类
*
* @author liner
* @version 1.0
*/
public class PageUtils {
/**
* 将PageHelper框架中的PageInfo类型对象转换成自定义的PageData类型对象
*
* @param pageInfo PageInfo对象
* @param <T> PageInfo对象中的列表数据中的元素数据的类型
* @return 自定义的PageData类型的对象
*/
//static方法中 第一个泛型 指的是本方法中有个泛型 PageInfo 第二个泛型 指PageData
public synchronized static <T> PageData<T> convert(PageInfo pageInfo){
PageData<T> pageData = new PageData<>();
pageData.setTotal(pageInfo.getTotal())
.setMaxPage(pageInfo.getPages())
.setCurrentPage(pageInfo.getPageNum())
.setPageSize(pageInfo.getPageSize())
.setList(pageInfo.getList());
return pageData;
}
}
即最后成型的关于分页的方法写为
@Override
public PageData<TagTypeListItemVO> listTagType(Integer pageNum, Integer pageSize) {
log.debug("开始执行【查询标签类别列表】,页码:{},每页记录数:{}", pageNum, pageSize);
PageHelper.startPage(pageNum, pageSize);
List<TagTypeListItemVO> list = tagMapper.listTagType();
PageInfo<TagTypeListItemVO> pageInfo = new PageInfo<>(list);
return PageUtils.convert(pageInfo);
}
关于RESTful
在设计开发中:能不暴露的就不暴露,能少一些参数就少一些参数,用户所选越多,后台压力越大
使用Restful,建议和提倡将id
作为url
的一部分,采用{}
包住id
,写在请求的Mapping参数中,易于理解当前请求在干什么,相比传统的将请求参数放到?
后面跟有优势。更加的简短,且这些放在url
中的变量值都具有唯一性,并在方法的传参中使用@PathVariable
通过在{id}
中加入正则表达式和使用 @Range
对参数值进行取值检查,且对于把注解直接加到参数上的检查,需要在当前类上加入@Validated
,从而使注解生效
@ApiOperation("删除标签")
@ApiOperationSupport(order = 200)
@PostMapping("/{id:[0-9]+}/delete") //404更早的发现错误,400在尝试请求之后发现错误
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "标签ID", required = true, dataType = "long"),
})
public JsonResult deleteNew(@PathVariable @Range(min = 1,message = "删除标签失败,请提交合法的ID值") Long id) {
log.debug("开始处理【删除标签】的请求,参数:{}", id);
tagService.deleteById(id);
return JsonResult.ok();
}
关于事务
事务(Transaction):是一种数据库中能够保证一系列的写操作(增、删、改)要么全部成功,要么全部失败的机制
假设存在某个转账行为:
update 账户表 set 余额=余额-1000 where 账户名='张三';
update 账户表 set 余额=余额+1000 where 账户名='李四';
以上2个 UPDATE 操作,无论它们执行先后顺序是哪种,只要第1条执行成功,第2条没有执行成功,就是不可接受的结果
添加事务
在基于Spring JDBC
的数据库编程中,如果需要某个方法是“事务性"的,在此方法上添加@Transactional
注解即可。关于@Transactional
注解,可以添加在:
- 方法上
- 作用于当前方法,但此方法必须是重写的接口中声明过的方法
- 类上
- 作用于当前类中所有重写的接口中声明过的方法,即自定义的方法不可能是事务性的
- 接口中的抽象方法上
- 作用于实现了接口的类中重写的方法
- 接口上
- 作用于当前接口的实现类中的所有重写的方法
提示: SpringJDBC框架在实现事务管理时,使用了基于接口的代理模式
使用@Transactional
注解时,应该在接口的抽象方法上使用此注解,但是,在学习过程中,建议在接口上添加此注解
处理事务时执行过程
try {
开启事务:BEGIN
执行业务
提交事务:COMMIT
}catch (RuntimeException e) {
回滚事务:ROLLBACK
}
默认情况下,Spring JDBC
会在执行业务的过程中出现RuntimeException
时执行回滚,如果需要根据其它异常执行回滚,可以配置@Transactional
注解的rollbackFor
属性
@Transactional (rollbackFor = {
ServiceException.class,NullPointerException.class
})
或者,配置rollbackForclassName
属性,例如:
@Transactional (rollbackForclassName =
"com.liner.demo","java.lang.NullPointerException"
})
还可以配置noRollbackFor
属性,指定哪些异常不会导致回滚
@Transactional (noRollbackFor = {
classcastException.c1ass,IndexoutofBoundsException.class
})
也有对应的noRollbackForClassName
属性
注意:无论如何配置,导致回滚的异常类型都必须是RuntimeException
的子孙类。
由于Spring JDBC
会根据 RuntimeException
及其子孙类异常执行回滚,所以,在业务方法中执行任何增、删、改操作时,都应该及时获取受影响的行数,并判断此值是否符合预期,如果不符合,必须抛出RuntimeException
或其子孙类异常,触发回滚机制