Spring Data JPA 更换 MyBatis Plus

前言:为什么我们要用 Mybatis、JPA?

场景:在原始社会,有一天你想吃葡萄

  1. 原始社会,目前什么都没有,只能靠你自己干,需要自己找种子,种葡萄,可能长出来你就不想吃了,说不定中途还遇到什么自然灾害(写BUG了)(手写 SQL 语句时代)
  2. 工业文明,有一天你在吃葡萄,你发现这样效率太低了,但是这个时候可以去商店直接买葡萄了(有人造出了 Mybatis\JPA 这种东西,手工活进入自动化时代)
  3. 信息时代,你已经不满足买葡萄来吃了,于是开始利用更新的技术培育口感更佳、且无籽的优良品种普通,并进行了量产,来满足自己的需求(Mybatis Plus 是 Mybatis 的增强工具,就是比 Mybatis 更香了)

总结:

  1. 提高开发效率,减少出错几率
  2. 降低代码耦合度,sql 和 java 编码分开,功能边界清晰,一个专注业务、一个专注数据。

1. Spring Data Jpa 与 MyBatis plus 介绍

  • 1.1 Spring Data Jpa

    • JPA (Java Persistence API) 是 Sun 官方提出的 Java 持久化规范。它为 Java 开发人员提供了一种对象/关联映射工具来管理 Java 应用中的关系数据。他的出现主要是为了简化现有的持久化开发工作和整合 ORM 技术,结束现在 Hibernate,TopLink,JDO 等 ORM 框架各自为营的凌乱局面。JPA 在充分吸收了现有 Hibernate,TopLink,JDO 等ORM框架的基础上发展而来的,具有易于使用,伸缩性强等优点。从上面的解释中我们可以了解到JPA 是一套规范,而类似 Hibernate,TopLink,JDO 这些产品是实现了 JPA 规范。
    • Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率。
  • 1.2 MyBatis plus

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
  • 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
  • 1.3 MyBatis Plus 支持数据库

    • MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
  • 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
  • 1.4 MyBatis Plus 框架结构

1.png

2. 为什么要更换 MyBatis Plus?

2.1 不同的业务场景举例

  1. 如果我们使用原始的 JDBC 来连接数据库进行操作,我们需要封装数据库对象及常用的操作数据库的方法,处理返回的数据,以及手动创建表,对于拥有成熟生态环境的Java环境来讲这样很显然很低效的
  2. 所以项目开始使用 Spring Data JPA,增删改查及常用的方法不用写了,直接继承;数据库表也不用建了,直接自动生成(我们实际更谨慎一点,所以没有开启这个功能)。原来数据库操作代码要写一天,现在一条语句就搞定!但是当项目发展到一定阶段,用户量激增,对数据库的操作不仅限于增删改查,我们需要针对一些关键场景的 SQL 进行优化,JPA 就有点捉襟见肘了。
  3. 当我们使用 Mybatis Plus,宛如一个黑科技!直接映射返回对象,前台所需要的数据库建个DTO类就行,多表关联的数据也可以一个DTO接收所有数据。根据条件组装各种SQL,简直是爽爆了!
  4. 因为业务需要更换或使用多数据源的场景:我们现在会使用不同类型的数据库来处理不同场景的数据,比如目前 MySQL 就对人群分群的需求支持的不太好,我们现在需要换成 clickhouse 数据库来做人群分群,如果之前使用的是 JPA 操作 MySql 做的人群分群,配置下数据源即可完成。但是,如果使用 mybatis 并且是纯手写 SQL,依赖于数据库。不便于换数据源。你想起了不依赖数据源的 jpa,并且可以支持多个数据源,并且不需要改查询,换个配置全搞定。

2.2 总结

  • Jpa 是面向对象的思想,一个对象就是一个表,强化的是你对这个表的控制。Jpa 继承的那么多表约束注解也证明了jpa对这个数据库对象控制很注重。
  • Mybatis 则是面向 SQL,你的结果完全来源于 SQL,而对象这个东西只是用来接收sql带来的结果集。你的一切操作都是围绕 SQL,包括动态根据条件决定sql语句等。Mybatis 并不那么注重对象的概念。只要能接收到数据就好。(阿里大厂也在用,面向 sql 好做优化更容易调优)

2.3 现在的 Mybatis Plus

