深入学习Mybatis-plus


 

?了解Mybatis-plus

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

1.1MP特性

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

  • 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作

  • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,

  • 更有强大的条件构造器,满足各类使用需求

  • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错

  • 支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、

  • SQLServer2005、SQLServer 等多种数据库

  • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解

  • 决主键问题

  • 支持 XML 热加载:Mapper 对应的 XML 支持热加载,对于简单的 CRUD 操作,甚至可以无 XML 启动

  • 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操

  • 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )

  • 支持关键词自动转义:支持数据库关键词(order、key......)自动转义,还可自定义关键词

  • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,

  • 支持模板引擎,更有超多自定义配置等您来使用

  • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List

  • 查询

  • 内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询

  • 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

  • 内置 Sql 注入剥离器:支持 Sql 注入剥离,有效预防 Sql 注入攻击

1.2架构

image-1205

?? Mybatis-Plus 快速开始

对于Mybatis整合MP有常常有三种用法,分别是Mybatis+MP、Spring+Mybatis+MP、Spring Boot+Mybatis+MP。

2.1创建数据库以及表

-- 创建数据库
CREATE DATABASE mp CHARSET 'utf8';
?
-- 创建表
CREATE TABLE tb_user(
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID',
username VARCHAR(20) NOT NULL COMMENT'用户名',
PASSWORD VARCHAR(20) NOT NULL COMMENT'密码',
NAME VARCHAR(30) DEFAULT NULL COMMENT'姓名',
age INT DEFAULT NULL COMMENT'年龄',
email VARCHAR(50) DEFAULT NULL COMMENT'邮箱'
)CHARSET = utf8;

2.2、创建工程

<groupId>priv.chenJiegroupId>
<artifactId>myBatis-plus-1artifactId>
<packaging>pompackaging>

2.3、Mybatis 实现查询User

第一步 编写mybatis-confifig.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>
?
   <typeAliases>
       <typeAlias type="priv.cj.pojo.User" alias="user"/>
   typeAliases>
?
   <environments default="development">
       <environment id="development">
           <transactionManager type="JDBC"/>
           <dataSource type="POOLED">
               <property name="driver" value="com.mysql.jdbc.Driver"/>
               <property name="url" value="jdbc:mysql://127.0.0.1:3306/mp? useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQuerie s=true&useSSL=false"/>
               <property name="username" value="root"/>
               <property name="password" value="root"/>
           dataSource>
       environment>
   environments>
?
   <mappers>
       <mapper resource="UserMapper.xml"/>
   mappers>
configuration>

第二步 编写User实体对象:(这里使用lombok进行了进化bean操作)

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user") //Mybatis+MP中需要注明数据库表名
public class User {
   private Long id;
   private String userName;
   private String password;
   private String name;
   private Integer age;
   private String email;
}

第三步 编写UserMapper接口:

public interface UserMapper {
   List<User> findAll();
}

第四步 编写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="priv.cj.mapper.UserMapper">
   <select id="findAll" resultType="user">
       select * from tb_user
   select>
mapper>

第五步 编写TestMybatis测试用例:

    @Test
   public void test1() throws IOException {
       InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
       SqlSession sqlSession = sqlSessionFactory.openSession();
       UserMapper mapper = sqlSession.getMapper(UserMapper.class);
       List<User> all = mapper.findAll();
       for (User user : all) {
           System.out.println(user);
      }
  }

2.4、Mybatis +MP实现查询User

第一步 将UserMapper继承BaseMapper,将拥有了BaseMapper中的所有方法:

public interface UserMapper extends BaseMapper<User> {
   List<User> findAll();
}

第二步 使用MP中的MybatisSqlSessionFactoryBuilder进程构建:

    @Test
public void test1() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
//由于使用了MybatisSqlSessionFactoryBuilder进行了构建,继承的BaseMapper中的方法就载入到了SqlSession中,所以就可以直接使用相关的方法;
SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 可以调用BaseMapper中定义的方法
List all = mapper.selectList(null);
for (User user : all) {
System.out.println(user);
}
}
[tips]  在User对象中添加@TableName,指定数据库表名

2.5、Spring + Mybatis + MP

引入了Spring框架,数据源、构建等工作就交给了Spring管理。

第一步 创建model

<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

priv.chenJie
myBatis-plus-1
1.0-SNAPSHOT

4.0.0

myBatis-plus-spring


5.2.9.RELEASE




