前言
关于mybatis的使用其实蛮常用的,而其中的分页也是非常常用的。我见过有的公司直接将分页写成业务内容。其实吧,我觉得这才是正途,但是很多人认为其中存在大量重复内容,难以维护,难以统一控制质量。于是,我们就希望写查询的时候正常写,然后有个什么帮我们把这东西变成分好页的,也能够自动获取总条数。而这次呢,我放弃掉了之前自己写的分页常见,转而使用一个纯第三方的插件,用起来很简单。而且,之前我在springboot配置mybatis的时候,使用注解配置也总是失败,这次我参照官网进行配置,一切顺利,故,记录下来,方便后来搭建项目使用。
这次项目的基础代码是建立在 https://blog.naturetrible.com/index.php/294/research/set-up-background-framework/nature/ 基础之上的,有不清楚的可以参见之前的博客。
依赖
很多时候我们对引入这些东西的时候,其实搞不清楚他们的依赖,导致,加的东西不敢删,添加的配置不敢去除,其实很多都是无用的。而这次,我们的依赖是我试验后相对明确的,如下:
<!--db start-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.startber.version}</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.7</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.5</version>
</dependency>
<!--db end-->
其实,我们在操作mybatis的时候,使用的是spring对mybatis提供的支持库,不过我也不知道这是spring写的还是mybatis写的,总之,很简单,很好用。而分页插件是第三方的,似乎是个人写的,总是,用起来也很简单。如果还是要对分页有性能优化,控制sql生成的话,还是得自己实现哈。druid是我们使用的数据源。我们这次使用的数据库是postgresql,搭建的方法参见: https://blog.naturetrible.com/index.php/99/research/nature/
配置
由于这次的代码量相对多些,我们就按照依赖顺序给出最终结果并加以说明了。这次的配置除了上面的maven依赖配置以外,就是我们对数据源、mybatis和分页插件的具体配置了。
数据源
我们先说数据源的配置,下面先给出配置类:
package com.ntbrick.management.platform.config;
@Configuration
@ConditionalOnClass(DruidDataSource.class)
static class Druid extends DruidConfiguration {
@Bean
@ConfigurationProperties("spring.datasource.druid")
public DruidDataSource dataSource(DataSourceProperties properties) {
DruidDataSource druidDataSource = properties.initializeDataSourceBuilder().type(DruidDataSource.class).build();
DatabaseDriver databaseDriver = DatabaseDriver.fromJdbcUrl(properties.determineUrl());
String validationQuery = databaseDriver.getValidationQuery();
if (validationQuery != null) {
druidDataSource.setValidationQuery(validationQuery);
}
return druidDataSource;
}
}
这里其实只是注解了一个内部类,至于为什么这么写,其实我还没梳理明白,我只搞清楚了一些控制点。比如,@ConditionalOnClass(DruidDataSource.class),是说在扫描路径中发现了DruidDataSource.class的这个类生效。这个配置类里注册了一个bean,从spring.datasource.druid属性下面配置这个bean的属性。
在application.yml文件中对应的配置信息为:
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://192.168.137.98:5432/ntbrick_management_platform
username: postgres
password: 123456
druid:
#初始化连接大小
initial-size: 8
#最小空闲连接数
min-idle: 5
#最大连接数
max-active: 10
#查询超时时间
query-timeout: 6000
#事务查询超时时间
transaction-query-timeout: 6000
#关闭空闲连接超时时间
remove-abandoned-timeout: 1800
filters: stat,config
这里的属性我觉得我就不用详细解释了,用的时候自己看着改吧。需要注意的就是我这里的配置是操作postgresql数据库的。至此,我们已经把数据源配置成spring中的一个bean了。
mybatis
有了mybatis-spring-boot-starter,其实spring的配置也是非常简单的,只需要一个配置类即可。
package com.ntbrick.management.platform.config;
@Configuration // 该注解类似于spring配置文件
//@MapperScan(basePackages = "com.example.mpdemo.dao") // 扫描Dao文件
@EnableTransactionManagement //支持事务注解
public class MybatisConfig implements TransactionManagementConfigurer {
@Autowired
private DataSource dataSource;//默认配置文件中的数据源
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(dataSource);// 指定数据源(这个必须有,否则报错)
return fb.getObject();
}
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return new DataSourceTransactionManager(dataSource);
}
}
这里我保留了一个我没用到注释,就是指定扫描dao的位置。因为我的dao的位置并不集中,所以我就没配,就会从所有的bean中扫描。这里面还用到了之前配置的DataSource。最根本的,就是配置一个SqlSessionFactory类的bean,mybatis就可以使用了,而另一个是我为了使用事务而进行的配置。然后,mybatis就可以使用了。不过我们还是晚点介绍使用代码,接下来顺手把分页插件的配置也介绍了。
分页插件
这个分页插件相对简单,只要在application.yml中配置一些内容就可以了。
pagehelper:
#数据库方言
helper-dialect: postgresql
#如果为true,页码过大的时候总是会显示最后一页数据,我们这里配置的是false,注意小于1的页码总是相当于第1页
reasonable: false
supportMethodsArguments: true
params: count=countSql
实现
这里我们设计了两个简单的接口用来简单得测试mybatis和分页插件可用:
- 根据ID获取实体
- 获取分页后的实体列表
这次代码都放在com.ntbrick.management.platform.mybatis包内。
根据ID获取实体
我们需要在controller层添加一个接口,service层添加一个实现方法,dao层添加一个数据库操作方法。具体代码展示如下:
@RequestMapping("/mybatis/demo")
@RestController
public class DemoController extends BaseController {
@Autowired
private DemoService demoService;
@RequestMapping("/get")
public DefaultResponse getDemo(@RequestBody String demoId){
return this.successResponse(demoService.getEntityById(Long.valueOf(demoId)));
}
}
@Component
public class DemoService {
@Autowired
private DemoDao demoDao;
/**
* 根据id获取demo实体
* @param id demo的id
* @return demo的实体,如果没有找到,则返回null
*/
public DemoEntity getEntityById(Long id){
return demoDao.getEntityById(id);
}
}
@Mapper
public interface DemoDao {
@Select("SELECT\n" +
"\tdi.demo_id AS demoId,\n" +
"\tdi.NAME AS NAME,\n" +
"\tdi.remark AS remark \n" +
"FROM\n" +
"\tdemo_info AS di\n" +
"where di.demo_id =#{id}")
DemoEntity getEntityById(@Param("id") Long id);
}
public class DemoEntity {
private Long demoId;
private String name;
private String remark;
public Long getDemoId() {
return demoId;
}
public void setDemoId(Long demoId) {
this.demoId = demoId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRemark() {
return remark;
}
public void setRemark(String remark) {
this.remark = remark;
}
}
这些代码我们一个类一个类得看:
- DemoEntity: 这个是测试用的实体,和数据库表的结构是一样的,方便我们对数据库进行操作。
- DemoController 为了可以进行交互,我们写了这个controller,提供一个可以带入参数的接口,返回查询出的实体。期间的操作都是通过DemoService实现的,所以这里也注解进了一个DemoService实例
- DemoService 具体实现操作的服务,方便管理,我们将DemoService打上了Component注解,注册成了一个Bean。DemoService使用DemoDao具体实现数据库操作,DemoDao也是一个bean,我们注解了进来。
- DemoDao 这个是mybatis操作的具体实现类了。首先,注意我们在类上打了@Mapper注解,这既标志了这个类mybatis的一个Mapper,也将这个Dao注册成了一个spring的bean。另外,看我们在getEntityById方法上面打的@Select注解,这标志着这是一个查询,与此类似的还有@Update,@Insert,@Delete注解,代表了不同的sql操作,里面的写法和mybatis在xml里的写法差不多。而如果在sql里面需要使用xml里的标签新型循环判断啥的,则可以把注解中的内容包裹在script标签中,用法就和xml中一模一样了。而我们在方法参数中的注解@Param则表明这个参数在sql语句中的变量名字。至此,我们已经可以操作数据库了。
获取分页后的实体列表
我们分别在controller、service和dao中添加了方法,如下:
DemoController:
@RequestMapping("/list")
public DefaultResponse getList(@RequestBody Map<String,Integer> params){
PageContainer<DemoEntity> result=demoService.getEntityList(params.get("pageSize"),params.get("pageNum"));
return this.successResponse(result);
}
DemoService:
public PageContainer<DemoEntity> getEntityList(Integer pageSize,Integer pageNum){
PageParamDto pageParams=new PageParamDto();
pageParams.setPageNum(pageNum);
pageParams.setPageSize(pageSize);
PageHelper.startPage(pageNum,pageSize); //开始分页
List<DemoEntity> data=this.demoDao.getEntityList(); //正常查询数据
PageInfo<DemoEntity> pageInfo=new PageInfo<>(data); //创建这个对象的时候,对象内会被内置分页的信息,主要是总条数
PageContainer<DemoEntity> pageContainer = new PageContainer<>();
pageContainer.setData(data);
pageContainer.setTotalCount(pageInfo.getTotal());
pageContainer.setPageInfo(pageParams);
return pageContainer;
}
DemoDao:
@Select("SELECT\n" +
"\tdi.demo_id AS demoId,\n" +
"\tdi.NAME AS NAME,\n" +
"\tdi.remark AS remark \n" +
"FROM\n" +
"\tdemo_info AS di\n"+
" order by di.demo_id")
List<DemoEntity> getEntityList();
在上述代码中我们可以发现两个找不到的类,PageContainer和PageParamDto,这两个类是我们为了方便操作分页后的数据结构而创建的两个类。PageContainer类包含了分页后的数据以及这些数据的分页信息,PageParamDto则为请求分页时需要的参数。它们的代码如下:
public class PageContainer<T> {
//页码,从1开始
private Integer pageNum;
//每页多少条
private Integer pageSize;
//查询数据
private List<T> data;
//查询数据总数
private Long totalCount;
public Integer getPageNum() {
return pageNum;
}
public void setPageNum(Integer pageNum) {
this.pageNum = pageNum;
}
public Integer getPageSize() {
return pageSize;
}
public void setPageSize(Integer pageSize) {
this.pageSize = pageSize;
}
public List<T> getData() {
return data;
}
public void setData(List<T> data) {
this.data = data;
}
public Long getTotalCount() {
return totalCount;
}
public void setTotalCount(Long totalCount) {
this.totalCount = totalCount;
}
/**
* 将PageParamDto的PageNum与PageSize的值设置到PageContainer
* @param pageParamDto
*/
public void setPageInfo(PageParamDto pageParamDto) {
this.setPageNum(pageParamDto.getPageNum());
this.setPageSize(pageParamDto.getPageSize());
}
}
public class PageParamDto {
//页码,从1开始
private int pageNum;
//每页多少条
private int pageSize;
public int getPageNum() {
return pageNum;
}
public void setPageNum(int pageNum) {
this.pageNum = pageNum;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
}
这里,我们挑重点看,重点,在service的那个方法里,重点的三行代码我们在下面再看下:
PageHelper.startPage(pageNum,pageSize); //开始分页
List<DemoEntity> data=this.demoDao.getEntityList(); //正常查询数据
PageInfo<DemoEntity> pageInfo=new PageInfo<>(data); //创建这个对象的时候,对象内会被内置分页的信息,主要是总条数
这里new对象的时候注入数据的具体方式以前本来说追下源码怎么回事的,结果最后也没有看明白。总之,分页的方式就是在查询数据前调用startPage,然后new一个PageInfo对象。这里不能嵌套进行分页,一个session一次只能进行一个分页操作。反正我们公司的项目目前没有觉得这个限制很大,就先这么用着吧。
总结
本篇文章代码较多,可能调理不是太清晰。自己敲代码试下就会明白。另外修改数据的方法我没写,和查询是一样的。另外,现在的配置下,事务是生效的,只需要在需要的方法上使用@Transaction注解就可以了。