Шестая серия Spring Boot Spring boot интегрирует mybatis, плагин подкачки pagehelper

Spring Boot MyBatis

1 Обзор

Содержание этой статьи включает следующее:

  1. Spring Boot интегрирует mybatis
  2. Spring Boot интегрирует плагин пейджинга pagehelper для определения связанных классов для пейджинга.
  3. Реализовать класс инструментов: модель для dto, реализовать разделение уровня данных и транспортного уровня.
  4. Полностью показывает весь процесс ввода URL-адреса из браузера и манипулирования данными из базы данных.

2. Spring Boot интегрирует Mybatis

2.1. pom.xml

Связанные банки mybatis и базы данных

<!-- druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.0.31</version>
</dependency>
<!-- Spring Boot集成mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

2.2 Класс Java Mapper, xml и модель

Тестовая таблица sql выглядит следующим образом

CREATE TABLE `test`.`test` (
  `id` INT NOT NULL,
  `age` INT NULL,
  `name` VARCHAR(45) NULL,
  PRIMARY KEY (`id`),
  UNIQUE INDEX `id_UNIQUE` (`id` ASC));

Таблица соответствует классу Model: com.hry.spring.mybatis.model.TestModel

public class TestModel {
    private Integer id;

    private Integer age;

    private String name;
   // set/get略
}

Настройте класс Mapper: com.hry.spring.mybatis.mapper.TestMapper

public interface TestMapper {
    int deleteByPrimaryKey(Integer id);

    int insert(TestModel record);

    int insertSelective(TestModel record);

    TestModel selectByPrimaryKey(Integer id);

    List<TestModel> selectAll();

    int updateByPrimaryKeySelective(TestModel record);

    int updateByPrimaryKey(TestModel record);
}

Настроить Mapper xml: TestMapper.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.hry.spring.mybatis.mapper.TestMapper">
 <resultMap id="BaseResultMap" type="com.hry.spring.mybatis.model.TestModel">
   <id column="id" jdbcType="INTEGER" property="id" />
   <result column="age" jdbcType="INTEGER" property="age" />
   <result column="name" jdbcType="VARCHAR" property="name" />
 </resultMap>
 <sql id="Base_Column_List">
   id, age, name
 </sql>
 <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
   select 
   <include refid="Base_Column_List" />
   from test
   where id = #{id,jdbcType=INTEGER}
 </select>
 <select id="selectAll"  resultMap="BaseResultMap">
   select
   <include refid="Base_Column_List" />
   from test
     order by id desc
 </select>

 <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
   delete from test
   where id = #{id,jdbcType=INTEGER}
 </delete>
 <insert id="insert" parameterType="com.hry.spring.mybatis.model.TestModel">
   insert into test (id, age, name, 
     )
   values (#{id,jdbcType=INTEGER}, #{age,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR}
    )
 </insert>
 <insert id="insertSelective" parameterType="com.hry.spring.mybatis.model.TestModel">
   insert into test
   <trim prefix="(" suffix=")" suffixOverrides=",">
     <if test="id != null">
       id,
     </if>
     <if test="age != null">
       age,
     </if>
     <if test="name != null">
       name,
     </if>

   </trim>
   <trim prefix="values (" suffix=")" suffixOverrides=",">
     <if test="id != null">
       #{id,jdbcType=INTEGER},
     </if>
     <if test="age != null">
       #{age,jdbcType=INTEGER},
     </if>
     <if test="name != null">
       #{name,jdbcType=VARCHAR},
     </if>

   </trim>
 </insert>
 <update id="updateByPrimaryKeySelective" parameterType="com.hry.spring.mybatis.model.TestModel">
   update test
   <set>
     <if test="age != null">
       age = #{age,jdbcType=INTEGER},
     </if>
     <if test="name != null">
       name = #{name,jdbcType=VARCHAR},
     </if>
   </set>
   where id = #{id,jdbcType=INTEGER}
 </update>
 <update id="updateByPrimaryKey" parameterType="com.hry.spring.mybatis.model.TestModel">
   update test
   set age = #{age,jdbcType=INTEGER},
     name = #{name,jdbcType=VARCHAR}
   where id = #{id,jdbcType=INTEGER}
 </update>