Mybatis Plus 弥补了 Mybatis 的不足,的确是名副其实的 Plus,他很强大 。对于之前 JPA 与 Mybatis 都有各自比较适合的场景。但是目前来讲 Mybatis Plus 的出现,很好的弥补了之前 Mybatis 所有 SQL 全部需要手写的条件,并且可以自动解析实体关系映射转换为MyBatis内部对象注入容器,支持强大的条件构造器,并且也可以直接手写 SQL 语句进行性能的优化。所以目前建议直接使用 Mybatis Plus。

3. 项目更换 MyBatis Plus 过程

3.1 修改 pom 文件,Maven 引入MyBatis-Plus 依赖

<!-- mybatis plus -->

<dependency>

<groupId>io.github.Caratacus</groupId>

<artifactId>mybatis-plus-boot-starter</artifactId>

<version>1.0.9</version>

</dependency>

<dependency>

<groupId>com.baomidou</groupId>

<artifactId>mybatis-plus-generator</artifactId>

<version>3.2.0</version>

</dependency>

3.2 Application.yml 配置文件如下所示:

spring:

datasource:

dynamic:

primary: mysql-push *# 默认*

**datasource:

mysql-push:

driver-class-name: com.mysql.jdbc.Driver

url: jdbc:mysql://[xxxxxxxx.mysql.rds.aliyuncs.com:3306/xxxxx?useSSL=false&useUnicode=true&autoReconnect=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull](http://xxxxxxxx.mysql.rds.aliyuncs.com:3306/xxxxx?useSSL=false&useUnicode=true&autoReconnect=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull)

username: xxxxxx

password: xxxxxx

connection-init-sql: "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_unicode_ci'"

connection-test-query: SELECT 1

connection-timeout: 5000

idle-timeout: 180000

minimum-idle: 10

maximum-pool-size: 100

validation-timeout: 5000

clickhouse-push:

url: jdbc:clickhouse://xxxxxx.ads.rds.aliyuncs.com:8123/push_services

driver-class-name: ru.yandex.clickhouse.ClickHouseDriver

username: xxxxxx

password: xxxxxx

maxActive: 100

minIdle: 10

maxWait: 6000

maxQuerySizeBytes: 100000000

socketTimeoutSeconds: 600

queryTimeoutSeconds: 120

connectionTimeoutMillis: 10000

timeToLiveMillis: 60000

ssl: false

compress: false

clickhouse-user:

url: jdbc:clickhouse://xxxxxxxx.ads.rds.aliyuncs.com:8123/user_db

driverClassName: ru.yandex.clickhouse.ClickHouseDriver

username: xxxxxx

password: xxxxxx

maxActive: 100

minIdle: 10

maxWait: 6000

maxQuerySizeBytes: 100000000

socketTimeoutSeconds: 600

queryTimeoutSeconds: 120

connectionTimeoutMillis: 10000

timeToLiveMillis: 60000

ssl: false

compress: false

*# mybatis 配置*

mybatis-plus:

mapper-locations: classpath:mapper/**/*.xml

*#实体扫描,多个package用逗号或者分号分隔*

**typeAliasesPackage: cn.yizhoucp.push.api.project.entity.clickhousePushServices,;cn.yizhoucp.push.api.project.entity.clickhouseUserServices,;cn.yizhoucp.push.api.project.entity.mysqlPush

check-config-location: true

type-enums-package: cn.yizhoucp.ms.core.base.enums.push,;cn.yizhoucp.push.api.project.biz.enums

configuration:

default-enum-type-handler: org.apache.ibatis.type.EnumOrdinalTypeHandler

*#是否开启自动驼峰命名规则(camel case)映射*

**map-underscore-to-camel-case: true

*#全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存*

**cache-enabled: false

call-setters-on-nulls: true

*#配置JdbcTypeForNull, oracle数据库必须配置*

**jdbc-type-for-null: '*null*'

*#MyBatis 自动映射时未知列或未知属性处理策略 NONE:不做任何处理 (默认值), WARNING:以日志的形式打印相关警告信息, FAILING:当作映射失败处理,并抛出异常和详细信息*

**auto-mapping-unknown-column-behavior: *warning*

**global-config:

banner: false

db-config:

*#主键类型 0:"数据库ID自增", 1:"未设置主键类型",2:"用户输入ID (该类型可以通过自己注册自动填充插件进行填充)", 3:"全局唯一ID (idWorker), 4:全局唯一ID (UUID), 5:字符串全局唯一ID (idWorker 的字符串表示)";*

