来自MyBatis不一样收获结果的探索之旅-v3.5.9
概述
定义
MyBatis官网 https://mybatis.org/mybatis-3/ 最新版本为3.5.9
MyBatis是一个的ORM框架,支持自定义SQL、存储过程和高级映射。MyBatis对JDBC做了稳定封装使我们不再需要直接操作繁琐JDBC的代码进行参数的手动设置和结果检索。在MyBatis可以使用XML、注解和Java pojo映射到数据库记录。
由于我们Java技术栈程序员对MyBatis都已非常熟悉,本篇我们则主要是从源码的角度去收获不一样MyBatis理解之旅。众所周知JDBC操作核心步骤包括创建连接、创建Statement和执行SQL语句、ResultSet接收结果;MyBatis的ORM核心思想是用于实现面向对象编程语言里不同类型系统的数据之间的转换,我们接下来探索三个问题。
- 如何获取数据库源?
- 如何执行Sql语句?
- 结果集如何处理?
源码探索
JDBC
我们先简单回顾下JDBC操作MySQL数据库的例子
package org.apache.ibatis.itxs;
import java.sql.*;
public class JDBCMain {
  public static void main(String[] args) {
    Connection connection = null;
    Statement st = null;
    ResultSet rs = null;
    try {
      //第一种注册驱动
      DriverManager.registerDriver(new com.mysql.jdbc.Driver());
      //第二种方式也可加载驱动程序
      //Class.forName("com.mysql.jdbc.Driver");
      //2.建立连接,参数一:协议+访问数据库,参数二:用户名,参数三:密码
      connection = DriverManager.getConnection("192.168.50.68:3306/testdb?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull", "test", "");
      //3.创建statement,跟数据库打交道一定需要这个对象
      st = connection.createStatement();
      //4.执行查询
      String sql = "select * from blog";
      rs = st.executeQuery(sql);
      //5.遍历查询每一条记录
      while(rs.next()) {
        int id = rs.getInt("id");
        String title = rs.getString("title");
        String content = rs.getString("content");
        System.out.println("id = " + id + "; title = " + title + "; content = " + content);
      }
      connection.close();
      st.close();
      rs.close();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }
}
搭建源码调试环境
由于我们直接在mybatis源码演示demo,所以需要先将mybatis源码下的pom中mysql-connector-java依赖的test注释掉,
数据库准备Blog表并初始数据,新建resources并标记为资源目录,常见mybatis-config.xml和db.properties文件,创建实体类Blog、还有对应Mapper接口、Mapper xml文件。创建一个main类调试。
Blog表语句
CREATE TABLE `blog` (
  `id` BIGINT NOT NULL,
  `title` VARCHAR(100) DEFAULT NULL,
  `content` VARCHAR(300) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into blog(id,title,content) values(1,'Java','面向对象');
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>
  <properties resource="config/db.properties"></properties>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/apache/ibatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.50.68:3306/testdb?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull
username=test
password=
Blog.java
package org.apache.ibatis.example;
public class Blog {
  private long id;
  private String title;
  private String content;
  public long getId() {
    return id;
  }
  public void setId(long id) {
    this.id = id;
  }
  public String getTitle() {
    return title;
  }
  public void setTitle(String title) {
    this.title = title;
  }
  public String getContent() {
    return content;
  }
  public void setContent(String content) {
    this.content = content;
  }
  @Override
  public String toString() {
    return "Blog{" +
      "id=" + id +
      ", title='" + title + '\'' +
      ", content='" + content + '\'' +
      '}';
  }
}
BlogMapper.java
package org.apache.ibatis.example;
public interface BlogMapper {
  Blog selectBlog111(Long id);
}
BlogMapper.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="org.apache.ibatis.example.BlogMapper">
  <select id="selectBlog" resultType="org.apache.ibatis.example.Blog">
    select * from Blog where id = #{id}
  </select>
</mapper>
Main.java
package org.apache.ibatis.itxs;
import org.apache.ibatis.example.Blog;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.InputStream;
public class Main {
  public static void main(String[] args) {
    String resource = "config/mybatis-config.xml";
    InputStream inputStream = null;
    try {
      inputStream = Resources.getResourceAsStream(resource);
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      try (SqlSession session = sqlSessionFactory.openSession()) {
        Blog blog = session.selectOne("org.apache.ibatis.example.BlogMapper.selectBlog", 1);
        System.out.printf(blog.toString());
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

如何获取数据源
加载数据源配置信息
我们这里采用基于XML配置文件方式来演示,这样方式来学习通过mybatis源码笔者也认为最能得到,是Main类的main方法逐步调试进入mybatis的源码,从下面的堆栈调用链可以看出操作mybatis是从SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)这一行开始,在这里先读取mybatis配置文件数据加载到全局配置Configuration里

首先parseConfiguration(XNode root)这个方法主要是初始化处理mybatis配置文件,我们发现配置文件中的在源码中是放在最前面,这个也解释如果我们把这行放在environments节点后面XML格式校验提示错误。org.apache.ibatis.builder.xml.XMLConfigBuilder中parseConfiguration方法代码如下:
  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }


在org.apache.ibatis.builder.xml.XMLConfigBuilder中environmentsElement(XNode context)这个方法处理environments节点,通过configuration.setEnvironment(environmentBuilder.build())将数据源的配置存储数据源工厂中,便于后续操作sql时候可以从配置类中获取信息进而创建连接。

Configuration可以说是mybatis最重要的一个全局类,mybatis大部分重要对象都存在Configuration里,Environment在Configuration里面,而JDBC的数据源是在Environment类中。mybatis采用工厂模式创建数据源和管理连接池(池化技术),不过在实际项目开发中,我们都会使用阿里的druid或者hikari等三方的连接池。



如何操作JDBC
首先在mybatis源码的pom有包含mysql-connector-java的依赖,上面我们也已把test注释掉,在程序加载时候org.apache.ibatis.datasource.unpooled.UnpooledDataSource类通过静态代码块方式将mysql驱动写入registeredDrivers的ConcurrentHashMap里。

在UnpooledDataSource中doGetConnection方法调用获取连接,在这里我们见到熟悉的JDBC操作代码(DriverManager.getConnection),并返回一个JDBC的连接供后续操作使用。该方法主要做下面三个工作:
- initializeDriver,初始化驱动,如果在前面的静态代码块中没有扫描到需要的数据库驱动,则需要在这里加载需要的驱动。
- 根据配置信息获取一个新的连接。
- 配置创建的连接对象。
  private Connection doGetConnection(Properties properties) throws SQLException {
    initializeDriver();
    Connection connection = DriverManager.getConnection(url, properties);
    configureConnection(connection);
    return connection;
  }
  private synchronized void initializeDriver() throws SQLException {
    if (!registeredDrivers.containsKey(driver)) {
      Class<?> driverType;
      try {
        if (driverClassLoader != null) {
          driverType = Class.forName(driver, true, driverClassLoader);
        } else {
          driverType = Resources.classForName(driver);
        }
        // DriverManager requires the driver to be loaded via the system ClassLoader.
        // http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
        Driver driverInstance = (Driver) driverType.getDeclaredConstructor().newInstance();
        DriverManager.registerDriver(new DriverProxy(driverInstance));
        registeredDrivers.put(driver, driverInstance);
      } catch (Exception e) {
        throw new SQLException("Error setting driver on UnpooledDataSource. Cause: " + e);
      }
    }
  }
  private void configureConnection(Connection conn) throws SQLException {
    if (defaultNetworkTimeout != null) {
      conn.setNetworkTimeout(Executors.newSingleThreadExecutor(), defaultNetworkTimeout);
    }
    if (autoCommit != null && autoCommit != conn.getAutoCommit()) {
      conn.setAutoCommit(autoCommit);
    }
    if (defaultTransactionIsolationLevel != null) {
      conn.setTransactionIsolation(defaultTransactionIsolationLevel);
    }
  }

到此我们知道MyBatis是如何一步步走到获取数据源和JDBC创建连接的过程,到这里第一个问题也已梳理清楚。
如何执行Sql语句
mapper支持类型?
我们接着从上面org.apache.ibatis.builder.xml.XMLConfigBuilder中parseConfiguration(XNode root)方法中的mapperElement(XNode parent)这里可以看到源码对于mapper的四种类型配置支持的优先级和逻辑,可以很清晰看出package优先级最高,且resource、url 、class只能存在一个,否则直接抛出异常。我们这里的例子使用的是resource方式

Mybatis配置文件mapper类型配置使用官网示例如下

mapper.xml内容在哪里?
mapper.xml存储很多操作的信息比如最核心的sql语句,看下堆栈调用链从XMLConfigBuilder->XMLMapperBuilder->XMLStatementBuilder,mybatis有很多这种Builder,每个Builder处理各自的职责。
addMappedStatement:298, MapperBuilderAssistant (org.apache.ibatis.builder)
parseStatementNode:113, XMLStatementBuilder (org.apache.ibatis.builder.xml)
buildStatementFromContext:138, XMLMapperBuilder (org.apache.ibatis.builder.xml)
buildStatementFromContext:131, XMLMapperBuilder (org.apache.ibatis.builder.xml)
configurationElement:121, XMLMapperBuilder (org.apache.ibatis.builder.xml)
parse:95, XMLMapperBuilder (org.apache.ibatis.builder.xml)
mapperElement:379, XMLConfigBuilder (org.apache.ibatis.builder.xml)
parseConfiguration:120, XMLConfigBuilder (org.apache.ibatis.builder.xml)
parse:99, XMLConfigBuilder (org.apache.ibatis.builder.xml)
build:78, SqlSessionFactoryBuilder (org.apache.ibatis.session)
build:64, SqlSessionFactoryBuilder (org.apache.ibatis.session)
main:33, Main (org.apache.ibatis.itxs)
我们这里XMLMapperBuilder则是加载解析mapper节点文件并将数据写入Configuration里面集合成员变量。

XMLStatementBuilder的parseStatementNode则是解析我们mapper.xml文件的内容,将mapper.xml的所有元素拆分并组装成MappedStatement对象,然后通过configuration.addMappedStatement(statement)的put存储到Configuration全局配置中mappedStatements的StrictMap,StrictMap是一个内部类继承HashMap,所以本质也是一个HashMap。
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
  protected static class StrictMap<V> extends HashMap<String, V> {
  }

执行器类型有哪些?
通过堆栈调用链,mybatis在openSession中先创建执行器,
newExecutor:691, Configuration (org.apache.ibatis.session)
openSessionFromDataSource:96, DefaultSqlSessionFactory (org.apache.ibatis.session.defaults)
openSession:47, DefaultSqlSessionFactory (org.apache.ibatis.session.defaults)
main:34, Main (org.apache.ibatis.itxs) //sqlSessionFactory.openSession()
执行器有SimpleExecutor(简单执行器)、BatchExecutor(批量执行器)、ReuseExecutor(重用执行器),然后对上面三种执行器判断是否需要缓存增强功能,在调试中可以看到cacheEnabled为true,也即是mybatis默认是开启缓存功能的,本篇就不展开研究mybatis的一二级缓存功能。

public enum ExecutorType {
  SIMPLE, REUSE, BATCH
}
Mybatis中的插件允许你针对核心组件接口Executor 、StatementHandler、ParameterHandler、ResultSetHandler中任何一个方法进行拦截调用。而每个Interceptor(拦截的接口)其实是通过JDK的动态代理技术生成的代理类,每当执行这4种接口中的方法时,就会进入拦截方法(具体就是InvocationHandler的invoke()方法)。插件这里采用的是责任链设计模式,很多对象由每一个对象对其下家的引用而连接起来形成一条链,请求在这个链上传递,直到链上的某一个对象决定处理此请求。这里设计到InterceptorChain(拦截链类)、Interceptor(拦截器接口)、Plugin(插件类,实现了InvocationHandler),具体有兴趣可以自己再深入研究。
在哪执行SQL语句
从下面当我们执行session.selectOne查询数据时
query:64, PreparedStatementHandler (org.apache.ibatis.executor.statement)
query:79, RoutingStatementHandler (org.apache.ibatis.executor.statement)
doQuery:63, SimpleExecutor (org.apache.ibatis.executor)
queryFromDatabase:325, BaseExecutor (org.apache.ibatis.executor)
query:156, BaseExecutor (org.apache.ibatis.executor)
query:109, CachingExecutor (org.apache.ibatis.executor)
query:89, CachingExecutor (org.apache.ibatis.executor)
selectList:151, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:145, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectList:140, DefaultSqlSession (org.apache.ibatis.session.defaults)
selectOne:76, DefaultSqlSession (org.apache.ibatis.session.defaults)
main:35, Main (org.apache.ibatis.itxs)  //session.selectOne("org.apache.ibatis.example.BlogMapper.selectBlog", 1);
首先从DefaultSqlSession获取MappedStatement 这里存储了我们在上一步存储的sql语句当然还有其他很多很多的信息,执行器去执行sql语句
MappedStatement ms = configuration.getMappedStatement(statement);
BoundSql boundSql = ms.getBoundSql(parameterObject);
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

//在org.apache.ibatis.executor.SimpleExecutor#doQuery完成sql语句和参数组装,这里包括sql语句解析、参数替换等内容,我们本篇不展开
stmt = prepareStatement(handler, ms.getStatementLog());

最后在org.apache.ibatis.executor.statement.PreparedStatementHandler#query,这里就非常熟悉了,这个是JDBC的PreparedStatement,到这里我们也一步步找到MyBatis使用JDBC执行查询Sql语句最终地方。

如何处理结果集?
我们接着上小节最后地方org.apache.ibatis.executor.resultset.ResultSetHandler#handleResultSets,这个就是mybatis处理结果集核心函数,mybatis提供一个非常重要结果集包装类ResultSetWrapper,这个就是MyBatis实现ORM的重要地方

ResultSetWrapper使用了columnNames、classNames、jdbcTypes三个ArrayList分别存放数据库的列名、Java类型、Mybatis jdbc对应数据库类型。

至此我们前面对MyBatis源码的三个基础问题已经全部弄清楚了。
MyBatis-Plus
概述
MyBatis-Plus官网 https://www.baomidou.com/
MyBatis-Plus(简称 MP)是MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。是MyBatis最好的搭档,就像魂斗罗中的1P、2P,基友搭配,效率翻倍。
简单示例
我们在企业数据库应用开发为提升效率更多会使用MyBatis-Plus,MyBatis-Plus宗旨就是为了简化开发而生,只做增强,简单配置就可以使用单表的CRUD,还支持丰富功能如代码生成、自动分页、逻辑删除、自动填充。MyBatis-Plus提供非常详细功能使用示例,需全面学习伙伴可自行前往官网,我们这里以一个简单Mybatis-Plus整合Spring Boot的实例抛出一个引子。
创建用户信息表:
CREATE TABLE user_info
(
   id                   INT NOT NULL AUTO_INCREMENT COMMENT '主键唯一ID',
   user_name            VARCHAR(20) COMMENT '名称',
   age                  SMALLINT COMMENT '年龄',
   create_time          DATETIME COMMENT '创建时间',
   update_time          DATETIME COMMENT '更新时间',
   VERSION              INT COMMENT '版本号',
   deleted              INT COMMENT '删除标识(0:正常,1:删除)',
   PRIMARY KEY (id)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
pom文件内容
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         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">
    <modelVersion>4.0.0</modelVersion>
    <groupId>cn.itxs</groupId>
    <artifactId>mybatis-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <repositories>
        <!--阿里云仓库-->
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
        <!--快照版本使用,正式版本无需添加此仓库-->
        <repository>
            <id>snapshots</id>
            <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
        </repository>
    </repositories>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
    </parent>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>1.8</java.version>
        <druid.version>1.2.8</druid.version>
        <mysql.version>8.0.27</mysql.version>
        <lombok.version>1.18.22</lombok.version>
        <mybatis-plus.version>3.5.0</mybatis-plus.version>
        <mybatis-plus-generator.version>3.5.1</mybatis-plus-generator.version>
        <freemarker.version>2.3.31</freemarker.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus-generator.version}</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>${freemarker.version}</version>
        </dependency>
    </dependencies>
</project>
application.yml内容
spring:
  application:
    name: mybatis-plus-demo
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://192.168.50.95:3308/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8
    username: root
    password: 123456
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      max-active: 1000
      min-idle: 5
      initial-size: 10
mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: on
    call-setters-on-nulls: on
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
MyBatisPlusConfig.java
package cn.itxs.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@MapperScan("cn.itxs.mapper")
@EnableTransactionManagement
@Configuration
public class MyBatisPlusConfig {
    /**
     * 配置新版乐观锁插件,新版分页插件
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
        //乐观锁插件
        mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        //分页插件
        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return mybatisPlusInterceptor;
    }
}
MyMetaObjectHandler.java
package cn.itxs.handle;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
 * 用于对数据库表中实现记录的创建时间和修改时间自动填充实现类
 */
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
    //插入记录时填充策略
    @Override
    public void insertFill(MetaObject metaObject) {
        log.info("start insert fill.....");
        this.setFieldValByName("createTime",new Date(),metaObject);
        this.setFieldValByName("updateTime",new Date(),metaObject);
        this.setFieldValByName("modifyTime",new Date(),metaObject);
    }
    //更新记录时填充策略
    @Override
    public void updateFill(MetaObject metaObject) {
        log.info("start update fill.....");
        this.setFieldValByName("updateTime",new Date(),metaObject);
        this.setFieldValByName("modifyTime",new Date(),metaObject);
    }
}
UserInfo.java
package cn.itxs.pojo;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class UserInfo {
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    private String userName;
    private Short age;
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    @Version
    private Integer version;
    @TableLogic
    private Integer deleted;
}
UserInfoMapper.java
package cn.itxs.mapper;
import cn.itxs.pojo.UserInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface UserInfoMapper extends BaseMapper<UserInfo> {
}
加下来的service和serviceImpl文件是为了使用提供批量插入功能,上面UserInfoMapper已经提供很多常用增删改查的功能了。
UserInfoService.java
package cn.itxs.service;
import cn.itxs.pojo.UserInfo;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserInfoService extends IService<UserInfo> {
}
UserInfoServiceImpl.java
package cn.itxs.service.impl;
import cn.itxs.mapper.UserInfoMapper;
import cn.itxs.pojo.UserInfo;
import cn.itxs.service.UserInfoService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {
}
MainAppllication.java
package cn.itxs;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MainAppllication {
    public static void main(String[] args) {
        SpringApplication.run(MainAppllication.class, args);
    }
}
最后我们创建一个测试类,常见的添加、默认值填充、乐观锁修改、逻辑删除、批量添加、条件查询、条件修改、条件删除等功能
MyBatisPlusTest.java
package cn.itxs;
import cn.itxs.mapper.UserInfoMapper;
import cn.itxs.pojo.UserInfo;
import cn.itxs.service.UserInfoService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
@SpringBootTest
@Slf4j
@SuppressWarnings("all")
public class MyBatisPlusTest {
    @Autowired
    UserInfoMapper userInfoMapper;
    @Autowired
    UserInfoService userInfoService;
    @Test
    void addUserInfo(){
        int result = userInfoMapper.insert(UserInfo.builder().userName("张三").age((short)22).version(1).deleted(0).build());
        log.info("result={}",result);
    }
    @Test
    void updateUserInfo(){
        UserInfo userInfo = new UserInfo();
        userInfo.setId(3);
        userInfo.setUserName("张三三");
        userInfo.setAge((short)26);
        int result = userInfoMapper.updateById(userInfo);
        log.info("result={}",result);
    }
    @Test
    void updateUserInfoByVersion(){
        UserInfo userInfo = userInfoMapper.selectById(3);
        userInfo.setUserName("张三丰");
        userInfo.setAge((short)23);
        int result = userInfoMapper.updateById(userInfo);
        log.info("result={}",result);
    }
    @Test
    void insertUserInfoBatch() {
        Random random =new Random();
        List<UserInfo> userInfos = new ArrayList<>(1000);
        for (int i=1;i<1000;i++){
            UserInfo userInfo = new UserInfo();
            userInfo.setUserName("李四"+i);
            userInfo.setAge((short)(random.nextInt(100)+1));
            userInfo.setVersion(1);
            userInfo.setDeleted(0);
            userInfos.add(userInfo);
        }
        boolean result = userInfoService.saveBatch(userInfos, 100);
        log.info("result={}",result);
    }
    @Test
    public void SelectAll() {
        List<UserInfo> userInfos = userInfoMapper.selectList(null);
        userInfos.forEach(System.out::println);
    }
    @Test
    void selectUserInfoByQuery() {
        QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda().gt(UserInfo::getId,300);
        queryWrapper.lambda().eq(UserInfo::getAge,50);
        List<UserInfo> userInfos = userInfoMapper.selectList(queryWrapper);
        userInfos.forEach(System.out::println);
    }
    @Test
    void updateUserInfoByQuery() {
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().set(UserInfo::getAge,30)
                .gt(UserInfo::getId ,800);
        int result = userInfoMapper.update(null,updateWrapper);
        log.info("result={}",result);
    }
    @Test
    void updateUserInfoByQuerySql() {
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.set("user_name","age30username");
        updateWrapper.apply("age = 30");
        int result = userInfoMapper.update(null,updateWrapper);
        log.info("result={}",result);
    }
    @Test
    void deleteUserInfoByQuery() {
        UpdateWrapper<UserInfo> updateWrapper = new UpdateWrapper<>();
        updateWrapper.lambda().gt(UserInfo::getId ,900);
        int result = userInfoMapper.delete(updateWrapper);
        log.info("result={}",result);
    }
}
示例运行如下

代码生成器
上一小节我们已经引入依赖mybatis-plus-generator和freemarker模板引擎,详细参数可以查阅官网
快速生成
package cn.itxs.generator;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class CodeGererator {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://192.168.50.95:3308/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "123456")
                .globalConfig(builder -> {
                    builder.author("itxs") // 设置作者
                            .enableSwagger() // 开启 swagger 模式
                            .fileOverride() // 覆盖已生成文件
                            .outputDir("D://code_generator"); // 指定输出目录,一般设置我们工程项目下
                })
                .packageConfig(builder -> {
                    builder.parent("cn.itxs.generator") // 设置父包名
                            .moduleName("system") // 设置父包模块名
                            .entity("po")
                            .service("service")
                            .serviceImpl("service.impl")
                            .mapper("mapper")
                            .controller("controller")
                            .pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://code_generator")); // 设置mapperXml生成路径,指定输出目录,一般设置我们工程项目下
                })
                .strategyConfig(builder -> {
                    builder.addInclude("user_info") // 设置需要生成的表名
                            .addTablePrefix("t_", "c_"); // 设置过滤表前缀
                })
                .templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
                .execute();
    }
}
交互式生成
package cn.itxs.generator;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.fill.Column;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class InteractiveCodeGererator {
    public static void main(String[] args) {
        FastAutoGenerator.create("jdbc:mysql://192.168.50.95:3308/test?useSSL=false&useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT%2B8", "root", "123456")
                // 全局配置
                .globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride())
                // 包配置
                .packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
                // 策略配置
                .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
                        .controllerBuilder().enableRestStyle().enableHyphenStyle()
                        .entityBuilder().enableLombok().addTableFills(
                                new Column("create_time", FieldFill.INSERT)
                        ).build())
                /*
                    模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
                   .templateEngine(new BeetlTemplateEngine())
                   .templateEngine(new FreemarkerTemplateEngine())
                 */
                .execute();
    }
    // 处理 all 情况
    protected static List<String> getTables(String tables) {
        return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
    }
}
苞米豆生态圈
概述
MyBatis-Plus由苞米豆开发的,而苞米豆生态圈还有很多组件。我们本篇再简单了解MybatisX和Mybatis-Mate。
- MybatisX (opens new window) - 一款全免费且强大的 IDEA 插件,支持跳转,自动补全生成 SQL,代码生成。
- Mybatis-Mate (opens new window) - 为 MyBatis-Plus 企业级模块,支持分库分表、数据审计、字段加密、数据绑定、数据权限、表结构自动生成 SQL 维护等高级特性。
- Dynamic-Datasource (opens new window) - 基于 SpringBoot 的多数据源组件,功能强悍,支持 Seata 分布式事务。
- Shuan (opens new window) - 基于 Pac4J-JWT 的 WEB 安全组件, 快速集成。
- Kisso (opens new window) - 基于 Cookie 的单点登录组件。
- Lock4j (opens new window) - 基于 SpringBoot 同时支持 RedisTemplate、Redission、Zookeeper 的分布式锁组件。
- Kaptcha (opens new window) - 基于 SpringBoot 和 Google Kaptcha 的简单验证码组件,简单验证码就选它。
- Aizuda 爱组搭 (opens new window) - 低代码开发平台组件库。
MybatisX
MybatisX是一款全免费且强大的 IDEA 插件,支持跳转,自动补全生成 SQL,代码生成。在插件市场下载MybatisX,安装即可使用

快速体现跳转功能,可以从mapper接口中的接口方法直接跳转到对应xml文件的代码部分

代码快速补全功能

Mybatis-Mate
Mybatis-Mate为 MyBatis-Plus 企业级模块,支持分库分表、数据审计、字段加密、数据绑定、数据权限、表结构自动生成 SQL 维护等高级特性等。
https://gitee.com/baomidou/mybatis-mate-examples 这里提供企业级很多使用场景示例,我们下载源码工程后测试数据脱敏的例子,我将数据库修改为mysql,端口为8088

访问测试页面UserController的几个测试页面,针对用户表用户名、手机、邮箱的脱敏的结果如下,有兴趣可以多研究其他高级功能




**本人博客网站 **IT小神 www.itxiaoshen.com
来自MyBatis不一样收获结果的探索之旅-v3.5.9的更多相关文章
- 【Linux探索之旅】开宗明义+第一部分第一课:什么是Linux?
		内容简介 1.课程大纲 2.第一部分第一课:什么是Linux? 3.第一部分第二课预告:下载Linux,免费的噢! 开宗明义 我们总听到别人说:Linux挺复杂的,是给那些追求逼格的程序员用的.咱 ... 
- 【Linux探索之旅】第二部分第二课:命令行,世界尽在掌握
		内容简介 1.第二部分第二课:命令行,世界尽在掌握 2.第二部分第三课预告:文件和目录,组织不会亏待你 命令行,世界尽在掌握 今天的标题是不是有点霸气侧漏呢? 读者:“小编,你为什么每次都要起这么非主 ... 
- 【Linux探索之旅】第一部分第五课:Unity桌面,人生若只如初见
		内容简介 1.第一部分第五课:Unity桌面,人生若只如初见 2.第一部分第六课预告:Linux如何安装在虚拟机中 Unity桌面,人生若只如初见 不容易啊,经过了前几课的学习,我们认识了Linux是 ... 
- 【Web探索之旅】第四部分:Web程序员
		内容简介 1.第四部分第一课:什么是Web程序员? 2.第四部分第二课:如何成为Web程序员? 3.第四部分第三课:成为优秀Web程序员的秘诀 第四部分:Web程序员(完结篇) 大家好.终于来到了[W ... 
- 【Web探索之旅】第三部分第一课:服务器
		内容简介 1.第三部分第一课:服务器 2.第三部分第二课预告:IP地址和域名 第三部分第一课:服务器 大家好,欢迎来到[Web探索之旅]的第三部分.这一部分有不少原理,还是很重要的. 这一部分我们会着 ... 
- 【Web探索之旅】第三部分第二课:IP地址和域名
		内容简介 1.第三部分第二课:IP地址和域名 2.第三部分第三课预告:协议 第三部分第二课:IP地址和域名 上一课我们说了在Web之中,全球各地有无数台机器,有些充当客户机,有些作为服务器. 那么这些 ... 
- 【Web探索之旅】第一部分:什么是Web?
		内容简介 1.Web探索之旅:开宗明义 2.第一部分第一课:什么是Web? 3.第一部分第二课:Web,服务和云 4.第一部分第三课:Web的诞生史 Web探索之旅:开宗明义 大家好. 我们这个系列课 ... 
- 【C++探索之旅】第一部分第三课:第一个C++程序
		内容简介 1.第一部分第三课:第一个C++程序 2.第一部分第四课预告:内存的使用 第一个C++程序 经过上两课之后,我们已经知道了什么是编程,编程的语言,编程的必要软件,C++是什么,我们也安装了适 ... 
- 【C语言探索之旅】 第三部分第二课:SDL开发游戏之创建窗口和画布
		内容简介 1.第三部分第二课: SDL开发游戏之创建窗口和画布 2.第三部分第三课预告: SDL开发游戏之显示图像 第三部分第二课:SDL开发游戏之创建窗口和画布 在上一课中,我们对SDL这个开源库做 ... 
随机推荐
- velocity示例
			创建maven项目 pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns ... 
- MFC入门示例之访问对话框控件的7种方法
			方法一: 1 //方法一 2 void CMFCApplication2Dlg::OnBnClickedButton1() 3 { 4 int num1, num2, num3; 5 TCHAR ch ... 
- 搭建mybatis开发环境
			1.创建工程 <groupId>com.hope</groupId> <artifactId>day01_eesy_01mybatis</artifa ... 
- 第三届“传智杯”全国大学生IT技能大赛(初赛A组)题解
			留念 C - 志愿者 排序..按照题目规则说的排就可以.wa了两发我太菜了qwq #include<bits/stdc++.h> using namespace std; const in ... 
- 【死磕Java并发】—–深入分析volatile的实现原理
			通过前面一章我们了解了synchronized是一个重量级的锁,虽然JVM对它做了很多优化,而下面介绍的volatile则是轻量级的synchronized.如果一个变量使用volatile,则它比使 ... 
- python   银行管理系统
			这是一个使用python连接mysql的例子 涉及到类的使用 import pymysql import function as f def mysql(): db=pymysql.connect(h ... 
- [Elasticsearch] ES 的Mapping 设计在实际场景中应用
			背景 项目中有个需求是需要几个字段作为标签,统计各个标签的文档数量,同时支持分词后的全文检索功能. 原有的mapping设计: curl -XPUT http://ip:9200/meta_es_me ... 
- Vue2使用Axios发起请求教程详细
			当你看到该文章时希望你已知晓什么是跨域请求以及跨域请求的处理,本文不会赘述 本文后台基于Springboot2.3进行搭建,Controller中不会写任何业务逻辑仅用于配合前端调试 Controll ... 
- c++11之 algorithm 算法库新增 minmax_element同时计算最大值和最小值
			0.时刻提醒自己 Note: vector的释放 1. minmax_element 功能 寻找范围 [first, last) 中最小和最大的元素. 2. 头文件 #include <algo ... 
- 【LeetCode】987. Vertical Order Traversal of a Binary Tree 解题报告(C++ & Python)
			作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 DFS BFS 日期 题目地址:https://le ... 