</mapper>

2.3 конфигурация mybatis

spring_mybatis.xml
Значение spring.datasource.url настраивается в application.yml

<!-- ### 配置数据源 begin ###-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> 
  <!-- 基本属性 url、user、password -->
  <property name="url" value="${spring.datasource.url}" />
  <property name="username" value="${spring.datasource.username}" />
  <property name="password" value="${spring.datasource.password}" />
  <property name="driverClassName" value="${spring.datasource.driverClassName}" />


  <!-- 配置初始化大小、最小、最大 -->
  <property name="initialSize" value="${spring.datasource.initialSize:5}" />
  <property name="minIdle" value="${spring.datasource.minIdle:5}" /> 
  <property name="maxActive" value="${spring.datasource.maxActive:20}" />

  <!-- 配置获取连接等待超时的时间 -->
  <property name="maxWait" value="${spring.datasource.maxWait:30000}" />

  <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
  <property name="timeBetweenEvictionRunsMillis" value="${spring.datasource.timeBetweenEvictionRunsMillis}" />

  <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
  <property name="minEvictableIdleTimeMillis" value="${spring.datasource.minEvictableIdleTimeMillis}" />

  <property name="validationQuery" value="${spring.datasource.validationQuery}" />
  <property name="testWhileIdle" value="${spring.datasource.testWhileIdle}" />
  <property name="testOnBorrow" value="${spring.datasource.testOnBorrow}" />
  <property name="testOnReturn" value="${spring.datasource.testOnReturn}" />

  <!-- 打开PSCache,并且指定每个连接上PSCache的大小 -->
  <property name="poolPreparedStatements" value="${spring.datasource.poolPreparedStatements}" />
  <property name="maxPoolPreparedStatementPerConnectionSize" value="${spring.datasource.maxPoolPreparedStatementPerConnectionSize}" />

  <!-- 配置监控统计拦截的filters -->
  <property name="filters" value="${spring.datasource.filters}" />

  <property name="connectionProperties" value="{spring.datasource.connectionProperties}" />
</bean>
<!-- ### 配置数据源  end ###-->

<!-- ### Mybatis和事务配置  begin ###-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <!-- 配置扫描Mapper XML的位置 -->
    <property name="mapperLocations" value="classpath:com/hry/spring/mybatis/mapper/*.xml"/>
    <!-- 配置mybatis配置文件的位置 -->
    <property name="configLocation" value="classpath:config/spring/mybatis_config.xml"/>

</bean>

<!-- 配置扫描Mapper接口的包路径 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.hry.spring.mybatis.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>

<!-- 事务配置 -->
<bean id="transactionManager"
      class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
      p:dataSource-ref="dataSource"/>

<tx:advice id="txAdvice" transaction-manager="transactionManager" >
    <tx:attributes>
        <tx:method name="add*" propagation="REQUIRED" />
    <tx:method name="create*" propagation="REQUIRED" />
    <tx:method name="save*" propagation="REQUIRED" />
        <tx:method name="insert*" propagation="REQUIRED" />
    <tx:method name="update*" propagation="REQUIRED" />
    <tx:method name="batch*" propagation="REQUIRED" />
    <tx:method name="del*" propagation="REQUIRED" />
    <tx:method name="get*" propagation="SUPPORTS" read-only="true" />
    <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
        <tx:method name="*" read-only="true"/>
    </tx:attributes>
</tx:advice>
<aop:config >
    <aop:pointcut id="pt" expression="execution(* com.hry.spring.mybatis.service..*.*(..))" />
    <aop:advisor pointcut-ref="pt" advice-ref="txAdvice"/>
</aop:config> 
<!-- ### Mybatis和事物配置  end ###-->

mybatis_config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

</configuration>

2.4. application.yml

Информация для настройки базы данных следующая:

