前言:为什么我们要用 Mybatis、JPA?
场景:在原始社会,有一天你想吃葡萄
- 原始社会,目前什么都没有,只能靠你自己干,需要自己找种子,种葡萄,可能长出来你就不想吃了,说不定中途还遇到什么自然灾害(写BUG了)(手写 SQL 语句时代)
- 工业文明,有一天你在吃葡萄,你发现这样效率太低了,但是这个时候可以去商店直接买葡萄了(有人造出了 Mybatis\JPA 这种东西,手工活进入自动化时代)
- 信息时代,你已经不满足买葡萄来吃了,于是开始利用更新的技术培育口感更佳、且无籽的优良品种普通,并进行了量产,来满足自己的需求(Mybatis Plus 是 Mybatis 的增强工具,就是比 Mybatis 更香了)
总结:
- 提高开发效率,减少出错几率
- 降低代码耦合度,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
-
MyBatis-Plus (opens new window)(简称 MP)是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
-
特点
-
-
无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 框架结构
2. 为什么要更换 MyBatis Plus?
2.1 不同的业务场景举例
- 如果我们使用原始的 JDBC 来连接数据库进行操作,我们需要封装数据库对象及常用的操作数据库的方法,处理返回的数据,以及手动创建表,对于拥有成熟生态环境的Java环境来讲这样很显然很低效的
- 所以项目开始使用 Spring Data JPA,增删改查及常用的方法不用写了,直接继承;数据库表也不用建了,直接自动生成(我们实际更谨慎一点,所以没有开启这个功能)。原来数据库操作代码要写一天,现在一条语句就搞定!但是当项目发展到一定阶段,用户量激增,对数据库的操作不仅限于增删改查,我们需要针对一些关键场景的 SQL 进行优化,JPA 就有点捉襟见肘了。
- 当我们使用 Mybatis Plus,宛如一个黑科技!直接映射返回对象,前台所需要的数据库建个DTO类就行,多表关联的数据也可以一个DTO接收所有数据。根据条件组装各种SQL,简直是爽爆了!
- 因为业务需要更换或使用多数据源的场景:我们现在会使用不同类型的数据库来处理不同场景的数据,比如目前 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 所需配置
- 移除项目中 application.yml 中jpa配置数据库信息
- 移除项目中 JPA 的配置
- 将之前项目创建的DO类移动到 entity 目录,并去掉类名中的DO,增加 MyBatis Plus 需要的注解
-
然后针对每一个实体类,重复上面 3.6、3.7、3.8、3.9 的步骤
-
项目中其他引用的地方也需要调整引用的路径以及调整查询的方法,如果项目之前是一些自定义的 SQL,建议用MyBatis Plus 的构造器重新编写。如果确实要用原来的SQL语句有两种方式:
- 第一种:使用注解实现,注解类型比较简单,在 mapper 层的接口类方法上使用 @Select、@Update、@Insert、@Delete 等注解并加上自定义的 sql 语句,即可代表 查询、更新、存储、删除 等操作。如下图所示:
- 第二种:自定义 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 就完成了。