这一篇从一个入门的基本体验介绍,再到对于 CRUD 的一个详细介绍,在介绍过程中将涉及到的一些问题,例如逐渐策略,自动填充,乐观锁等内容说了一下,只选了一些重要的内容,还有一些没提及到,具体可以参考官网,简单的看完,其实会发现,如果遇到单表的 CRUD ,直接用 MP 肯定舒服,如果写多表,还是用 Mybatis 多点,毕竟直接写 SQL 会直观一点,MP 给我的感觉,就是方法封装了很多,还有一些算比较是用的插件,但是可读性会稍微差一点,不过个人有个人的看法哇,祝大家国庆快乐 ~

一 引言

最初的 JDBC,我们需要写大量的代码来完成与基本的 CRUD ,或许会在一定程度上使用 Spring 的 JdbcTemplate 或者 Apache 的 DBUtils ,这样一些对 JDBC 的简单封装的工具类。

再到后再使用 Mybatis 等一些优秀的持久层框架,大大的简化了开发,我们只需要使用一定的 XML 或者注解就可以完成原来的工作

JDBC --> Mybatis 无疑简化了开发者的工作,而今天我们所讲额 MyBatis-Plus 就是在 MyBatis 的基础上,更加的简化开发,来一起看看吧!

二 初识 MyBatis-Plus

下列介绍来自官网:

(一) 概述

MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

我们的愿景是成为 MyBatis 最好的搭档,就像魂斗罗中的 1P、2P,基友搭配,效率翻倍。

总之一句话:MyBatis-Plus —— 为简化开发而生

(二) 特性

  • 无侵入:只做增强不做改变,引入它 不会 对现有工程产生影响,如丝般顺滑
  • 损耗小:启动即会自动注入基本 CRUD,性能基本无损耗,直接面向对象操作
  • 强大的 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 操作智能分析阻断,也可自定义拦截规则,预防误操作

(三) 支持数据库

  • mysql 、mariadb 、oracle 、db2 、h2 、hsql 、sqlite 、postgresql 、sqlserver 、presto 、Gauss 、Firebird
  • Phoenix 、clickhouse 、Sybase ASE 、 OceanBase 、达梦数据库 、虚谷数据库 、人大金仓数据库 、南大通用数据库

三 入门初体验

按照官网的案例简单试一下 ,注:官网是基于 Springboot 的示例

@Repository
public interface UserMapper extends BaseMapper<User> { }

(一) 创建入门案例表

@Repository
public interface UserMapper extends BaseMapper<User> { }