# 数据库配置
spring:
  datasource:
    #### Datasource 配置 ####
    type: com.alibaba.druid.pool.DruidDataSource
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1:3306/test?zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2b8&useSSL=true
    # url: jdbc:mysql://127.0.0.1:3306/test
    driverClassName: com.mysql.cj.jdbc.Driver
    # driverClassName: oracle.jdbc.driver.OracleDriver
    # 下面为连接池的补充设置,应用到上面所有数据源中# 初始化大小,最小,最大
    initialSize: 5
    minIdle: 5
    maxActive: 20
    # 配置获取连接等待超时的时间
    maxWait: 30000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
    timeBetweenEvictionRunsMillis: 60000
    validationQuery: SELECT 1 FROM DUAL
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: false
    maxPoolPreparedStatementPerConnectionSize: 20
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: log4j
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000

2.5 Spring boot интегрирует mybatis

Загрузите конфигурацию mybatis через @ImportResource. Метод fastJsonHttpMessageConverters, аннотированный @Bean, означает использование fastjson для разбора json

@SpringBootApplication
// 加载mybatis配置
@ImportResource({"classpath:config/spring/spring_*.xml"})
public class MybatisSpringBoot {
    public static void main(String[] args){
        SpringApplication.run(MybatisSpringBoot.class, args);
    }

    @Bean
    public HttpMessageConverters fastJsonHttpMessageConverters() {
        // 格式化时间
        SerializeConfig mapping = new SerializeConfig();
        mapping.put(Date.class, new SimpleDateFormatSerializer(
               "yyyy-MM-dd HH:mm:ss"));

        FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
        FastJsonConfig fastJsonConfig = new FastJsonConfig();
        //  fastJsonConfig.setSerializerFeatures(SerializerFeature.PrettyFormat);
        fastJsonConfig.setSerializeConfig(mapping);
        fastConverter.setFastJsonConfig(fastJsonConfig);
        HttpMessageConverter<?> converter = fastConverter;
        return new HttpMessageConverters(converter);
    }

}

3. Плагин интегрированного дистрибутива Spring Boot Pagehelper

Вышеуказанные функции реализуют простое приложение mybatis. Но когда дело доходит до запросов к базе данных, использование пейджинга неизбежно. Здесь я рекомендую плагин pagehelper для реализации функции пейджинга.

3.1. pom.xml

Импорт связанных банок

<!-- 分布插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.1</version>
</dependency>
<!-- Spring Boot集成 pagehelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.1</version>
</dependency>

3.2 Вспомогательные классы, связанные с пейджинговой связью

MyPage
Упаковка возвращает информацию на стойку регистрации, включая код состояния этого запроса, сообщение об ошибке, общее количество записей и список данных.

public class MyPage<T> {
    @JSONField(ordinal = 1)
    private Integer code = 200;// 状态码,默认状态

    @JSONField(ordinal = 2)
    private String message = "";// 提示消息或者错误消息

    @JSONField(ordinal = 3)
    private String apiId = "";// 请求的唯一标识,预留

    @JSONField(ordinal = 4)
    private Integer totalCount = 0;//记录总数

    @JSONField(ordinal = 5)
    private List<T> rows = Collections.emptyList();//本次返回的数据列表
   // set/get略
}

IPageHelperPageCallBack
Пользовательский метод обратного вызова на заказ, если пейджинг, вы должны использовать Mapper в этом методе.
Этот интерфейс используется в качестве параметра PageCallBackUtil ниже.

public interface IPageHelperPageCallBack {
    <T> List<T> select();
}

PageCallBackUtil
Суть pagehelper заключается в следующем: вызовите метод PageHelper.startPage(pageNum, pageSize,requireTotalCount), чтобы установить начальный адрес записи запроса, а затем сразу же вызовите метод класса сопоставления, чтобы получить возвращаемый список List, используйте ( PageInfo pageInfo = new PageInfo(list)) Оберните PageInfo списка, PageInfo содержит информацию об этом запросе, включая общее количество этого запроса, а затем преобразуйте модель в класс Dto.

/**
 * 分页的回调函数
 * Created by huangrongyou@yixin.im on 2017/9/6.
 */
public class PageCallBackUtil {