org.springframework
spring-webmvc
${spring.version}


org.springframework
spring-jdbc
${spring.version}


org.springframework
spring-test
${spring.version}


第二步 编写jdbc.properties及log4j.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mp
jdbc.username=root
jdbc.password=root

log4j.rootLogger=DEBUG,A1
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=[%t] [%c]-[%p] %m%n

第三步 编写applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cintext="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">




















第四步 编写User对象以及UserMapper接口:

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
public interface UserMapper extends BaseMapper {
}

第五步 编写测试用例

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestMybatisSpring {

@Autowired
private UserMapper userMapper;

@Test
public void test1(){
List users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}

2.6、SpringBoot + Mybatis + MP

使用SpringBoot将进一步的简化MP的整合,需要注意的是,由于使用SpringBoot需要继承parent,所以需要重新创建工程,并不是创建子Module。

第一步 创建工程继承parent并导入依赖

<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0


org.springframework.boot
spring-boot-starter-parent
2.6.2


priv.chenJie
myBatis-plus-springboot
1.0-SNAPSHOT




org.springframework.boot
spring-boot-starter


org.springframework.boot
spring-boot-starter-logging





org.springframework.boot
spring-boot-starter-test
2.6.2



org.projectlombok
lombok
1.18.12



com.baomidou
mybatis-plus-boot-starter
3.1.1


mysql
mysql-connector-java
5.1.32


org.slf4j
slf4j-log4j12
1.7.32


第二步 编写application.properties及log4j.properties

spring.application.name=myBatis-plus-springboot	//当前项目名
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mp
spring.datasource.username=root
spring.datasource.password=root

第四步 编写pojo及mapper

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private String name;
private Integer age;
private String email;
}
@Mapper
public interface UserMapper extends BaseMapper {
}

第五步 编写启动类

@MapperScan("priv.cj.mapper") //设置mapper接口的扫描包
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}

第六步 编写测试类

@SpringBootTest
public class TestMybatisSpringBoot {

@Autowired
private UserMapper userMapper;

@Test
public void test1(){
List users = this.userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}
}

?? Mybatis-Plus实现CURD

通过前面的学习,我们了解到通过继承BaseMapper就可以获取到各种各样的单表操作,接下来我们将详细讲解这些 操作。

3.1插入操作

	@Test
public void testInsert() {
User user = new User();
user.setAge(20);
user.setMail("test@itcast.cn");
user.setName("曹操");
user.setUserName("caocao");
user.setPassword("123456");
user.setFavorite("陈洁");
int result = this.userMapper.insert(user); //返回的result是受影响的行数,并不是自增后的id
System.out.println("result = " + result);
System.out.println(user.getId()); //自增后的id会回填到对象中
}

还需要设置id的生成策略,期望的是数据库自增长

    //设置id自增长
@TableId(type = IdType.AUTO)
private Long id;

@TableField

在MP中通过@TableField注解可以指定字段的一些属性,常常解决的问题有2个:

1、对象中的属性名和字段名不一致的问题(非驼峰) ? 2、对象中的属性字段在表中不存在的问题

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User {

@TableId(type = IdType.AUTO)//设置id自增长
private Long id;
private String userName;

@TableField(select = false)//查询时不返回当前字段中的值
private String password;
private String name;
private Integer age;

@TableField(value = "email")//解决字段名不一致
private String mail;

@TableField(exist = false)//解决对象字段在表中不存在的问题
private String favorite;
}

3.2 更新操作

在MP中,更新操作有2种,一种是根据id更新,另一种是根据条件更新。

3.2.1 根据id更新

    @Test
public void testUpdateById() {
User user = new User();
user.setId(4L);
user.setUserName("chenJie");
user.setPassword("loveJT1314");
user.setMail("loveCJ.com");
int result = userMapper.updateById(user);
System.out.println("result =>" + result);
}

3.2.2 根据条件更新

QueryWrapper实体对象封装操作类,或可以为null

    @Test
public void testUpdate1() {
User user = new User();
user.setUserName("jiangTao");//更新的字段
user.setPassword("loveCJ1314");
user.setMail("loveJT.com");

QueryWrapper wrapper = new QueryWrapper<>();///更新的条件
wrapper.eq("user_name", "jt");//匹配user_name=jt的数据
int result = userMapper.update(user, wrapper);
System.out.println("result=>" + result);
}

使用UpdateWrapper

    @Test