**id-type: *UUID*

*#字段验证策略 IGNORED:"忽略判断", NOT_NULL:"非NULL判断", NOT_EMPTY:"非空判断", DEFAULT 默认的,一般只用于注解里(1. 在全局里代表 NOT_NULL,2. 在注解里代表 跟随全局)*

**field-strategy: NOT_EMPTY

*#数据库大写下划线转换*

**capital-mode: true

*#逻辑删除值*

**logic-delete-value: 0

*#逻辑未删除值*

**logic-not-delete-value: 1

3.3 增加 MyBatis 配置,支持分页

package cn.yizhoucp.push.api.common.base.config.mybatis;

import com.baomidou.mybatisplus.annotation.DbType;

import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;

import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;

import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

@ConditionalOnClass(value = {PaginationInterceptor.class})

public class MybatisPlusConfig {

*/***

** 分页配置*

***

*** ***@return***

********/*

**@Bean

public MybatisPlusInterceptor mybatisPlusInterceptor() {

MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();

interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.*H2*));

return interceptor;

}

}

3.4 插入时自动更新,创建时间、更新时间

package cn.yizhoucp.push.api.project.biz.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;

import org.apache.ibatis.reflection.MetaObject;

import org.springframework.stereotype.Component;

import java.util.Date;

@Component

public class MyMetaObjectHandler implements MetaObjectHandler {

@Override

public void insertFill(MetaObject metaObject) {

this.strictInsertFill(metaObject, "createTime", Date.class, new Date());

this.strictInsertFill(metaObject, "updateTime", Date.class, new Date());

}

@Override

public void updateFill(MetaObject metaObject) {

this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());

}

}

3.5 创建实体类

package cn.yizhoucp.push.api.project.entity.mysqlPush;

import cn.yizhoucp.ms.core.base.enums.push.PushChannelTypeEnum;

import cn.yizhoucp.ms.core.base.enums.push.PushSceneType;

import cn.yizhoucp.push.api.common.core.entity.BaseEntity;

import com.baomidou.mybatisplus.annotation.*;

import lombok.Data;

*/***

** push config DO*

***

*** ***@author*** *hash*

**/*

@Data

@TableName("push_config")

public class PushConfig extends BaseEntity {

*/***

** id*

**/*

**@TableId(value = "id", type = IdType.*AUTO*)

private Long id;

*/***

** App ID*

**/*

**@TableField("app_id")

private Long appId;

*/***

** 分组描述*

**/*

**@TableField("push_name")

private String pushName;

*/***

** push 场景*

**/*

**@EnumValue

@TableField("push_scene")

private PushSceneType pushScene;

*/***

** 推送渠道类型*

**/*

**@EnumValue

@TableField("push_channel_type")

private PushChannelTypeEnum pushChannelType;

*/***

** 分组用户人数*

**/*

**@TableField("daily_limit")

private Integer dailyLimit;

*/***

** 记录状态*

**/*

**@TableField("content")

private String content;

}

3.6 创建 Mapper,由于配置的多数据源需要用 @DS()指定操作的数据源

package cn.yizhoucp.push.api.project.mapper.mysqlPush;

import cn.yizhoucp.push.api.project.entity.mysqlPush.PushConfig;

import com.baomidou.dynamic.datasource.annotation.DS;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import org.apache.ibatis.annotations.Mapper;

@Mapper

@DS("mysql-push")

public interface PushConfigMapper extends BaseMapper<PushConfig> {

}

3.7 创建 Service

package cn.yizhoucp.push.api.project.services.mysqlPush;

import com.baomidou.dynamic.datasource.annotation.DS;

public interface PushConfigService {

}

3.8 创建 Impl

package cn.yizhoucp.push.api.project.services.mysqlPush.impl;

import cn.yizhoucp.push.api.project.entity.mysqlPush.PushConfig;

import cn.yizhoucp.push.api.project.mapper.clickhouseUserDb.ViewUserInfoMapper;

import cn.yizhoucp.push.api.project.mapper.mysqlPush.PushConfigMapper;

import cn.yizhoucp.push.api.project.services.mysqlPush.PushConfigService;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import lombok.AllArgsConstructor;

import org.springframework.stereotype.Service;

@Service

@AllArgsConstructor