    /**
     * 封装公共PageHelper的操作
     * @param qry
     * @param callBack
     * @param <T>
     * @return
     */
    public static<T>  MyPage<T> selectRtnPage(AbstractQry qry, IPageHelperPageCallBack callBack){
        Assert.notNull(qry, "qry can't be null!");
        Assert.notNull(callBack, "callBack cant' be null!");
        setPageHelperStartPage(qry);

        List<T> list = callBack.select();
        // 分页时,实际返回的结果list类型是Page<E>
        if(!(list instanceof Page)){
            throw new RuntimeException("list must be 'com.github.pagehelper.Page', now is " + list.getClass().getCanonicalName());
        }
        MyPage<T> myPage = new MyPage<T>();
        PageInfo<T> pageInfo = new PageInfo<T>(list);
        myPage.setTotalCount((int) pageInfo.getTotal());
        myPage.setRows(pageInfo.getList());
        return myPage;
    }

    /**
     * 设置PageHelper的startPage
     * @param qry
     */
    private static void setPageHelperStartPage(AbstractQry qry) {
        // 设置分页信息
        // pageNum
        Integer pageNum = qry.getPageNum();
        pageNum = pageNum == null? AbstractQry.DEFAULT_PAGENUM : pageNum;
        // pageSize
        Integer pageSize = qry.getPageSize();
        pageSize = pageSize == null ? AbstractQry.DEFAULT_PAGESIZE : pageSize;
        // requireTotalCount
        Boolean requireTotalCount = qry.getRequireTotalCount();
        requireTotalCount = requireTotalCount == null ? AbstractQry.DEFAULT_REQUIRETOTALCOUNT : requireTotalCount;
        PageHelper.startPage(pageNum, pageSize,requireTotalCount);
    }
}

Qry
Базовый класс для условий запроса

public interface Qry {
    String getId();
    void setId(String id);
}

AbstractQry
Информация о номере страницы запроса, который определяет нумерацию страниц.

public class AbstractQry implements Qry {
    public static final int DEFAULT_PAGENUM = 1;
    public static final int DEFAULT_PAGESIZE = 1;
    public static final boolean DEFAULT_REQUIRETOTALCOUNT = false;

    private String id;
    private Integer pageNum = 1;// 第几页,首页为1
    private Integer pageSize = 10;// 每页记录条数
    private Boolean requireTotalCount = Boolean.FALSE;// 是否需要记录总数
    // set/get略
}

TestQry
Этот класс используется для определения конкретных условий запроса.

public class TestQry extends AbstractQry{

}

3.3 Сервисный уровень: ITestService и TestServiceImpl

ITestService

public interface ITestService {
    int deleteByPrimaryKey(Integer id);

    int insertSelective(TestModel record);

    TestModel selectByPrimaryKey(Integer id);

    List<TestModel> selectAll(TestQry qry);

    MyPage<TestModel> selectAllWithPage(TestQry qry);
}

TestServiceImpl
selectAllWithPage определяет использование метода

@Service
@Primary
public class TestServiceImpl implements ITestService{
    @Autowired
    private TestMapper testMapper;

    @Override
    public int deleteByPrimaryKey(Integer id) {
        Assert.notNull(id, "id can't be null!");
        return testMapper.deleteByPrimaryKey(id);
    }

    @Override
    public MyPage<TestModel> selectAllWithPage(TestQry qry) {
        if(qry == null){
            qry = new TestQry();
        }
        MyPage<TestModel> myPage = PageCallBackUtil.selectRtnPage(qry, new IPageHelperPageCallBack() {
            @Override
            public List<TestModel> select() {
                return  testMapper.selectAll();
            }
        });
        return myPage;
    }

    … 其他方法略

}

3.4 Уровень управления

класс TestCtl

@RestController
@RequestMapping(value = "/simple")
@EnableSwagger2
public class TestCtl {
    @Autowired
    private ITestService testService;

    @RequestMapping(value = "delete-by-primary-key/{id}", method = RequestMethod.GET)
    public int deleteByPrimaryKey( @PathVariable("id") Integer id){
        // 参数验证略
        return testService.deleteByPrimaryKey(id);
    }

    @RequestMapping(value = "insert-selective", method = RequestMethod.POST)
    public int insertSelective(@RequestBody TestDto dto){
        // 参数验证略
        TestModel record = new TestModel();
        record.setId(dto.getId());
        record.setAge(dto.getAge());
        record.setName(dto.getName());
        return testService.insertSelective(record);
    }