public void testUpdate2() {
UpdateWrapper wrapper = new UpdateWrapper<>();
wrapper.set("age", 21).set("password", "loveChenJie1314")//设置更新字段
.eq("user_name", "jiangTao");//使用的都是字段的名字,不是实体中属性的名
int result = userMapper.update(null, wrapper);
System.out.println("result=>" + result);
}

3.3 删除操作

3.3.1 deleteById

    @Test
public void testDeleteById() {
int result = userMapper.deleteById(9);
System.out.println("result=>" + result);
}

3.3.2 deleteByMap

    @Test
public void testDeleteByMap() {
Map map = new HashMap<>();
map.put("user_name", "caocao");
map.put("id", 8);
//根据map查询数据,多条件之间为and关系
int result = this.userMapper.deleteByMap(map);
System.out.println("result=>" + result);
}

3.3.3 delete 根据包装条件删除

	@Test
public void testDelete() {
//方法一:手动填写字段
// QueryWrapper wrapper = new QueryWrapper<>();
// wrapper.eq("user_name","caocao").eq("id",7);
//方法二:面向对象 ==推荐
User user = new User();
user.setUserName("caocao");
user.setId(6L);
QueryWrapper wrapper = new QueryWrapper<>(user);

int result = this.userMapper.delete(wrapper);
System.out.println("result=>" + result);
}

3.3.4 deleteBatchIds 根据id批量删除

    @Test
public void testDeleteBatchIds(){
//List list = new ArrayList<>();
//list.add(15L);
//list.add(16L);
int result = this.userMapper.deleteBatchIds(Arrays.asList(10L, 11L));
System.out.println("result=>" + result);
}

3.4 查询操作

MP提供了多种查询操作,包括根据id查询、批量查询、查询单条数据、查询列表、分页查询等操作。

3.4.1 selectById

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

3.4.2 selectBatchIds

    @Test
public void testSelectBatchIds(){
//根据id批量查询数据
List users = this.userMapper.selectBatchIds(Arrays.asList(4L,5L,1314L));
for (User user : users) {
System.out.println(user);
}
}

3.4.3 selectOne

@Test
public void testSelectOne(){
//根据条件查询一条语句,结果超过一条会报错
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("user_name","zhangsan");
User user = this.userMapper.selectOne(wrapper);
System.out.println(user);
}

3.4.4 selectCount

    @Test
public void testSelectCount(){
//根据条件查询数据条数
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.gt("age",20);//年龄大于20的用户
Integer count = this.userMapper.selectCount(wrapper);
System.out.println(count);
}

3.4.5 selectList

@Test
public void testSelectList(){
//根据条件查询
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.like("email","test");//like模糊查询
List users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}

3.4.6、selectPage

配置分页插件:

@Configuration
@MapperScan("priv.cj.mapper") //设置mapper接口的扫描包
public class MybatisPlusConfig {

//配置分页拦截器,分页插件
@Bean
public PaginationInterceptor paginationInterceptor(){
return new PaginationInterceptor();
}
}
    @Test
public void testSelectPage(){
Page page = new Page<>(1,2);//查询第1页,查询2条数据

QueryWrapper wrapper = new QueryWrapper<>();
wrapper.notLike("email","test");

IPage iPage = this.userMapper.selectPage(page, wrapper);

System.out.println("数据总条数:"+iPage.getTotal());
System.out.println("数据总页数:"+iPage.getPages());
System.out.println("当前页数:"+iPage.getCurrent());
List records = iPage.getRecords();//获取分页信息
for (User record : records) {
System.out.println(record);
}
}

3.5 SQL注入原理

MP在启动后会将BaseMapper中的一系列的方法注册到meppedStatements中,那么究竟是如何注入的呢?流程又是怎么样的?

  1. 在MP中,ISqlInjector负责SQL的注入工作,它是一个接口,AbstractSqlInjector是它的实现类

  2. 在AbstractSqlInjector中,主要是由inspectInject()方法进行注入的

  3. 在实现方法中, methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); 是关键,循环遍历方法,进行注入。

  4. 最终调用抽象方法injectMappedStatement进行真正的注入,该方法的实现:

    image-20220109200731172

以selectById查看:

可以看到,生成了SqlSource对象,再将SQL通过addSelectMappedStatement方法添加到meppedStatements中。

image-20220109104631144

??配置