public class PushConfigImpl extends ServiceImpl<PushConfigMapper, PushConfig> implements PushConfigService {

private ViewUserInfoMapper viewUserInfoMapper;

}

3.9 引入完成,用测试用例测试下

package cn.yizhoucp.push.api.project.biz.manager;

import cn.yizhoucp.push.api.PushServiceApplication;

import cn.yizhoucp.push.api.project.entity.mysqlPush.PushConfig;

import cn.yizhoucp.push.api.project.services.mysqlPush.impl.PushConfigImpl;

import org.junit.jupiter.api.Test;

import org.junit.runner.RunWith;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.boot.test.context.SpringBootTest;

import org.springframework.test.annotation.Rollback;

import org.springframework.test.context.ActiveProfiles;

import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@RunWith(SpringRunner.class) // 基于Spring的Junit测试单元

@ActiveProfiles("local") // 运行环境注解

@Rollback // 回滚

@SpringBootTest(classes = PushServiceApplication.class) // 标注为测试

class PushConfigManagerTest {

@Autowired

PushConfigImpl pushConfigImpl;

@Test

void testPushConfig() {

List<PushConfig> list = pushConfigImpl.list();

}

}

3.10 将项目中的 JPA 相关配置移除及转为 MyBatis Plus 所需配置

  1. 移除项目中 application.yml 中jpa配置数据库信息

  1. 移除项目中 JPA 的配置

  1. 将之前项目创建的DO类移动到 entity 目录,并去掉类名中的DO,增加 MyBatis Plus 需要的注解

  1. 然后针对每一个实体类,重复上面 3.6、3.7、3.8、3.9 的步骤

  2. 项目中其他引用的地方也需要调整引用的路径以及调整查询的方法,如果项目之前是一些自定义的 SQL,建议用MyBatis Plus 的构造器重新编写。如果确实要用原来的SQL语句有两种方式:

    1. 第一种:使用注解实现,注解类型比较简单,在 mapper 层的接口类方法上使用 @Select、@Update、@Insert、@Delete 等注解并加上自定义的 sql 语句,即可代表 查询、更新、存储、删除 等操作。如下图所示:

  1. 第二种:自定义 xml 类型,由于配置文件内 mybatis-plus.mapper-locations 定义的 xml 文件路径是:classpath:/mapper/*Mapper.xml 。

所以需要先创建 resources/mapper 目录,在这里面创建 xxxMapper.xml ,来自定义 sql 语句。由于项目配置了多个数据源,所以创建了多个目录,如下图:

  • select – 映射查询语句
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • 下面配置为查询语句
<?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="cn.yizhoucp.push.api.project.mapper.clickhouseUserDb.ViewUserInfoMapper">

<resultMap id="BaseResultMap" type="cn.yizhoucp.push.api.project.entity.clickhouseUserDb.ViewUserInfo">

<result column="app_id" jdbcType="BIGINT" property="appId"/>

<result column="uid" jdbcType="BIGINT" property="uid"/>

<result column="vest_channel" jdbcType="VARCHAR" property="vestChannel"/>

<result column="platform" jdbcType="VARCHAR" property="platform"/>

<result column="cellphone" jdbcType="VARCHAR" property="cellPhone"/>

<result column="country_code" jdbcType="VARCHAR" property="countryCode"/>

<result column="name" jdbcType="VARCHAR" property="name"/>

<result column="sex" jdbcType="VARCHAR" property="sex"/>

<result column="age" jdbcType="VARCHAR" property="age"/>

<result column="county" jdbcType="VARCHAR" property="county"/>

<result column="province" jdbcType="VARCHAR" property="province"/>

<result column="city" jdbcType="VARCHAR" property="city"/>

<result column="ctime" jdbcType="DATE" property="ctime"/>

<result column="last_active_time" jdbcType="DATE" property="lastActiveTime"/>

</resultMap>

<sql id="Base_Column_List">app_id, uid, vest_channel, platform, cellphone, country_code, name, sex, age, county, province, city, ctime, last_active_time</sql>

<select id="selectUser" resultMap="BaseResultMap">

<![CDATA[

SELECT app_id, uid, sex

FROM user_db.view_user_info

where app_id = #{appId}

and uid = #{uid}

group by app_id, uid, sex

]]>

</select>

</mapper>

4. 总结

到此 Spring Cloud 引入 Mybatis Plus 就完成了。