自行创建一个数据库即可,然后导入官网给出的案例表,然后插入如下数据

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '姓名',
`age` int(11) NULL DEFAULT NULL COMMENT '年龄',
`email` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`) USING BTREE
); -- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'Jone', 18, 'test1@baomidou.com');
INSERT INTO `user` VALUES (2, 'Jack', 20, 'test2@baomidou.com');
INSERT INTO `user` VALUES (3, 'Tom', 28, 'test3@baomidou.com');
INSERT INTO `user` VALUES (4, 'Sandy', 21, 'test4@baomidou.com');
INSERT INTO `user` VALUES (5, 'Billie', 24, 'test5@baomidou.com');

(二) 初始化 SpringBoot 项目

如果没有接触过 SpringBoot,使用常规的 SSM 也是可以的,为了演示方便,这里还是使用了SpringBoot,如果想在 SSM 中使用,一个注意依赖的修改,还一个就需要修改 xml 中的一些配置

(1) 引入依赖

引入 MyBatis-Plus-boot-starter 肯定是没什么疑问的,同样我们还需要引入,数据库连接的驱动依赖,还可以看需要引入 lombok,这里为了简便所以使用了它,如果不想使用,手动生成构造方法和 get set 即可

<!-- 数据库驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency> <!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency> <!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

(2) 配置数据库信息

如何进行数据库相关的信息在以前的SpringBoot文章已经说过了,这里强调一下:

mysql 5 驱动:com.mysql.jdbc.Driver

mysql 8 驱动:com.mysql.cj.jdbc.Driver 、还需要增加时区的配置

serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root99
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

(3) 创建实体类

根据数据库字段创建出对应实体属性就行了,还是提一下:上方三个注解,主要是使用了 lombok 自动的生成那些 get set 等方法,不想用的同学直接自己按原来的方法显式的写出来就可以了~

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private Integer age;
private String email;
}

(4) 创建Mapper接口

代码如下,可以看到,我们额外的继承了 BaseMapper,同时指定了泛型为 User

@Repository
public interface UserMapper extends BaseMapper<User> { }

其实点进去 BaseMapper 看一下,你会发现,在其中已经定义了关于 CRUD 一些基本方法还有一些涉及到配合条件实现更复杂的操作,同时泛型中指定的实体,会在增删改查的方法中被调用

照这样说的话,好像啥东西都被写好了,如果现在想要进行一个简单的增删改查,是不是直接使用就行了

(5) 测试

首先在测试类中注入 UserMapper,这里演示一个查询所有的方法,所以使用了 selectList ,其参数是一个条件,这里先置为空。

如果有哪些方法的使用不明确,我们可以先点到 BaseMapper 中去看一下,down 下源码以后,会有一些注释说明

/**
* 根据 entity 条件,查询全部记录
*
* @param queryWrapper 实体对象封装操作类(可以为 null)
*/
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

下面是测试查询所有的全代码

@SpringBootTest
class MybatisPlusApplicationTests { @Autowired
private UserMapper userMapper; @Test
void contextLoads() {
List<User> userList = userMapper.selectList(null);
for (User user : userList) {
System.out.println(user);
}
}
}

(5) 结果

控制台输出如下

User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)

经过一个简单的测试,感觉还是很香的,而以前在 Mybatis 中我们执行sql语句时,是可以看到控制台打印的日志的,而这里显然没有,其实通过一行简单的配置就可以了

(三) 开启日志

其实只需要在配置文件中加入短短的一行就可以了

MyBatis-Plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

打印如下:

当然你还可以通过一些 MyBatis Log 的插件,来快速的查看自己所执行的 sql

四 CRUD

(一) 插入操作

(1) 可用方法

首先,先试试插入一个实体的操作,我们选择使用了 insert 这个方法,下面是其定义:

/**
* 插入一条记录
*
* @param entity 实体对象
*/
int insert(T entity);

(2) 演示

@Test
public void testInsert() {
// 拟一个对象
User user = new User();
user.setName("理想二旬不止");
user.setAge(30);
user.setEmail("ideal_bwh@163.com");
// 插入操作
int count = userMapper.insert(user);
System.out.println(count);
System.out.println(user);
}

结果:

根据结果看到,插入确实成功了,但是一个发蒙的问题出现了,为啥 id 变成了一个 long 类型的值

(3) 主键生成策略

对于主键的生成,官网有如下的一句话:

自3.3.0开始,默认使用雪花算法+UUID(不含中划线)

也就是说,因为上面我们没有做任何的处理,所以它使用了默认的算法来当做主键 id

A:雪花算法(snowflake)

snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。

雪花算法 + UUID 所以基本是可以保证唯一的

当然除了雪花算法为,我们还有一些别的主键生成的策略,例如 Redis、数据库自增

对于我们之前常用的一种主键生成方式,一般都会用到数据库id自增

(4) 设置主键自增

  • 数据库主键字段设置自增!!!
  • 主键实体类字段注解 @TableId(type = IdType.AUTO)

再次插入,发现 id 已经实现了自增

(5) 字段注解解释

@TableId 注解中的属性 Type 的值来自于 IdType 这个枚举类,其中我把每一项简单解释一下

  • AUTO(0) :数据库 ID 自增(MySQL 正常,Oracle 未测试)

    • 如果你想要全局都使用 AUTO 这样的数据库自增方式,可以直接在 application.properties 中添加
    • MyBatis-Plus.global-config.db-config.id-type=auto
  • NONE(1) :该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)

  • INPUT(2) :用户输入 ID,也可以自定义输入策略,内置策略如下

    • DB2KeyGenerator
    • H2KeyGenerator
    • KingbaseKeyGenerator
    • OracleKeyGenerator
    • PostgreKeyGenerator

    使用时:

    先添加 @Bean,然后实体类配置主键 Sequence,指定主键策略为 IdType.INPUT 即可,重点不说这个,有需要可以直接扒官网

@Bean
public IKeyGenerator keyGenerator() {
return new H2KeyGenerator();
}
  • ASSIGN_ID(3) :雪花算法

  • ASSIGN_UUID(4):不含中划线的UUID

3.3.0 后,ID_WORKER(3)、ID_WORKER_STR(3)、UUID(4) 就已经被弃用了,前两个可以使用 ASSIGN_ID(3)代替,最后一个使用 ASSIGN_UUID(4)代替

(二) 更新操作

(1) 可用方法

// 根据 whereEntity 条件,更新记录
int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);

(2) 演示

MyBatis-Plus 中的更新操作也是非常方便

举一种比较常见的一种情况,通过 id 值修改某些字段

传统做法会传一个修改后的对象,然后通过 #{} 设置具体更新的值和 id

<update id="updateById">
UPDATE user SET name=#{name}, portrait=#{portrait}, gender=#{gender}, telephone=#{telephone}, email=#{email} WHERE id=#{id}
</update>

MyBatis-Plus 方式:

@Test
public void testUpdate() {
// 拟一个对象
User user = new User();
user.setId(1L);
user.setName("理想二旬不止");
user.setAge(20);
int i = userMapper.updateById(user);
System.out.println(i);
}

首先我们给定了 id 值,同时又修改了姓名和年龄这两个字段,但是并不是全部字段,来看一下执行效果

神奇的发现,我们不需要在 sql 中进行设置了,所有的配置都被自动做好了,更新的内容和 id 都被自动填充好了

(3) 自动填充

自动填充是填充什么内容呢?首先我们需要知道,一般来说表中的创建时间修改时间,我们总是希望能够给根据插入或者修改的时间自动填充,而不需要我们手动的去更新

可能以前的项目不是特别综合或需要等原因,有时候也不会去设置创建时间等字段,写这部分是因为,在阿里巴巴的Java开发手册(第5章 MySQL 数据库 - 5.1 建表规约 - 第 9 条 )有明确指出:

【强制】表必备三字段:id, create_time, update_time。

说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。create_time, update_time

的类型均为 datetime 类型,前者现在时表示主动式创建,后者过去分词表示被动式更新

A:数据库级别(不常用)

我们可以通过直接修改数据库中对应字段的默认值,来实现数据库级别的自动添加语句

例如上图中我首先添加了 create_time, update_time 两个字段,然后将类型选择为 datetime,又设置其默认值为 CURRENT_TIMESTAMP

注:更新时间字段中要勾选 On Update Current_Timestamp ,插入不用,使用 SQLYog 没问题,在 Navicat 某个版本下直接通过可视化操作可能会报错,没有此默认值,这种情况就把表先导出来,然后修改SQL,在SQL 中修改语句

create_time` datetime(0)  DEFAULT CURRENT_TIMESTAMP(0) COMMENT '创建时间',
update_time` datetime(0) DEFAULT CURRENT_TIMESTAMP(0) COMMENT '修改时间',

B:代码级别

根据官网的自动填充功能的说明,其实我们需要做的只有两点:

  • 为 create_time, update_time 两个字段配置注解
  • 自定义实现类 MyMetaObjectHandler

注:开始前,别忘了删除刚才数据库级别测试时的字段默认值等喔

首先填充字段注解:

@TableField(fill = FieldFill.INSERT)

@TableField(fill = FieldFill.INSERT_UPDATE)

@TableField(fill = FieldFill.INSERT)
private Date createTime; @TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;

FieldFill 说明:

public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}

接着创建自定义实现类 MyMetaObjectHandler,让其实现MetaObjectHandler,重写其 insertFill 和 updateFill 方法,打印日志就不说了,通过 setFieldValByName 就可以对字段进行赋值,源码中这个方法有三个参数

/**
* 通用填充
*
* @param fieldName java bean property name
* @param fieldVal java bean property value
* @param metaObject meta object parameter
*/
default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {...}
  • fieldName:字段名
  • fieldVal:该字段赋予的值
  • metaObject:操作哪个数据
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
setFieldValByName("createTime", new Date(), metaObject);
setFieldValByName("updateTime", new Date(), metaObject); } @Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
setFieldValByName("updateTime", new Date(), metaObject);
}
}

查看一下效果:

下面还有一些注意事项:

注意事项:

  • 填充原理是直接给entity的属性设置值!!!
  • 注解则是指定该属性在对应情况下必有值,如果无值则入库会是null
  • MetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null则不填充
  • 字段必须声明TableField注解,属性fill选择对应策略,该声明告知MyBatis-Plus需要预留注入SQL字段
  • 填充处理器MyMetaObjectHandler在 Spring Boot 中需要声明@Component@Bean注入
  • 要想根据注解FieldFill.xxx字段名以及字段类型来区分必须使用父类的strictInsertFill或者strictUpdateFill方法
  • 不需要根据任何来区分可以使用父类的fillStrategy方法

(4) 乐观锁插件

演示乐观锁插件前,首先补充一些基础概念:

A:没有锁会怎么样

打个比方,一张电影票价格为 30,老板告诉员工 A ,把价格上调到 50,员工 A 因为有事耽搁了两个小时,但是老板想了一会觉得提价太高了,就想着定价 40 好了,正好碰到员工 B,就让员工 B 将价格降低 10 块

当正好两个员工都在操作后台系统时,两人同时取出当前价格,即 30 元,员工A 先操作后 价格变成了 50元,但是员工 B 又将30 - 10 ,即 变成20块,执行了更新操作,此时员工 B 的更新操作就会把前面的 50 元覆盖掉,即最终成为了 20元,虽然我内心毫无波澜,但老板却亏的一匹

B:乐观锁和悲观锁

  • 乐观锁:故名思意十分乐观,它总是认为不会出现问题,无论干什么不去上锁!如果出现了问题,

    再次更新值测试

    • 乐观锁下,员工 B 更新时会检查是否这个价格已经被别人修改过了,如果是就会取出新的值,再操作
  • 悲观锁:故名思意十分悲观,它总是认为总是出现问题,无论干什么都会上锁!再去操作!
    • 悲观锁下,员工 B 只能在 员工 A 操作完以后才能操作,这样能保证数据只有一个人在操作

C:MP 中的乐观锁插件

意图:

当要更新一条记录的时候,希望这条记录没有被别人更新

乐观锁实现方式:

  • 取出记录时,获取当前version
  • 更新时,带上这个version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果version不对,就更新失败

实现这个功能,只需要两步:

  • 添加数据库version字段和实体字段以及注解
@Version // 乐观锁的Version注解
private Integer version;
  • 创建配置类,引入乐观锁插件
// 扫描 mapper 文件夹
@MapperScan("cn.ideal.mapper")
@EnableTransactionManagement
// 代表配置类
@Configuration
public class MyBatisPlusConfig {
// 乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}

说明:刚开始例如扫描 mapper 这样的注解就放在了启动类中,现在有了配置类,所以把它也移过来了

测试一下:

首先1号和2号获取到的数据是一样的,但是在1号还没有执行到更新的时候,2号抢先提交了更新操作,也就是说,当前真实数据已经是被2号修改过的了,与1号前面获取到的不一致了

如果没有乐观锁,那么2号提交的更新会被1号的更新数据覆盖

// 测试更新
@Test
public void testUpdate() {
// 1号取得了数据
User user1 = userMapper.selectById(1L);
user1.setName("乐观锁1号");
user1.setAge(20);
user1.setEmail("ideal_bwh@xxx.com");
// 2号取得了数据
User user2 = userMapper.selectById(1L);
user2.setName("乐观锁2号");
user2.setAge(30);
user2.setEmail("ideal@xxx.com");
// 2号提交更新
userMapper.updateById(user2);
// 1号提交更新
userMapper.updateById(user1);
}

可以看到,在2号抢先执行后,1号就没有成功执行了

同样数据库中表的其 version 也从1变成了1

(三) 查询操作

(1) 可用方法

// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper); // 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

(2) 简单查询

A:根据 id 查询

@Test
public void testSelectById(){
User user = userMapper.selectById(1L);
System.out.println(user);
}

B:根据 id 集合查询

说明:我这里使用的还是最基本的写法,例如 List 可以用工具类创建 如:Arrays.asList(1, 2, 3)

遍历也完全可以这样 users.forEach(System.out::println);

@Test
public void testSelectByBatchId(){
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
List<User> users = userMapper.selectBatchIds(list);
for (User user : users){
System.out.println(user);
}
}

C:根据 map 查询

@Test
public void testSelectByMap(){
HashMap<String, Object> map = new HashMap<>();
// 自定义要查询的字段和值
map.put("name","理想二旬不止");
map.put("age",30); List<User> users = userMapper.selectByMap(map);
for (User user : users){
System.out.println(user);
}
}

通过日志的打印可以看到,它根据我们的选择自动拼出了 SQL 的条件

==>  Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user WHERE name = ? AND age = ?
==> Parameters: 理想二旬不止(String), 30(Integer)

(3) 分页查询

JavaWeb 阶段,大家都应该有手写过分页,配合 SQL 的 limit 进行分页,后面在 Mybatis 就会用一些例如 pageHelper 的插件,而 MyBatis-Plus 中也有一个内置的分页插件

使用前只需要进行一个小小的配置,在刚才配置类中,加入分页插件的配置代码

// 扫描我们的mapper 文件夹
@MapperScan("cn.ideal.mapper")
@EnableTransactionManagement
@Configuration // 配置类 public class MyBatisPlusConfig { // 乐观锁插件
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
} // 分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
} }

接着就可以测试分页了

@Test
public void testPage(){
// Page 参数: 参数1:当前页 ,参数1:页面大小
Page<User> page = new Page<>(2,3);
userMapper.selectPage(page,null); List<User> users = page.getRecords();
for (User user : users){
System.out.println(user);
}
System.out.println(page.getTotal());
}

执行结果日志:

==>  Preparing: SELECT id,name,age,email,version,create_time,update_time FROM user LIMIT ?,?
==> Parameters: 3(Long), 3(Long)
<== Columns: id, name, age, email, version, create_time, update_time
<== Row: 4, Sandy, 21, test4@baomidou.com, 1, null, null
<== Row: 5, Billie, 24, test5@baomidou.com, 1, null, null
<== Row: 1308952901602811906, 理想二旬不止, 30, ideal_bwh@163.com, 1, null, null
<== Total: 3

(4) 条件查询 ※

如何实现一些条件相对复杂的查询呢?MyBatis-Plus 也给我们提供了一些用法,帮助我们方便的构造各种条件

其实前面大家应该就注意到了,在查询操作的可用方法中,参数中往往带有一个名叫 Wrapper<T> queryWrapper 的内容,这就是我们要构造条件的重点

查询中最常用的就是 QueryWrapper

说明:

继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件

及 LambdaQueryWrapper, 可以通过 new QueryWrapper().lambda() 方法获取

实例化一个 QueryWrapper 后,通过调用一些内置的方法,就可以实现条件构造

A:如何使用

例如我们想要构造这样一个条件:查询邮箱不为空,且年龄小于 25 岁的用户

@Test
void contextLoads() {
// 实例化一个 QueryWrapper 对象
QueryWrapper<User> wrapper = new QueryWrapper<>();
// 进行具体条件构造
wrapper
.isNotNull("email")
.lt("age", 25);
// 执行具体的查询方法,同时将 wrapper 条件作为参数传入
List<User> users = userMapper.selectList(wrapper);
for (User user : users){
System.out.println(user);
}
}

看一下打印的日志:

==>  Preparing: SELECT id,name,age,email,version,deleted,create_time,update_time FROM user WHERE deleted=0 AND (email IS NOT NULL AND age < ?)
==> Parameters: 25(Integer)
<== Columns: id, name, age, email, version, deleted, create_time, update_time
<== Row: 2, 理想, 22, ideal_bwh@xxx.com, 1, 0, 2020-09-26 15:06:09, 2020-09-26 21:21:52
<== Row: 4, Sandy, 21, test4@baomidou.com, 1, 0, null, null
<== Row: 5, Billie, 24, test5@baomidou.com, 1, 0, null, null
<== Total: 3

可以看到条件都被自动在 SQL 中构造出来了

使用的方式就这么简单,通过各种巧妙的构造就好了

B:构造方式

下面是从官网摘取的各种构造方式:


allEq
allEq(Map<R, V> params)
allEq(Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, Map<R, V> params, boolean null2IsNull)

个别参数说明:

params : key为数据库字段名,value为字段值

null2IsNull : 为true则在mapvaluenull时调用 isNull 方法,为false时则忽略valuenull

  • 例1: allEq({id:1,name:"老王",age:null})--->id = 1 and name = '老王' and age is null
  • 例2: allEq({id:1,name:"老王",age:null}, false)--->id = 1 and name = '老王'
allEq(BiPredicate<R, V> filter, Map<R, V> params)
allEq(BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)
allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull)

个别参数说明:

filter : 过滤函数,是否允许字段传入比对条件中

paramsnull2IsNull : 同上

  • 例1: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null})--->name = '老王' and age is null
  • 例2: allEq((k,v) -> k.indexOf("a") >= 0, {id:1,name:"老王",age:null}, false)--->name = '老王'
eq
eq(R column, Object val)
eq(boolean condition, R column, Object val)
  • 等于 =
  • 例: eq("name", "老王")--->name = '老王'
ne
ne(R column, Object val)
ne(boolean condition, R column, Object val)
  • 不等于 <>
  • 例: ne("name", "老王")--->name <> '老王'
gt
gt(R column, Object val)
gt(boolean condition, R column, Object val)
  • 大于 >
  • 例: gt("age", 18)--->age > 18
ge
ge(R column, Object val)
ge(boolean condition, R column, Object val)
  • 大于等于 >=
  • 例: ge("age", 18)--->age >= 18
lt
lt(R column, Object val)
lt(boolean condition, R column, Object val)
  • 小于 <
  • 例: lt("age", 18)--->age < 18
le
le(R column, Object val)
le(boolean condition, R column, Object val)
  • 小于等于 <=
  • 例: le("age", 18)--->age <= 18
between
between(R column, Object val1, Object val2)
between(boolean condition, R column, Object val1, Object val2)
  • BETWEEN 值1 AND 值2
  • 例: between("age", 18, 30)--->age between 18 and 30
notBetween
notBetween(R column, Object val1, Object val2)
notBetween(boolean condition, R column, Object val1, Object val2)
  • NOT BETWEEN 值1 AND 值2
  • 例: notBetween("age", 18, 30)--->age not between 18 and 30
like
like(R column, Object val)
like(boolean condition, R column, Object val)
  • LIKE '%值%'
  • 例: like("name", "王")--->name like '%王%'
notLike
notLike(R column, Object val)
notLike(boolean condition, R column, Object val)
  • NOT LIKE '%值%'
  • 例: notLike("name", "王")--->name not like '%王%'
likeLeft
likeLeft(R column, Object val)
likeLeft(boolean condition, R column, Object val)
  • LIKE '%值'
  • 例: likeLeft("name", "王")--->name like '%王'
likeRight
likeRight(R column, Object val)
likeRight(boolean condition, R column, Object val)
  • LIKE '值%'
  • 例: likeRight("name", "王")--->name like '王%'
isNull
isNull(R column)
isNull(boolean condition, R column)
  • 字段 IS NULL
  • 例: isNull("name")--->name is null
isNotNull
isNotNull(R column)
isNotNull(boolean condition, R column)
  • 字段 IS NOT NULL
  • 例: isNotNull("name")--->name is not null
in
in(R column, Collection<?> value)
in(boolean condition, R column, Collection<?> value)
  • 字段 IN (value.get(0), value.get(1), ...)
  • 例: in("age",{1,2,3})--->age in (1,2,3)
in(R column, Object... values)
in(boolean condition, R column, Object... values)
  • 字段 IN (v0, v1, ...)
  • 例: in("age", 1, 2, 3)--->age in (1,2,3)
notIn
notIn(R column, Collection<?> value)
notIn(boolean condition, R column, Collection<?> value)
  • 字段 NOT IN (value.get(0), value.get(1), ...)
  • 例: notIn("age",{1,2,3})--->age not in (1,2,3)
notIn(R column, Object... values)
notIn(boolean condition, R column, Object... values)
  • 字段 NOT IN (v0, v1, ...)
  • 例: notIn("age", 1, 2, 3)--->age not in (1,2,3)
inSql
inSql(R column, String inValue)
inSql(boolean condition, R column, String inValue)
  • 字段 IN ( sql语句 )
  • 例: inSql("age", "1,2,3,4,5,6")--->age in (1,2,3,4,5,6)
  • 例: inSql("id", "select id from table where id < 3")--->id in (select id from table where id < 3)
notInSql
notInSql(R column, String inValue)
notInSql(boolean condition, R column, String inValue)
  • 字段 NOT IN ( sql语句 )
  • 例: notInSql("age", "1,2,3,4,5,6")--->age not in (1,2,3,4,5,6)
  • 例: notInSql("id", "select id from table where id < 3")--->id not in (select id from table where id < 3)
groupBy
groupBy(R... columns)
groupBy(boolean condition, R... columns)
  • 分组:GROUP BY 字段, ...
  • 例: groupBy("id", "name")--->group by id,name
orderByAsc
orderByAsc(R... columns)
orderByAsc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, ... ASC
  • 例: orderByAsc("id", "name")--->order by id ASC,name ASC
orderByDesc
orderByDesc(R... columns)
orderByDesc(boolean condition, R... columns)
  • 排序:ORDER BY 字段, ... DESC
  • 例: orderByDesc("id", "name")--->order by id DESC,name DESC
orderBy
orderBy(boolean condition, boolean isAsc, R... columns)
  • 排序:ORDER BY 字段, ...
  • 例: orderBy(true, true, "id", "name")--->order by id ASC,name ASC
having
having(String sqlHaving, Object... params)
having(boolean condition, String sqlHaving, Object... params)
  • HAVING ( sql语句 )
  • 例: having("sum(age) > 10")--->having sum(age) > 10
  • 例: having("sum(age) > {0}", 11)--->having sum(age) > 11

func

func(Consumer<Children> consumer)
func(boolean condition, Consumer<Children> consumer)
  • func 方法(主要方便在出现if...else下调用不同方法能不断链)
  • 例: func(i -> if(true) {i.eq("id", 1)} else {i.ne("id", 1)})
or
or()
or(boolean condition)
  • 拼接 OR

注意事项:

主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)

  • 例: eq("id",1).or().eq("name","老王")--->id = 1 or name = '老王'
or(Consumer<Param> consumer)
or(boolean condition, Consumer<Param> consumer)
  • OR 嵌套
  • 例: or(i -> i.eq("name", "李白").ne("status", "活着"))--->or (name = '李白' and status <> '活着')
and
and(Consumer<Param> consumer)
and(boolean condition, Consumer<Param> consumer)
  • AND 嵌套
  • 例: and(i -> i.eq("name", "李白").ne("status", "活着"))--->and (name = '李白' and status <> '活着')
nested
nested(Consumer<Param> consumer)
nested(boolean condition, Consumer<Param> consumer)
  • 正常嵌套 不带 AND 或者 OR
  • 例: nested(i -> i.eq("name", "李白").ne("status", "活着"))--->(name = '李白' and status <> '活着')
apply
apply(String applySql, Object... params)
apply(boolean condition, String applySql, Object... params)
  • 拼接 sql

注意事项:

该方法可用于数据库函数 动态入参的params对应前面applySql内部的{index}部分.这样是不会有sql注入风险的,反之会有!

  • 例: apply("id = 1")--->id = 1
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
  • 例: apply("date_format(dateColumn,'%Y-%m-%d') = {0}", "2008-08-08")--->date_format(dateColumn,'%Y-%m-%d') = '2008-08-08'")
last
last(String lastSql)
last(boolean condition, String lastSql)
  • 无视优化规则直接拼接到 sql 的最后

注意事项:

只能调用一次,多次调用以最后一次为准 有sql注入的风险,请谨慎使用

  • 例: last("limit 1")
exists
exists(String existsSql)
exists(boolean condition, String existsSql)
  • 拼接 EXISTS ( sql语句 )
  • 例: exists("select id from table where age = 1")--->exists (select id from table where age = 1)
notExists
notExists(String notExistsSql)
notExists(boolean condition, String notExistsSql)
  • 拼接 NOT EXISTS ( sql语句 )
  • 例: notExists("select id from table where age = 1")--->not exists (select id from table where age = 1)

(四) 删除操作

(1) 可用方法

// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

(2) 简单删除

A:根据 id 删除

@Test
public void testDeleteById(){
userMapper.deleteById(1L);
}

B:根据 id 集合删除

@Test
public void testDeleteBatchIds(){
List list = new ArrayList();
list.add(1308952901602811906L);
list.add(1308952901602811907L);
list.add(1308952901602811908L);
list.add(1308952901602811909L);
userMapper.deleteBatchIds(list);
}

C:根据 map 删除

 @Test
public void testDeleteMap(){
HashMap<String, Object> map = new HashMap<>();
map.put("name","理想二旬不止");
userMapper.deleteByMap(map);
}

(3) 逻辑删除

删除这块再补充一下逻辑删除的概念,物理删除很好理解,就是实实在在的在数据库中删没了,但是逻辑删除,顾名思义只是逻辑上被删除了,实际上并没有,只是通过增加一个字段让其失效而已,例如 deleted = 0 => deleted = 1

可以

应用的场景就是管理员想查看删除记录,在错误删除下,可以有逆转的机会等等

首先数据库增加 deleted 字段,同时创建其实体和注解

@TableLogic // 逻辑删除
private Integer deleted;

接着只需要在全局配置中配置即可

application.properties

# 配置逻辑删除
MyBatis-Plus.global-config.db-config.logic-delete-value=1
MyBatis-Plus.global-config.db-config.logic-not-delete-value=0

application.yml

MyBatis-Plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

效果如下:

你会发现,逻辑删除会走一个更新操作,通过修改指定字段 deleted 的值为 0 实现我们想要的效果

五 代码自动生成器

MyBatis-Plus 提供了一个非常便捷,有意思的内容,那就是代码的自动生成,我们通过一些配置,就可以自动的生成 controller、service、mapper、pojo 的内容,并且接口或者注解等内容都会按照配置指定的格式生成。(提前准备好数据库和表)

首先除了 MyBatis-Plus 的依赖以外,还需要引入 swagger 和 velocity 的依赖,但是这两者其实是可选的,可以选择不配置就不用引入了,默认使用 velocity 这个模板引擎,大家还可以换成别的

<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency> <dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency> <dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

例如:

Velocity(默认):

<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>latest-velocity-version</version>
</dependency>

Freemarker:

<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>latest-freemarker-version</version>
</dependency>

Beetl:

<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>latest-beetl-version</version>
</dependency>
  • 注意!如果您选择了非默认引擎,需要在 AutoGenerator 中 设置模板引擎。

    AutoGenerator generator = new AutoGenerator();
    
    // set freemarker engine
    generator.setTemplateEngine(new FreemarkerTemplateEngine()); // set beetl engine
    generator.setTemplateEngine(new BeetlTemplateEngine()); // set custom engine (reference class is your custom engine class)
    generator.setTemplateEngine(new CustomTemplateEngine()); // other config
    ...

下面就是一个主配置了,修改其中的数据库连接等信息,以及包的名称等等等执行就可以了

package cn.ideal;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy; import java.util.ArrayList; /**
* @ClassName: AutomaticCodeGenerate
* @Description: TODO
* @Author: BWH_Steven
* @Date: 2020/10/2 21:29
* @Version: 1.0
*/
public class AutomaticCodeGenerate {
public static void main(String[] args) {
// 需要构建一个代码自动生成器对象
AutoGenerator mpg = new AutoGenerator();
// 配置策略
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setAuthor("BWH_Steven");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setOpen(false);
gc.setFileOverride(false); // 是否覆盖
gc.setServiceName("%sService"); // 去Service的I前缀
gc.setIdType(IdType.ID_WORKER);
gc.setDateType(DateType.ONLY_DATE);
gc.setSwagger2(true);
mpg.setGlobalConfig(gc);
// 设置数据源
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/mybatis_plus?useSSL=false&useUnicode=true&characterEncoding" +
"=utf-8&serverTimezone=GMT%2B8");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root99");
dsc.setDbType(DbType.MYSQL);
mpg.setDataSource(dsc);
// 包的配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("test");
pc.setParent("cn.ideal");
pc.setEntity("entity");
pc.setMapper("mapper");
pc.setService("service");
pc.setController("controller");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setInclude("user"); // 设置要映射的表名
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityLombokModel(true); // 自动lombok;
strategy.setLogicDeleteFieldName("deleted");
// 自动填充配置
TableFill gmtCreate = new TableFill("create_time", FieldFill.INSERT);
TableFill gmtModified = new TableFill("update_time", FieldFill.INSERT_UPDATE);
ArrayList<TableFill> tableFills = new ArrayList<>();
tableFills.add(gmtCreate);
tableFills.add(gmtModified);
strategy.setTableFillList(tableFills); // 乐观锁
strategy.setVersionFieldName("version");
strategy.setRestControllerStyle(true);
strategy.setControllerMappingHyphenStyle(true);
mpg.setStrategy(strategy);
mpg.execute(); //执行
}
}

生成结构效果如下:

我简单贴两段生成的内容:

controller

package cn.ideal.test.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; /**
* <p>
* 前端控制器
* </p>
*
* @author BWH_Steven
* @since 2020-10-02
*/
@RestController
@RequestMapping("/test/user")
public class UserController { }

entity

package cn.ideal.test.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.Version;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode; /**
* <p>
*
* </p>
*
* @author BWH_Steven
* @since 2020-10-02
*/
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="User对象", description="")
public class User implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "主键ID")
@TableId(value = "id", type = IdType.AUTO)
private Long id; @ApiModelProperty(value = "姓名")
private String name; @ApiModelProperty(value = "年龄")
private Integer age; @ApiModelProperty(value = "邮箱")
private String email; @ApiModelProperty(value = "版本")
@Version
private Integer version; @TableLogic
private Integer deleted; @ApiModelProperty(value = "创建时间")
@TableField(fill = FieldFill.INSERT)
private Date createTime; @ApiModelProperty(value = "修改时间")
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime; }

service

package cn.ideal.test.service;

import cn.ideal.test.entity.User;
import com.baomidou.mybatisplus.extension.service.IService; /**
* <p>
* 服务类
* </p>
*
* @author BWH_Steven
* @since 2020-10-02
*/
public interface UserService extends IService<User> { }

service 实现类

package cn.ideal.test.service.impl;

import cn.ideal.test.entity.User;
import cn.ideal.test.mapper.UserMapper;
import cn.ideal.test.service.UserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service; /**
* <p>
* 服务实现类
* </p>
*
* @author BWH_Steven
* @since 2020-10-02
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService { }

mapper

package cn.ideal.test.mapper;

import cn.ideal.test.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; /**
* <p>
* Mapper 接口
* </p>
*
* @author BWH_Steven
* @since 2020-10-02
*/
public interface UserMapper extends BaseMapper<User> { }

mapper 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="cn.ideal.test.mapper.UserMapper"> </mapper>

六 结尾

如果文章中有什么不足,欢迎大家留言交流,感谢朋友们的支持!

如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号

在这里的我们素不相识,却都在为了自己的梦而努力

一个坚持推送原创开发技术文章的公众号:理想二旬不止

MyBatis 进阶,MyBatis-Plus!(基于 Springboot 演示)的更多相关文章

  1. 基于SpringBoot+MyBatis实现一套电商系统

    项目介绍 mall项目是一套电商系统,包括前台商城系统及后台管理系统,基于SpringBoot+MyBatis实现. 前台商城系统包含首页门户.商品推荐.商品搜索.商品展示.购物车.订单流程.会员中心 ...

  2. 基于SpringBoot+Mybatis+MySQL5.7的轻语音乐网

    一个基于SpringBoot+Mybatis+MySQL5.7的轻语音乐网站项目 1.主要用到的技术: 使用maven进行项目构建 使用Springboot+Mybatis搭建整个系统 使用ajax连 ...

  3. 基于SpringBoot + Mybatis实现SpringMVC Web项目

    一.热身 一个现实的场景是:当我们开发一个Web工程时,架构师和开发工程师可能更关心项目技术结构上的设计.而几乎所有结构良好的软件(项目)都使用了分层设计.分层设计是将项目按技术职能分为几个内聚的部分 ...

  4. springboot集成mybatis及mybatis generator工具使用

    原文链接 前言mybatis是一个半自动化的orm框架,所谓半自动化就是mybaitis只支持数据库查出的数据映射到pojo类上,而实体到数据库的映射需要自己编写sql语句实现,相较于hibernat ...

  5. springboot入门(三)-- springboot集成mybatis及mybatis generator工具使用

    前言 mybatis是一个半自动化的orm框架,所谓半自动化就是mybaitis只支持数据库查出的数据映射到pojo类上,而实体到数据库的映射需要自己编写sql语句实现,相较于hibernate这种完 ...

  6. mybatis整合spring 之 基于接口映射的多对一关系

    转载自:http://my.oschina.net/huangcongmin12/blog/83731 mybatis整合spring 之  基于接口映射的多对一关系. 项目用到俩个表,即studen ...

  7. Mybatis(二)基于注解的入门实例

    前言 上一篇简单的介绍了Mybatis的概念和基于XML来实现数据库的CRUD,这篇给大家实现基于注解的CRUD. 一.初始搭建 在基于注解当中前四步和上一篇基于XML是一样的,分别是: 1)创建数据 ...

  8. SpringBoot整合mybatis——配置mybatis驼峰命名规则自动转换

    一.简述 mybatis驼峰式命名规则自动转换: 使用前提:数据库表设计按照规范“字段名中各单词使用下划线"_"划分”: 使用好处:省去mapper.xml文件中繁琐编写表字段列表 ...

  9. Mybatis分页-利用Mybatis Generator插件生成基于数据库方言的分页语句,统计记录总数 (转)

    众所周知,Mybatis本身没有提供基于数据库方言的分页功能,而是基于JDBC的游标分页,很容易出现性能问题.网上有很多分页的解决方案,不外乎是基于Mybatis本机的插件机制,通过拦截Sql做分页. ...

随机推荐

  1. WPF管理系统开发框架搭建指南,2020从入门到放弃

    WPF技术是一个很不错的技术,但一直没有上手过正式的项目,趁在做这个医疗项目时,遂搭建一个WPF开发框架,目的是为了统一WPF开发并提高开发效率:我对WPF技术算是零基础,现学现卖,用这些不成体系的文 ...

  2. [PyTorch 学习笔记] 4.2 损失函数

    本章代码: https://github.com/zhangxiann/PyTorch_Practice/blob/master/lesson4/loss_function_1.py https:// ...

  3. Promise、Generator,Async/await

    我们知道JavaScript是单线程语言,如果没有异步编程非得卡死. 以前,异步编程的方法有下面四种 回调函数 事件监听 发布/订阅 Promise对象 现在据说异步编程终极解决方案是——async/ ...

  4. Mybatis通用Join的实现(最终版)

    你是否还在为mybatis的多表关联查询而写xml烦恼,是否还在为动态组装查询条件烦恼,是否还在为此没有合适的解决方案烦恼? mybatis-extension插件,解决开发过程中需要多表关联时需手写 ...

  5. Linux下Vim常用操作

    linux下Vim的常用操作 linux ​ 首先\(ctrl+Alt+t\)打开小框框 ​ \(./\):相当于手机上的\(home\)键 ​ \(ls\):当前文件夹的东东 ​ \(mkdir\) ...

  6. vue项目打包上线发现 360 浏览器不兼容?

    分享链接: 文档:解决vue 和 360 浏览器兼容问题.note链接:http://note.youdao.com/noteshare?id=41914c6dbb4238d765b26d59aa05 ...

  7. HTML5 Drag & Drop

    一定要区分不同事件产生的对象 源元素 属性:draggable = "true" 事件: ondragstart:开始拖拽 ondragend:拖拽结束 目标元素 事件: ondr ...

  8. linux下P2P协议(BitTorrent)-libtorrent库编译,测试

    1.libtorrent 简介,下载和编译 libtorrent简介 libtorrent是功能齐全的C ++ bittorrent的p2p协议实现,专注于效率和可伸缩性.它可以在嵌入式设备和台式机上 ...

  9. 截图还在使用QQ的Ctrl + Alt + A 截图?还不会网页长截图?

    截图还在使用QQ的Ctrl + Alt + A 截图?还不会网页长截图?   手机自带快捷键,常常使用组合键进行快速截图编辑发好友.保存等,但是貌似到了电脑截图就出现了一大堆拍屏幕党,不少人需要打开微 ...

  10. String源码浅析

    如果问你,开发过程中用的最多的类是哪个?你可能回答是HashMap,一个原因就是HashMap的使用量的确很多,还有就是HashMap的内容在面试中经常被问起. 但是在开发过程中使用最多的类其实并不是 ...