在MP中有大量的配置,其中有一部分是Mybatis原生的配置,另一部分是MP的配置,详情:https://mybatis.plus/config/ 下面我们对常用的配置做讲解。

4.1、基本配置

4.1.1、configLocation

MyBatis 配置文件位置,如果您有单独的 MyBatis 配置,请将其路径配置到 configLocation 中。

Spring MVC:



Spring Boot:

mybatis-plus.config-location = classpath:mybatis-config.xml

4.1.2、mapperLocations

MyBatis Mapper 所对应的 XML 文件位置,如果您在 Mapper 中有自定义方法(XML 中有自定义实现),需要进行该配置,告诉 Mapper 所对应的 XML 文件位置。

Spring MVC:



[tips]  Maven 多模块项目的扫描路径需以 classpath*: 开头 (即加载多个 jar 包下的 XML 文件)

Spring Boot:

mybatis-plus.mapper-locations = classpath*:mybatis/*.xml

4.1.3、typeAliasesPackage

MyBaits 别名包扫描路径.

Spring MVC:



Spring Boot:

mybatis-plus.type-aliases-package=priv.cj.pojo

4.2、进阶配置

本部分(Configuration)的配置大都为 MyBatis 原生支持的配置,这意味着您可以通过 MyBatis XML 配置文件的形 式进行配置。

4.2.1、mapUnderscoreToCamelCase

是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属 性名 aColumn(驼峰命名) 的类似映射。mor

SprinfBoot

# 关闭自动驼峰映射,该参数不能和mybatis-plus.config-location同时存在
mybatis-plus.configuration.map-underscore-to-camel-case=false
[tips]  此属性在 MyBatis 中原默认值为 false,在 MyBatis-Plus 中,此属性也将用于生成最终的 SQL 的 select body。如果您的数据库命名符合规则无需使用 @TableField 注解指定数据库字段名

4.2.2、cacheEnabled

全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存,默认为 true。

 mybatis-plus.configuration.cache-enabled=false

4.3、DB 策略配置

4.3.1、idType

全局默认主键类型,设置后,即可省略实体对象中的@TableId(type = IdType.AUTO)配置。

SpringBoot:

mybatis-plus.global-config.db-config.id-type=auto

SpringMVC:












4.3.2、tablePrefix

表名前缀,全局配置后可省略@TableName()配置。

SpringBoot:

mybatis-plus.global-config.db-config.table-prefix=tb_

SpringMVC:













??条件构造器

在MP中,Wrapper接口的实现类关系如下:

image-20220110101948625

可以看到,AbstractWrapper和AbstractChainWrapper是重点实现,接下来我们重点学习AbstractWrapper以及其 子类

5.1、allEq

5.1.1 Map作为条件

allEq(Map params)
allEq(Map params, boolean null2IsNull)
allEq(boolean condition, Map params, boolean null2IsNull)

字段说明:

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

null2IsNull : 为 true 则在 map 的 value 为 null 时调用 isNull 方法,为 false 时则忽略 value 为 null 的字段

测试用例:

@Test
public void testWrapper(){
Map map = new HashMap<>();
map.put("name","曹操");
map.put("age",21);
map.put("password",null);

QueryWrapper wrapper = new QueryWrapper<>();

//SELECT * FROM tb_user WHERE password IS NULL AND name = 曹操 AND age = 21
wrapper.allEq(map);
//SELECT * FROM tb_user WHERE name = 曹操 AND age = 21
wrapper.allEq(map,false);

List users = this.userMapper.selectList(wrapper);
for (User user : users) {
System.out.println(user);
}
}

5.1.2 filter过滤函数

allEq(BiPredicate filter, Map params)
allEq(BiPredicate filter, Map params, boolean null2IsNull)
allEq(boolean condition, BiPredicate filter, Map params, boolean null2IsNull)

字段说明:

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

测试用例:

//SELECT * FROM tb_user WHERE name = 曹操 AND age = 21
wrapper.allEq((k, v) -> (k.equals("name") || k.equals("age")), map);

5.2、基本比较操作

方法名称含义
eq 等于 =
ne 不等于 <>
gt 大于 >
ge 大于等于 >=
lt 小于 <
le 小于等于 <=
between BETWEEN 值1 AND 值2
notBetween NOT BETWEEN 值1 AND 值2
in 字段 IN (value.get(0), value.get(1), ...)
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("password", "123456")
.ge("age", 20)
.in("name", "李四", "王五", "赵六");

//select * FROM tb_user WHERE password = ? AND age >= ? AND name IN (?,?,?)
List users = this.userMapper.selectList(wrapper);

5.3、模糊查询

方法名称含义
like LIKE '%值%'
notLike NOT LIKE '%值%'
likeLeft LIKE '%值'
likeRight LIKE '值%'

5.4、排序

方法名称含义
orderBy ORDER BY 字段, ..
orderByAsc ORDER BY 字段, ... ASC
orderByDesc ORDER BY 字段, ... DESC
//select * FROM tb_user ORDER BY age ASC , id ASC
wrapper.orderBy(true,true,"age","id");

5.5、逻辑查询

方法名称含义
or 用于拼接条件 OR
and AND 嵌套
//SELECT * FROM tb_user WHERE name = ? OR age = ? 
wrapper.eq("name","曹操").or().eq("age",12);

5.6、select

在MP查询中,默认查询所有的字段,如果有需要也可以通过select方法进行指定字段。

//SELECT name FROM tb_user WHERE name = ? OR age = ? 
wrapper.eq("name","曹操")
.or()
.eq("age",12)
.select("name");
List users = this.userMapper.selectList(wrapper);

?? ActiveRecord

ActiveRecord属于ORM(对象关系映射)层,由Rails最早提出,遵循标准的ORM模型:表映射到记录,记 录映射到对象,字段映射到对象属性。配合遵循的命名和配置惯例,能够很大程度的快速实现模型的操作,而且简洁易懂。

ActiveRecord的主要思想是:

  • 每一个数据库表对应创建一个类,类的每一个对象实例对应于数据库中表的一行记录;

  • 通常表的每个字段 在类中都有相应的Field;

  • ActiveRecord同时负责把自己持久化,在ActiveRecord中封装了对数据库的访问,即CURD;

  • ActiveRecord是一种领域模型(Domain Model),封装了部分业务逻辑;

6.1 开启AR之旅

在MP中,开启AR非常简单,只需要将实体对象继承Model即可。

@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("tb_user")
public class User extends Model {

@TableId(type = IdType.AUTO)//设置id自增长
private Long id;
private String userName;

@TableField(select = false)//查询时不返回当前字段中的值
private String password;
private String name;
private Integer age;

@TableField(value = "email")//解决字段名不一致
private String mail;

@TableField(exist = false)//解决对象字段在表中不存在的问题
private String favorite;
}
[tips]  AR不需要注入UserMapper但UserMapper必须存在

6.2 查询操作

@Test
public void testSelectById(){
User user = new User();
user.setId(2L);
//SELECT id,user_name,name,age,email AS mail FROM tb_user WHERE id=?
User user1 = user.selectById();
System.out.println(user1);
}
@Test
public void testSelect(){
User user = new User();
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.gt("age",18);
List userList = user.selectList(wrapper);
for (User user1 : userList) {
System.out.println(user1);
}
}

6.3 新增数据

    @Test
public void testInsert(){
User user = new User();
user.setUserName("AR");
user.setPassword("123");
user.setAge(21);
user.setMail("AR.com");

boolean res = user.insert();
System.out.println(res);
}

6.4 更新操作

    @Test
public void testUpdateById(){
User user = new User();
user.setId(12L);
user.setUserName("caocao");
user.setPassword("123123");
user.setAge(1);
user.setName("曹操");
boolean flag = user.updateById();
System.out.println("flag=>"+flag);
}
@Test
public void testUpdateByCondition(){
User user = new User();
user.setPassword("999");
user.setAge(99);
user.setName("枭雄");
QueryWrapper wrapper = new QueryWrapper<>();
wrapper.eq("user_name","caocao");
boolean flag = user.update(wrapper);
System.out.println("flag=>"+flag);
}

6.5 删除操作

 @Test
public void testDelete(){
User user = new User();
user.setId(12L);
boolean flag = user.deleteById();
System.out.println("flag=>"+flag);
}

??Oracle 主键Sequence

  • Oracle

??:插件

8.1、mybatis的插件机制

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法 调用包括:

  • Executor 拦截执行器的方法

  • ParameterHandler 拦截参数的处理

  • ResultSetHandler 拦截结果集的处理

  • StatementHandler 拦截Sql语法构建的处理

拦截器实例:

@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class MyInterceptor implements Interceptor {

public Object intercept(Invocation invocation) throws Throwable {
//拦截方法,具体业务逻辑编写的位置
return invocation.proceed();
}

public Object plugin(Object target) {
//创建target对象的代理对象,目的是将当前拦截器加入到该对象中
return Plugin.wrap(target, (org.apache.ibatis.plugin.Interceptor) this);
}

public void setProperties(Properties properties) {
//属性设置
}
}

注入到Spring容器:

/**
* 自定义拦截器
*/
@Bean
public MyInterceptor myInterceptor(){
return new MyInterceptor();
}