    @RequestMapping(value = "select-by-primary-key/{id}", method = RequestMethod.POST)
    public TestDto selectByPrimaryKey(@PathVariable("id") String id){
        // 参数验证略
        return Model2DtoUtil.model2Dto(testService.selectByPrimaryKey(Integer.parseInt(id)), TestDto.class);
    }

    @RequestMapping(value = "select-all", method = {RequestMethod.POST })
    public List<TestDto> selectAll(@RequestBody TestQry qry){
        return Model2DtoUtil.model2Dto(testService.selectAll(qry), TestDto.class);
    }

    @RequestMapping(value = "select-all-with-page", method = {RequestMethod.POST })
    public MyPage<TestDto> selectAllWithPage(@RequestBody TestQry qry){
        MyPage<TestDto> page = Model2DtoUtil.model2Dto(testService.selectAllWithPage(qry), TestDto.class);
        page.setMessage(getLocalInfo());
        return page;
    }

    private String getLocalInfo(){
        StringBuilder sb = new StringBuilder();
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            sb.append("server info :")
                    .append("[ip:").append(inetAddress.getHostAddress()).append(",hostname:").append(inetAddress.getHostName())
                    .append("]");
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }
}

3.5 Класс инструмента для преобразования модели в класс dto

Model2DtoUtil
Класс инструмента, инкапсулирует и преобразует модель в класс dto для реализации разделения уровня данных и транспортного уровня.

public class Model2DtoUtil {

    /**
     * 将 MyPage<model> 修改为  MyPage<Dto>
     *
     * @param sourcePage
     * @param cls
     * @param <T>
     * @param <K>
     * @return
     */
    public static <T, K> MyPage<K> model2Dto(MyPage<T> sourcePage, Class<K> cls) {
        if(sourcePage == null){
            return null;
        }
        Assert.notNull(cls, "cls can't be null!");

        MyPage<K> dstPage = new MyPage<K>();
        dstPage.setTotalCount(sourcePage.getTotalCount());
        dstPage.setApiId(sourcePage.getApiId());
        dstPage.setMessage(sourcePage.getMessage());
        dstPage.setCode(sourcePage.getCode());

        // list
        List<T> sourceList = sourcePage.getRows();
        List<K> dstList = new ArrayList<K>();

        dealListModel2Dto(sourceList, cls, dstList);
        dstPage.setRows(dstList);
        return dstPage;
    }
    private static <T, K> void dealListModel2Dto(List<T> sourceList, Class<K> cls, List<K> dstList) {
        for (T source : sourceList) {
            try {
                K dst = cls.newInstance();
                CommonBeanUtils.copyProperties(source, dst);
                dstList.add(dst);
            } catch (InstantiationException e) {
                e.printStackTrace();
                throw new BeanCreationException(e.getMessage());
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                throw new BeanCreationException(e.getMessage());
            }
        }
    }

}

CommonBeanUtils
Banutils Tool Class:

public class CommonBeanUtils {
    public static void copyProperties(Object source, Object target){
        BeanUtils.copyProperties(source, target);
    }
}

3.6. application.yml

Связанная конфигурация страницы, другие параметры конфигурации, см.Официальный сайт

Если следующие параметры не настроены, механизм пейджинга не будет работать

# 分页配置: https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
pagehelper:
    helperDialect: oracle
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql

4. Spring Boot объединяет чванство и тестирование

Для удобства тестирования вводим swagger

4.1. pom.xml

<!-- swagger2 -->
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.7.0</version>
</dependency>

4.2. @EnableSwagger2

Добавьте @EnableSwagger2 в класс TestCtl

@RestController
@RequestMapping(value = "/simple")
@EnableSwagger2
public class TestCtl {
    …
}

4.3 Тестирование

Открыть адрес:http://127.0.0.1:8080/swagger-ui.html
Вы можете увидеть доступные в настоящее время интерфейсы URL
这里写图片描述
Протестируйте функцию подкачки интерфейса /simple/select-all-with-page и установите функцию запроса
这里写图片描述
Результат возврата следующий

这里写图片描述

5. Код

Подробный код выше см.код github, пожалуйста, используйте тег v0.2 как можно чаще, не используйте мастер, потому что мастер всегда меняется, нет гарантии, что код в статье и код на github всегда будут одинаковыми