SpringBoot学习历程(九):SpringBoot2.X集成mybatis和pagehelper分页
前言
- 本人github仓库地址:https://github.com/RabbitsInTheGrass/springBoot-Learning
- 本人所用SpringBoot版本为2.1.9.RELEASE
1. 引入相关依赖
<!-- 引入mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.9</version>
</dependency>
<!-- 引入mysql依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2. 增加datasource和pagehelper配置信息
在application.yml文件中增加:
spring:
datasource:
#数据库访问配置
url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
username: root
password: root
driver-class-name: com.mysql.jdbc.Driver
pagehelper:
helperDialect: mysql #指定分页插件使用哪种方言
reasonable: true #分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页, pageNum>pages(超过总数时),会查询最后一页。
supportMethodsArguments: true #支持通过Mapper接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
mybatis:
type-aliases-package: com.rabbits.learn.entity # 注意:对应实体类的路径
#注意:一定要对应mapper映射xml文件的所在路径
mapper-locations: classpath*:com/rabbits/learn/**/dao/*Mapper.xml
3. 创建用户信息类
3.1 User.java
这里我用了lombok注解
package com.rabbits.learn.entity;
import lombok.Data;
import java.io.Serializable;
/**
* @Description:
* @Author: RabbitsInTheGrass_xj
* @Date: 2019/10/6 20:35
*/
@Data
public class User implements Serializable {
private static final long serialVersionUID = 793100833087044040L;
private Integer id;
private String userName;
private String userPsw;
private String idCard;
private String phone;
private String address;
private Integer state;
}
3.2 UserMapper.java
package com.rabbits.learn.dao;
import com.rabbits.learn.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @Description:
* @Author: RabbitsInTheGrass_xj
* @Date: 2019/10/1 20:39
*/
@Component
@Mapper
public interface UserMapper {
/**
* @return int
* @Description 新增用户信息
* @Param [record] 用户信息
**/
int insert(User record);
/**
* @return User用户信息
* @Description 根据主键id查询用户信息
* @Param id 键ID
**/
User selectByPrimaryKey(Integer id);
/**
* @return int
* @Description 根据主键id更新用户信息
* @Param [record] 用户
**/
int updateByPrimaryKey(User record);
/**
* @return int
* @Description 根据主键id删除用户信息
* @Param [id] 主键id
**/
int deleteByPrimaryKey(Integer id);
/**
* @return 用户信息列表
* @Description 查询用户信息列表
**/
List<User> selectAll();
}
3.3 UserMapper.xml
mybatis.mapper-locations=classpath:mapper/*.xml #注意:一定要对应mapper映射xml文件的所在路径
在resource目录下新建mapper目录,创建UserMapper.xml。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.rabbits.learn.dao.UserMapper" >
<resultMap id="UserMap" type="com.rabbits.learn.entity.User" >
<id property="id" column="ID" jdbcType="INTEGER" />
<result property="userName" column="USER_NAME" jdbcType="VARCHAR" />
<result property="userPsw" column="USER_PSW" jdbcType="VARCHAR" />
<result property="idCard" column="ID_CARD" jdbcType="VARCHAR" />
<result property="phone" column="PHONE" jdbcType="VARCHAR" />
<result property="address" column="ADDRESS" jdbcType="VARCHAR" />
<result property="state" column="STATE" jdbcType="INTEGER" />
</resultMap>
<insert id="insert" parameterType="com.rabbits.learn.entity.User" >
insert into sys_user (ID, USER_NAME, USER_PSW, ID_CARD, PHONE, ADDRESS, STATE)
values (#{id,jdbcType=INTEGER},
#{userName,jdbcType=VARCHAR}, #{userPsw,jdbcType=VARCHAR}, #{idCard,jdbcType=VARCHAR},
#{phone,jdbcType=VARCHAR}, #{address,jdbcType=VARCHAR}, #{state,jdbcType=INTEGER})
</insert>
<select id="selectAll" resultMap="UserMap" >
SELECT
t.ID,
t.USER_NAME,
t.USER_PSW,
t.ID_CARD,
t.PHONE,
t.ADDRESS,
t.state
FROM sys_user t
</select>
<select id="selectByPrimaryKey" resultMap="UserMap" parameterType="java.lang.Integer" >
SELECT
t.ID,
t.USER_NAME,
t.USER_PSW,
t.ID_CARD,
t.PHONE,
t.ADDRESS,
t.STATE
FROM sys_user t
where t.ID = #{id,jdbcType=INTEGER}
</select>
<update id="updateByPrimaryKey" parameterType="com.rabbits.learn.entity.User" >
update sys_user
set t.USER_NAME = #{userName,jdbcType=VARCHAR},
t.USER_PSW = #{userPsw,jdbcType=VARCHAR},
t.ID_CARD = #{idCard,jdbcType=VARCHAR},
t.PHONE = #{phone,jdbcType=VARCHAR},
t.ADDRESS = #{address,jdbcType=VARCHAR},
t.STATE = #{state,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
delete from sys_user t where t.id = #{id,jdbcType=INTEGER}
</delete>
</mapper>
3.4 UserService.java
package com.rabbits.learn.service;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.rabbits.learn.dao.UserMapper;
import com.rabbits.learn.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Description:
* @Author: RabbitsInTheGrass_xj
* @Date: 2019/10/6 21:12
*/
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
/**
* @return com.github.pagehelper.PageInfo<User>
* @Description 分页查询用户信息
* @Param [pageNum, pageSize] 分页信息
**/
public PageInfo<User> selectAll(int pageNum, int pageSize) {
//将参数传给这个方法就可以实现物理分页了,非常简单。
PageHelper.startPage(pageNum, pageSize);
List<User> userList = userMapper.selectAll();
return new PageInfo<>(userList);
}
/**
* @return int
* @Description 新增用户
* @Param [user] 用户信息
**/
public int addUser(User user) {
return userMapper.insert(user);
}
/**
* @return int
* @Description 更新用户信息
* @Param [user] 用户
**/
public int updateUser(User user) {
return userMapper.updateByPrimaryKey(user);
}
/**
* @return int
* @Description 根据ID删除用户
* @Param [userId] 用户ID
**/
public int deleteUser(int userId) {
return userMapper.deleteByPrimaryKey(userId);
}
/**
* @return User
* @Description 根据用户ID查询用户
* @Param [userId] 用户ID
**/
public User selectByPrimaryKey(int userId) {
return userMapper.selectByPrimaryKey(userId);
}
}
3.5 UserController.java
package com.rabbits.learn.controller;
import com.rabbits.learn.entity.User;
import com.rabbits.learn.service.UserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
/**
* @Description: 用户信息
* @Author: RabbitsInTheGrass_xj
* @Date: 2019/10/1 09:31
*/
@RestController
@RequestMapping("/user")
public class UserController {
private static final Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@GetMapping("/selectAllUser")
@ResponseBody
public Object selectAll(@RequestParam(name = "pageNum", required = false, defaultValue = "1") int pageNum,
@RequestParam(name = "pageSize", required = false, defaultValue = "10") int pageSize) {
logger.info("============查询所有用户=======");
return userService.selectAll(pageNum, pageSize);
}
@PostMapping("/addUser")
@ResponseBody
public int addUser(User user) {
return userService.addUser(user);
}
@PostMapping("/updateUser")
@ResponseBody
public int updateUser(User user) {
return userService.updateUser(user);
}
@PostMapping("/deleteUser")
@ResponseBody
public int deleteUser(User user) {
return userService.deleteUser(user.getId());
}
}
4. 启动类增加注解
package com.rabbits.learn;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Description MybatisApplication 类
* @Author RabbitsInTheGrass_xj
* @Date 2019-10-12 15:36
*/
@SpringBootApplication
@MapperScan("com.rabbits.learn.dao")
public class MybatisApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisApplication.class, args);
}
}
注意:@MapperScan(“com.xj.demo.dao”)对应了项目中mapper所对应的包路径
5. 启动进行测试
6. pagehelper分页
参考资料:
PageHelper:https://pagehelper.github.io/
PageHelper手册:https://pagehelper.github.io/docs/howtouse/
6.1 分页参数配置
- 如果你仍然在用类似ibatis式的命名空间调用方式,你觉得 RowBounds 中的两个参数 offset,limit 不如 pageNum,pageSize 容易理解, 你可以使用 offsetAsPageNum 参数,将该参数设置为 true 后,offset会当成 pageNum 使用,limit 和 pageSize 含义相同。
- 如果觉得某个地方使用分页后,你仍然想通过控制参数查询全部的结果,你可以配置 pageSizeZero 为 true, 配置后,当 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果。
- 如果你分页插件使用于类似分页查看列表式的数据,如新闻列表,软件列表, 你希望用户输入的页数不在合法范围(第一页到最后一页之外)时能够正确的响应到正确的结果页面, 那么你可以配置 reasonable 为 true,这时如果 pageNum<=0 会查询第一页,如果 pageNum>总页数 会查询最后一页。
- 如果你在 Spring 中配置了动态数据源,并且连接不同类型的数据库,这时你可以配置 autoRuntimeDialect 为 true,这样在使用不同数据源时,会使用匹配的分页进行查询。 这种情况下,你还需要特别注意 closeConn 参数,由于获取数据源类型会获取一个数据库连接,所以需要通过这个参数来控制获取连接后,是否关闭该连接。 默认为 true,有些数据库连接关闭后就没法进行后续的数据库操作。而有些数据库连接不关闭就会很快由于连接数用完而导致数据库无响应。所以在使用该功能时,特别需要注意你使用的数据源是否需要关闭数据库连接。
- 如果你仍然在用类似ibatis式的命名空间调用方式,你也许会用到rowBoundsWithCount, 分页插件对RowBounds支持和 MyBatis 默认的方式是一致,默认情况下不会进行 count 查询,如果你想在分页查询时进行 count 查询, 以及使用更强大的 PageInfo 类,你需要设置该参数为 true。
注: PageRowBounds 想要查询总数也需要配置该属性为 true。
当不使用动态数据源而只是自动获取 helperDialect 时,数据库连接只会获取一次,所以不需要担心占用的这一个连接是否会导致数据库出错,但是最好也根据数据源的特性选择是否关闭连接。
6.2 特别注意
6.2.1 PageHelper.startPage 静态方法调用
在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。
- 分页时,实际返回的结果list类型是Page,如果想取出分页信息,需要强制转换为Page或者使用PageInfo类;
- 紧跟着的第一个select方法会被分页,但是后面的不会被分页,除非再次调用PageHelper.startPage;
- 使用参数方式注入分页参数:
想要使用参数方式,需要配置 supportMethodsArguments 参数为 true,同时要配置 params 参数。 例如下面的配置:
在 MyBatis 方法中:<plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置参数,后面会有所有的参数介绍 --> <property name="supportMethodsArguments" value="true"/> <property name="params" value="pageNum=pageNumKey;pageSize=pageSizeKey;"/> </plugin> </plugins>
当调用这个方法时,由于同时发现了 pageNumKey 和 pageSizeKey 参数,这个方法就会被分页。params 提供的几个参数都可以这样使用。List<XXX> selectByPageNumSize(@Param("user") User user,@Param("pageNumKey") int pageNum,@Param("pageSizeKey") int pageSize);
除了上面这种方式外,如果 xxx对象中包含这两个参数值,也可以有下面的方法:
当从 User 中同时发现了 pageNumKey 和 pageSizeKey 参数,这个方法就会被分页。List<Country> selectByPageNumSize(User user);
注意:pageNum 和 pageSize 两个属性同时存在才会触发分页操作,在这个前提下,其他的分页参数才会生效。
6.2.2 PageHelper 的不安全调用
- 使用 RowBounds 和 PageRowBounds 参数方式是极其安全的
- 使用参数方式是极其安全的
- 使用 ISelect 接口调用是极其安全的
PageHelper 方法使用了静态的 ThreadLocal 参数,分页参数和线程是绑定的。
只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。
如果代码在进入 Executor 前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement 时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。
栗子:
PageHelper.startPage(1, 10);
List<XXX> list;
if(param != null){
list = mapper.selectIf(param);
} else {
list = new ArrayList<XXX>();
}
这种情况下由于 param 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。