或者通过xml配置,mybatis-config.xml:

<?xml version="1.0" encoding="UTF-8" ?>





8.2、执行分析插件

在MP中提供了对SQL执行的分析的插件,可用作阻断全表更新、删除的操作,注意:该插件仅适用于开发环境,不 适用于生产环境。

SprinigBoot配置

@Bean
public SqlExplainInterceptor sqlExplainInterceptor(){
SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
List sqlParserList = new ArrayList<>();
// 攻击 SQL 阻断解析器、加入解析链
sqlParserList.add(new BlockAttackSqlParser());
sqlExplainInterceptor.setSqlParserList(sqlParserList);
return sqlExplainInterceptor;
}
[ttips]  当执行全表更新时,会抛出异常,这样有效防止了一些误操作。

8.3、性能分析插件

性能分析拦截器,用于输出每条 SQL 语句及其执行时间,可以设置最大执行时间,超过时间会抛出异常。

[^]  该插件只用于开发环境,不建议生产环境使用。 

配置:

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



interceptor="com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor">





8.4、乐观锁插件

当要更新一条记录的时候,希望这条记录没有被别人更新,可避免多人同时更新导致的数据不一致。

乐观锁实现方式:

  • 取出记录时,获取当前version

  • 更新时,带上这个version

  • 执行更新时, set version = newVersion where version = oldVersion

  • 如果version不对,就更新失败

8.4.1、配置:

Spring xml:

 

SpringBoot:

@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}

8.4.2、注解实体字段

第一步,为表添加version字段,并且设置初始值为1:

ALTER TABLE `tb_user`
ADD COLUMN `version` int(10) NULL AFTER `email`;
UPDATE `tb_user` SET `version`='1';

第二步,为User实体对象添加version字段,并且添加@Version注解:

@Version
private Integer version;

8.4.3、测试

@Test
public void testUpdate(){
User user = new User();
user.setAge(30);
user.setId(2L);
user.setVersion(1); //获取到version为1
int result = this.userMapper.updateById(user);
System.out.println("result = " + result);
}

??Sql注入器

在MP中,通过AbstractSqlInjector将BaseMapper中的方法注入到了Mybatis容器,这样这些方法才 可以正常执行。 那么,如果我们需要扩充BaseMapper中的方法,又该如何实现呢?

9.1、编写MyBaseMapper

public interface MyBaseMapper extends BaseMapper {
List findAll();
}

其他的Mapper都可以继承该Mapper,实现方法的扩展

public interface UserMapper extends MyBaseMapper {
User findById(Long id);
}

9.2、编写MySqlInjictor

如果直接继承AbstractSqlInjector的话,原有的BaseMapper中的方法将失效,所以我们选择继承DefaultSqlInjector 进行扩展。

public class MySqlInjector extends DefaultSqlInjector {
@Override
public List getMethodList() {
List methodList = super.getMethodList();
methodList.add(new FindAll());
// 再扩充自定义的方法
list.add(new FindAll());
return methodList;
}
}

9.3、编写FindAll

public class FindAll extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
String sqlMethod = "findAll";
String sql = "select * from " + tableInfo.getTableName();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql,modelClass);
return this.addSelectMappedStatement(mapperClass, sqlMethod, sqlSource,modelClass, tableInfo);
}
}

9.4、注册到Spring容器

/**
* 自定义SQL注入器
*/
@Bean
public MySqlInjector mySqlInjector(){
return new MySqlInjector();
}

??自动填充功能:

插入或者更新数据时,希望有些字段可以自动填充数据,比如默认密码、version 等。在MP中提供了这样的功能,可以实现自动填充。

10.1、添加@TableField注解

@TableField(fill = FieldFill.INSERT) //插入数据时进行填充
private String password;
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入时填充字段
*/
INSERT,
/**
* 更新时填充字段
*/
UPDATE,
/**
* 插入和更新时填充字段
*/
INSERT_UPDATE
}

10.2、编写MyMetaObjectHandler

@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
Object password = getFieldValByName("password", metaObject);
if(null == password){
//字段为空,可以进行填充
setFieldValByName("password", "123456", metaObject);
}
}
@Override
public void updateFill(MetaObject metaObject) {
}
}

??逻辑删除

逻辑删除就是将数据标记为删除,而并非真正 的物理删除(非DELETE操作),查询时需要携带状态条件,确保被标记的数据不被查询到。这样做的目的就是避免 数据被真正的删除。

11.1、修改表结构

为tb_user表增加deleted字段,用于表示数据是否被删除,1代表删除,0代表未删除。

ALTER TABLE `tb_user`
ADD COLUMN `deleted` int(1) NULL DEFAULT 0 COMMENT '1代表删除,0代表未删除' AFTER`version`;

同时,也修改User实体,增加deleted属性并且添加@TableLogic注解:

@TableLogic
private Integer deleted;

11.2、配置

application.properties:

# 逻辑已删除值(默认为 1)
mybatis-plus.global-config.db-config.logic-delete-value=1
# 逻辑未删除值(默认为 0)
mybatis-plus.global-config.db-config.logic-not-delete-value=0

??通用枚举

解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!

12.1、修改表结构

ALTER TABLE `tb_user`ADD COLUMN `sex` int(1) NULL DEFAULT 1 COMMENT '1-男,2-女' AFTER `deleted`;

12.2、定义枚举

public enum SexEnum implements IEnum {

MAN(1,"男"),WOMAN(2,"女");

private int value;
private String desc;

SexEnum(int value, String desc) {
this.value = value;
this.desc = desc;
}

@Override
public Integer getValue() {
return this.value;
}

@Override
public String toString() {
return "SexEnum{" +
"value=" + value +
", desc='" + desc + '\'' +
'}';
}
}

12.3、配置

# 枚举包扫描
mybatis-plus.type-enums-package=cn.itcast.mp.enums

12.4、修改实体

private SexEnum sex;

12.5、测试

@Test
public void testInsert(){
User user = new User();
user.setName("貂蝉");
user.setUserName("diaochan");
user.setAge(20);
user.setEmail("diaochan@itast.cn");
user.setVersion(1);
user.setSex(SexEnum.WOMAN);
int result = this.userMapper.insert(user);
System.out.println("result = " + result);
}

??代码生成器

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

13.1、创建工程

<?xml version="1.0" encoding="UTF-8"?>


4.0.0


org.springframework.boot
spring-boot-starter-parent
2.1.4.RELEASE


cn.itcast.mp
itcast-mp-generator
1.0-SNAPSHOT



org.springframework.boot
spring-boot-starter-test
test



com.baomidou
mybatis-plus-boot-starter
3.1.1


com.baomidou
mybatis-plus-generator
3.1.1


org.springframework.boot
spring-boot-starter-freemarker



mysql
mysql-connector-java
5.1.47


org.slf4j
slf4j-log4j12





org.springframework.boot
spring-boot-maven-plugin



13.2、代码

/**
*


* mysql 代码生成器演示例子
*


*/
public class MysqlGenerator {
/**
*


* 读取控制台内容
*


*/
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotEmpty(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
/**
* RUN THIS
*/
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("itcast");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://127.0.0.1:3306/mp?
useUnicode=true&useSSL=false&characterEncoding=utf8");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("模块名"));
pc.setParent("cn.itcast.mp.generator");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
List focList = new ArrayList<>();
focList.add(new FileOutConfig("/templates/mapper.xml.ftl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输入文件名称
return projectPath + "/itcast-mpgenerator/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" +
StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
mpg.setTemplate(new TemplateConfig().setXml(null));
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
//
strategy.setSuperEntityClass("com.baomidou.mybatisplus.samples.generator.common.BaseEntity");
strategy.setEntityLombokModel(true);
//
strategy.setSuperControllerClass("com.baomidou.mybatisplus.samples.generator.common.BaseController");
strategy.setInclude(scanner("表名"));
strategy.setSuperEntityColumns("id");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// 选择 freemarker 引擎需要指定如下加,注意 pom 依赖必须有!
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.execute();
}
}

13.3、测试

生成代码

?? MybatisX 快速开发插件

MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。

安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。

功能:

  • Java 与 XML 调回跳转

  • Mapper 方法自动生成 XML