一、SpringBoot+MybatisPlus+P6spy环境搭建

一、SpringBoot+MybatisPlus+P6spy环境搭建

练习代码已上传到CSDN资源中

SpringBoot 整合Mybatis-plus

一、环境搭建

一、SpringBoot+MybatisPlus+P6spy环境搭建

1.pom.xml依赖

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.mybatisplus</groupId>
    <artifactId>mybatisplugsdemo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>mybatisplugsdemo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>druid
        </dependency>
        <dependency>
            <groupId>p6spy</groupId>
            <artifactId>p6spy</artifactId>
            <version>3.8.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.application.yml

spring:
  datasource:
    driver-class-name: com.p6spy.engine.spy.P6SpyDriver
    url: jdbc:p6spy:mysql://192.168.1.2:3306/mp?serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=true
    username: root
    password: root
  application:
    name: mybatisplus

3.spy.properties配置文件

module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.p6spy.engine.spy.appender.MultiLineFormat
# 使用日志系统记录sql
#appender=com.p6spy.engine.logging.appender.StdoutLogger
appender=com.p6spy.engine.spy.appender.StdoutLogger
#appender=com.p6spy.engine.logging.appender.FileLogger
#logfile  = d:/spy.log
## 配置记录Log例外
#excludecategories=info,debug,result,batc,resultset
# 设置使用p6spy driver来做代理
deregisterdrivers=true
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动
driverlist=com.mysql.cj.jdbc.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 秒
outagedetectioninterval=2

4.创建实体类

//TODO 实体类需要注意
/* 1. @TableName 2.TableId * */
@Data
@TableName("tbl_employee")
public class Employee implements Serializable {
   
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String lastName;
    private String email;
    private Integer gender;
    private Integer age;

}

sql语句

-- 创建库
CREATE DATABASE mp;
-- 使用库
USE mp;
-- 创建表
CREATE TABLE tbl_employee(
 id INT(11) PRIMARY KEY AUTO_INCREMENT,
 last_name VARCHAR(50),
 email VARCHAR(50),
 gender CHAR(1),
 age INT
);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Tom','tom@atguigu.com',1,22);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Jerry','jerry@atguigu.com',0,25);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('Black','black@atguigu.com',1,30);
INSERT INTO tbl_employee(last_name,email,gender,age) VALUES('White','white@atguigu.com',0,35);

5.创建Mapper

// TODO mapper的写法: mapper 继承 BaseMapper<T> T 实体类

public interface EmployeeMapper extends BaseMapper<Employee> {
   
}

6.启动类

@SpringBootApplication
@MapperScan("com.mybatisplus.mybatisplugsdemo.mapper")
public class MybatisplugsdemoApplication {
   

    public static void main(String[] args) {
   
        SpringApplication.run(MybatisplugsdemoApplication.class, args);
    }

}
// 注意:Springboot启动类放的位置,扫描的是当前包及子包。一般建议放到com.xxx
/* 比如: com.mybatisplus 存放启动类 com.mybatisplus.controller com.mybatisplus.mapper */

7.测试类

@RunWith(SpringRunner.class)
@SpringBootTest
class MybatisplugsdemoApplicationTests {
   

    @Test
    void contextLoads() {
   
    }

}
public class Test01 extends MybatisplugsdemoApplicationTests {
   

    @Resource
   private EmployeeMapper employeeMapper;

    @Test
    public void fn(){
   
        /** * 查询所有数据 */
        employeeMapper.selectList(null).forEach(System.out::println);
    }
}

只需要创建 EmployeeMapper 接口, 并继承 BaseMapper 接口.这就是使用 MP需要完成的所有操作,甚至不需要创建 SQL 映射文件。

二、BaseMapper 、Wrapper源码

1.BaseMapper.java

/** * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能 * <p>这个 Mapper 支持 id 泛型</p> * * @author hubin * @since 2016-01-23 */
public interface BaseMapper<T> extends Mapper<T> {
   

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

    /** * 根据 ID 删除 * * @param id 主键ID */
    int deleteById(Serializable id);

    /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /** * 根据 entity 条件,删除记录 * * @param wrapper 实体对象封装操作类(可以为 null) */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

    /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表(不能为 null 以及 empty) */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /** * 根据 ID 修改 * * @param entity 实体对象 */
    int updateById(@Param(Constants.ENTITY) T entity);

    /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 (set 条件值,可以为 null) * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /** * 根据 ID 查询 * * @param id 主键ID */
    T selectById(Serializable id);

    /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表(不能为 null 以及 empty) */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /** * 查询(根据 columnMap 条件) * * @param columnMap 表字段 map 对象 */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

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

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

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

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

    /** * 根据 Wrapper 条件,查询全部记录 * <p>注意: 只返回第一个字段的值</p> * * @param queryWrapper 实体对象封装操作类(可以为 null) */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /** * 根据 entity 条件,查询全部记录(并翻页) * * @param page 分页查询条件(可以为 RowBounds.DEFAULT) * @param queryWrapper 实体对象封装操作类(可以为 null) */
    IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /** * 根据 Wrapper 条件,查询全部记录(并翻页) * * @param page 分页查询条件 * @param queryWrapper 实体对象封装操作类 */
    IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

Wrapper 表示包装

2.Wrapper.java

/** * 条件构造抽象类 * * @author hubin * @since 2018-05-25 */
@SuppressWarnings("all")
public abstract class Wrapper<T> implements ISqlSegment {
   

    /** * 实体对象(子类实现) * * @return 泛型 T */
    public abstract T getEntity();

    public String getSqlSelect() {
   
        return null;
    }

    public String getSqlSet() {
   
        return null;
    }

    public String getSqlComment() {
   
        return null;
    }

    /** * 获取 MergeSegments */
    public abstract MergeSegments getExpression();

    /** * 获取自定义SQL 简化自定义XML复杂情况 * <p>使用方法</p> * <p>`自定义sql` + ${ew.customSqlSegment}</p> * <p>1.逻辑删除需要自己拼接条件 (之前自定义也同样)</p> * <p>2.不支持wrapper中附带实体的情况 (wrapper自带实体会更麻烦)</p> * <p>3.用法 ${ew.customSqlSegment} (不需要where标签包裹,切记!)</p> * <p>4.ew是wrapper定义别名,可自行替换</p> */
    public String getCustomSqlSegment() {
   
        MergeSegments expression = getExpression();
        if (Objects.nonNull(expression)) {
   
            NormalSegmentList normal = expression.getNormal();
            String sqlSegment = getSqlSegment();
            if (StringUtils.isNotEmpty(sqlSegment)) {
   
                if (normal.isEmpty()) {
   
                    return sqlSegment;
                } else {
   
                    return Constants.WHERE + StringPool.SPACE + sqlSegment;
                }
            }
        }
        return StringPool.EMPTY;
    }

    /** * 查询条件为空(包含entity) */
    public boolean isEmptyOfWhere() {
   
        return isEmptyOfNormal() && isEmptyOfEntity();
    }

    /** * 查询条件不为空(包含entity) */
    public boolean nonEmptyOfWhere() {
   
        return !isEmptyOfWhere();
    }

    /** * 查询条件为空(不包含entity) */
    public boolean isEmptyOfNormal() {
   
        return CollectionUtils.isEmpty(getExpression().getNormal());
    }

    /** * 查询条件为空(不包含entity) */
    public boolean nonEmptyOfNormal() {
   
        return !isEmptyOfNormal();
    }

    /** * 深层实体判断属性 * * @return true 不为空 */
    public boolean nonEmptyOfEntity() {
   
        T entity = getEntity();
        if (entity == null) {
   
            return false;
        }
        TableInfo tableInfo = TableInfoHelper.getTableInfo(entity.getClass());
        if (tableInfo == null) {
   
            return false;
        }
        if (tableInfo.getFieldList().stream().anyMatch(e -> fieldStrategyMatch(entity, e))) {
   
            return true;
        }
        return StringUtils.isNotEmpty(tableInfo.getKeyProperty()) ? Objects.nonNull(ReflectionKit.getMethodValue(entity, tableInfo.getKeyProperty())) : false;
    }

    /** * 根据实体FieldStrategy属性来决定判断逻辑 */
    private boolean fieldStrategyMatch(T entity, TableFieldInfo e) {
   
        switch (e.getWhereStrategy()) {
   
            case NOT_NULL:
                return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
            case IGNORED:
                return true;
            case NOT_EMPTY:
                return StringUtils.checkValNotNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
            case NEVER:
                return false;
            default:
                return Objects.nonNull(ReflectionKit.getMethodValue(entity, e.getProperty()));
        }
    }

    /** * 深层实体判断属性 * * @return true 为空 */
    public boolean isEmptyOfEntity() {
   
        return !nonEmptyOfEntity();
    }
}

三、CRUD操作

四、条件查询器

3.x版本以前 条件查询器EntityWrapper.java ,3.x版本改为QueryWrapper.java

QueryChainWrapper<T> extends AbstractChainWrapper

一、SpringBoot+MybatisPlus+P6spy环境搭建

//查询年龄在18-50 之间且性别为男姓名为tom的所有用户
employeerMapper.selectPage(new Page<Employeer>(1,4),new QueryWrapper<Employee>().between("age",18,50).eq("gender",1).eq("last_name","tom")).forEach(System.out::println);

AbstractWrapper.java

/** * 所有包装类都继承此抽象类,此抽象类代理了大部分生成 where 条件的方法 * <li> 泛型: Children ,表示子类 </li> * <li> 泛型: Param ,表示子类所包装的具体 Wrapper 类型 </li> * * @author miemie * @since 2018-12-19 */
@SuppressWarnings({
   "serial", "unchecked"})
public abstract class AbstractChainWrapper<T, R, Children extends AbstractChainWrapper<T, R, Children, Param>, Param>
    extends Wrapper<T> implements Compare<Children, R>, Func<Children, R>, Join<Children>, Nested<Param, Children> {
   

    protected final Children typedThis = (Children) this;
    /** * 子类所包装的具体 Wrapper 类型 */
    protected Param wrapperChildren;

    /** * 必须的构造函数 */
    public AbstractChainWrapper() {
   
    }

    @SuppressWarnings("rawtypes")
    public AbstractWrapper getWrapper() {
   
        return (AbstractWrapper) wrapperChildren;
    }

    @Override
    public T getEntity() {
   
        throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getEntity");
    }

    @Override
    public MergeSegments getExpression() {
   
        throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getExpression");
    }

    @Override
    public String getCustomSqlSegment() {
   
        throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getCustomSqlSegment");
    }

    @Override
    public <V> Children allEq(boolean condition, Map<R, V> params, boolean null2IsNull) {
   
        getWrapper().allEq(condition, params, null2IsNull);
        return typedThis;
    }

    @Override
    public <V> Children allEq(boolean condition, BiPredicate<R, V> filter, Map<R, V> params, boolean null2IsNull) {
   
        getWrapper().allEq(condition, filter, params, null2IsNull);
        return typedThis;
    }

    @Override
    public Children eq(boolean condition, R column, Object val) {
   
        getWrapper().eq(condition, column, val);
        return typedThis;
    }

    @Override
    public Children ne(boolean condition, R column, Object val) {
   
        getWrapper().ne(condition, column, val);
        return typedThis;
    }

    @Override
    public Children gt(boolean condition, R column, Object val) {
   
        getWrapper().gt(condition, column, val);
        return typedThis;
    }

    @Override
    public Children ge(boolean condition, R column, Object val) {
   
        getWrapper().ge(condition, column, val);
        return typedThis;
    }

    @Override
    public Children lt(boolean condition, R column, Object val) {
   
        getWrapper().lt(condition, column, val);
        return typedThis;
    }

    @Override
    public Children le(boolean condition, R column, Object val) {
   
        getWrapper().le(condition, column, val);
        return typedThis;
    }

    @Override
    public Children between(boolean condition, R column, Object val1, Object val2) {
   
        getWrapper().between(condition, column, val1, val2);
        return typedThis;
    }

    @Override
    public Children notBetween(boolean condition, R column, Object val1, Object val2) {
   
        getWrapper().notBetween(condition, column, val1, val2);
        return typedThis;
    }

    @Override
    public Children like(boolean condition, R column, Object val) {
   
        getWrapper().like(condition, column, val);
        return typedThis;
    }

    @Override
    public Children notLike(boolean condition, R column, Object val) {
   
        getWrapper().notLike(condition, column, val);
        return typedThis;
    }

    @Override
    public Children likeLeft(boolean condition, R column, Object val) {
   
        getWrapper().likeLeft(condition, column, val);
        return typedThis;
    }

    @Override
    public Children likeRight(boolean condition, R column, Object val) {
   
        getWrapper().likeRight(condition, column, val);
        return typedThis;
    }

    @Override
    public Children isNull(boolean condition, R column) {
   
        getWrapper().isNull(condition, column);
        return typedThis;
    }

    @Override
    public Children isNotNull(boolean condition, R column) {
   
        getWrapper().isNotNull(condition, column);
        return typedThis;
    }

    @Override
    public Children in(boolean condition, R column, Collection<?> coll) {
   
        getWrapper().in(condition, column, coll);
        return typedThis;
    }

    @Override
    public Children notIn(boolean condition, R column, Collection<?> coll) {
   
        getWrapper().notIn(condition, column, coll);
        return typedThis;
    }

    @Override
    public Children inSql(boolean condition, R column, String inValue) {
   
        getWrapper().inSql(condition, column, inValue);
        return typedThis;
    }

    @Override
    public Children notInSql(boolean condition, R column, String inValue) {
   
        getWrapper().notInSql(condition, column, inValue);
        return typedThis;
    }

    @Override
    public Children groupBy(boolean condition, R... columns) {
   
        getWrapper().groupBy(condition, columns);
        return typedThis;
    }

    @Override
    public Children orderBy(boolean condition, boolean isAsc, R... columns) {
   
        getWrapper().orderBy(condition, isAsc, columns);
        return typedThis;
    }

    @Override
    public Children having(boolean condition, String sqlHaving, Object... params) {
   
        getWrapper().having(condition, sqlHaving, params);
        return typedThis;
    }

    @Override
    public Children or(boolean condition) {
   
        getWrapper().or(condition);
        return typedThis;
    }

    @Override
    public Children apply(boolean condition, String applySql, Object... value) {
   
        getWrapper().apply(condition, applySql, value);
        return typedThis;
    }

    @Override
    public Children last(boolean condition, String lastSql) {
   
        getWrapper().last(condition, lastSql);
        return typedThis;
    }

    @Override
    public Children comment(boolean condition, String comment) {
   
        getWrapper().comment(condition, comment);
        return typedThis;
    }

    @Override
    public Children exists(boolean condition, String existsSql) {
   
        getWrapper().exists(condition, existsSql);
        return typedThis;
    }

    @Override
    public Children notExists(boolean condition, String notExistsSql) {
   
        getWrapper().notExists(condition, notExistsSql);
        return typedThis;
    }

    @Override
    public Children and(boolean condition, Consumer<Param> consumer) {
   
        getWrapper().and(condition, consumer);
        return typedThis;
    }

    @Override
    public Children or(boolean condition, Consumer<Param> consumer) {
   
        getWrapper().or(condition, consumer);
        return typedThis;
    }

    @Override
    public Children nested(boolean condition, Consumer<Param> consumer) {
   
        getWrapper().nested(condition, consumer);
        return typedThis;
    }

    @Override
    public String getSqlSegment() {
   
        throw ExceptionUtils.mpe("can not use this method for \"%s\"", "getSqlSegment");
    }
}

QueryWrapper.java


/** * Entity 对象封装操作类 * * @author hubin miemie HCL * @since 2018-05-25 */
@SuppressWarnings("serial")
public class QueryWrapper<T> extends AbstractWrapper<T, String, QueryWrapper<T>>
    implements Query<QueryWrapper<T>, T, String> {
   

    /** * 查询字段 */
    private SharedString sqlSelect = new SharedString();

    public QueryWrapper() {
   
        this(null);
    }

    public QueryWrapper(T entity) {
   
        super.setEntity(entity);
        super.initNeed();
    }

    public QueryWrapper(T entity, String... columns) {
   
        super.setEntity(entity);
        super.initNeed();
        this.select(columns);
    }

    /** * 非对外公开的构造方法,只用于生产嵌套 sql * * @param entityClass 本不应该需要的 */
    private QueryWrapper(T entity, Class<T> entityClass, AtomicInteger paramNameSeq,
                         Map<String, Object> paramNameValuePairs, MergeSegments mergeSegments,
                         SharedString lastSql, SharedString sqlComment) {
   
        super.setEntity(entity);
        this.entityClass = entityClass;
        this.paramNameSeq = paramNameSeq;
        this.paramNameValuePairs = paramNameValuePairs;
        this.expression = mergeSegments;
        this.lastSql = lastSql;
        this.sqlComment = sqlComment;
    }

    @Override
    public QueryWrapper<T> select(String... columns) {
   
        if (ArrayUtils.isNotEmpty(columns)) {
   
            this.sqlSelect.setStringValue(String.join(StringPool.COMMA, columns));
        }
        return typedThis;
    }

    @Override
    public QueryWrapper<T> select(Predicate<TableFieldInfo> predicate) {
   
        return select(entityClass, predicate);
    }

    @Override
    public QueryWrapper<T> select(Class<T> entityClass, Predicate<TableFieldInfo> predicate) {
   
        this.entityClass = entityClass;
        this.sqlSelect.setStringValue(TableInfoHelper.getTableInfo(getCheckEntityClass()).chooseSelect(predicate));
        return typedThis;
    }

    @Override
    public String getSqlSelect() {
   
        return sqlSelect.getStringValue();
    }

    /** * 返回一个支持 lambda 函数写法的 wrapper */
    public LambdaQueryWrapper<T> lambda() {
   
        return new LambdaQueryWrapper<>(entity, entityClass, sqlSelect, paramNameSeq, paramNameValuePairs, expression,
            lastSql, sqlComment);
    }

    /** * 用于生成嵌套 sql * <p> * 故 sqlSelect 不向下传递 * </p> */
    @Override
    protected QueryWrapper<T> instance() {
   
        return new QueryWrapper<>(entity, entityClass, paramNameSeq, paramNameValuePairs, new MergeSegments(),
            SharedString.emptyString(), SharedString.emptyString());
    }
}

查询条件

或者

//或者 or() (gender = ? and last_name like ? or email like ?)
new QueryWrapper().eq("gender",0).like("last_name","老师").or().like("email","a");

//orNew() (Gender = ? and last_name like ?)or(email liek ?) 3.x版本之后都没有这个方法
new QueryWrapper().eq("gender",0).like("last_name","老师").orNew().like("email","a");

条件修改

//修改zx 的年龄为29 

Employee employee = new Employee();
employee.setAge(29);
int update = employeeMapper.update(employee, new UpdateWrapper<Employee>().eq("last_name", "zx"));
/* UPDATE tbl_employee SET age=? WHERE (last_name = ?) UPDATE tbl_employee SET age=29 WHERE (last_name = 'zx'); */

排序

//查询性别为 0 ,根据age排序 asc/desc ,简单分页
 List<Employee> employees = employeeMapper.selectList(new QueryWrapper<Employee>()
                .eq("gender", 0)
                .orderByAsc("age")
                //.orderByDesc("age","gender")
                //last() 方法,有sql注入的风险
                .last("limit 1,2")
);
/* SELECT id,last_name,gender,age,email FROM tbl_employee WHERE (gender = ?) ORDER BY age ASC limit 1,2 写法有很多种,根据自己习惯进行选择 */

五、AR模式【不推荐】

仅仅需要让实体类继承 Model 类且实现主键指定方法,即可开启 AR 之旅.这个是版本在3.x以前的写法,3.x版本以后的写法就是直接继承Model<T>

@Data
@TableName("tb_user")
public class User extends Model<User> {
   
    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer age;

	// mybatisplus版本在3.x以后就不用写
    protected Serializable pkVal() {
   
        return this.id; }
}
 CREATE TABLE tb_user(
	id INT PRIMARY KEY AUTO_INCREMENT,
	`name` VARCHAR(50),
	age INT 
 );

错误:Not Found TableInfoCache.

/* mybatisplus activerecord之mybatisplus entity XXX Not Found TableInfoCache. 1.需要重写pkVal()方法 // mybatisplus版本在3.x以后就不用写 2.编写mapper.比如:public interface UserMapper extends BaseMapper<User>{ } */

案例:

public class TestModel  extends MybatisplugsdemoApplicationTests {
   
    //添加
    @Test
    public void fn(){
   
        User user = new User();
        user.setName("marry");
        user.setAge(11);
        user.insert();
    }
    //查询
    @Test
    public void findAll(){
   
        User user = new User();
        user.selectList(null).forEach(System.out::println);
    }
}

六、代码生成器

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

说明

自定义模板有哪些可用参数?Github Gitee AbstractTemplateEngine 类中方法 getObjectMap 返回 objectMap 的所有值都可用

  • 表及字段命名策略选择在 MP 中,我们建议数据库表名 和 表字段名采用驼峰命名方式, 如果采用下划线命名方式 请开启全局下划线开关,如果表名字段名命名方式不一致请注解指定,我们建议最好保持一致(2.3版本以后默认是开启的)。这么做的原因是为了避免在对应实体类时产生的性能损耗,这样字段不用做映射就能直接和实体类对应。当然如果项目里不用考虑这点性能损耗,那么你采用下滑线也是没问题的,只需要在生成代码时配置 dbColumnUnderline 属性就可以

1.MP 的代码生成器默认使用的是 Apache 的 Velocity 模板,当然也可以更换为别的模板技术,例如 freemarker。

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

Strategy 策率

顶级Service层

IService.java

/** * 顶级 Service * * @author hubin * @since 2018-06-23 */
public interface IService<T> {
   

    /** * 插入一条记录(选择字段,策略插入) * * @param entity 实体对象 */
    boolean save(T entity);

    /** * 插入(批量) * * @param entityList 实体对象集合 */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveBatch(Collection<T> entityList) {
   
        return saveBatch(entityList, 1000);
    }

    /** * 插入(批量) * * @param entityList 实体对象集合 * @param batchSize 插入批次数量 */
    boolean saveBatch(Collection<T> entityList, int batchSize);

    /** * 批量修改插入 * * @param entityList 实体对象集合 */
    @Transactional(rollbackFor = Exception.class)
    default boolean saveOrUpdateBatch(Collection<T> entityList) {
   
        return saveOrUpdateBatch(entityList, 1000);
    }

    /** * 批量修改插入 * * @param entityList 实体对象集合 * @param batchSize 每次的数量 */
    boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);

    /** * 根据 ID 删除 * * @param id 主键ID */
    boolean removeById(Serializable id);

    /** * 根据 columnMap 条件,删除记录 * * @param columnMap 表字段 map 对象 */
    boolean removeByMap(Map<String, Object> columnMap);

    /** * 根据 entity 条件,删除记录 * * @param queryWrapper 实体包装类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    boolean remove(Wrapper<T> queryWrapper);

    /** * 删除(根据ID 批量删除) * * @param idList 主键ID列表 */
    boolean removeByIds(Collection<? extends Serializable> idList);

    /** * 根据 ID 选择修改 * * @param entity 实体对象 */
    boolean updateById(T entity);

    /** * 根据 whereEntity 条件,更新记录 * * @param entity 实体对象 * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper} */
    boolean update(T entity, Wrapper<T> updateWrapper);

    /** * 根据 UpdateWrapper 条件,更新记录 需要设置sqlset * * @param updateWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper} */
    default boolean update(Wrapper<T> updateWrapper) {
   
        return update(null, updateWrapper);
    }

    /** * 根据ID 批量更新 * * @param entityList 实体对象集合 */
    @Transactional(rollbackFor = Exception.class)
    default boolean updateBatchById(Collection<T> entityList) {
   
        return updateBatchById(entityList, 1000);
    }

    /** * 根据ID 批量更新 * * @param entityList 实体对象集合 * @param batchSize 更新批次数量 */
    boolean updateBatchById(Collection<T> entityList, int batchSize);

    /** * TableId 注解存在更新记录,否插入一条记录 * * @param entity 实体对象 */
    boolean saveOrUpdate(T entity);

    /** * 根据 ID 查询 * * @param id 主键ID */
    T getById(Serializable id);

    /** * 查询(根据ID 批量查询) * * @param idList 主键ID列表 */
    Collection<T> listByIds(Collection<? extends Serializable> idList);

    /** * 查询(根据 columnMap 条件) * * @param columnMap 表字段 map 对象 */
    Collection<T> listByMap(Map<String, Object> columnMap);

    /** * 根据 Wrapper,查询一条记录 <br/> * <p>结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")</p> * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    default T getOne(Wrapper<T> queryWrapper) {
   
        return getOne(queryWrapper, true);
    }

    /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param throwEx 有多个 result 是否抛出异常 */
    T getOne(Wrapper<T> queryWrapper, boolean throwEx);

    /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    Map<String, Object> getMap(Wrapper<T> queryWrapper);

    /** * 根据 Wrapper,查询一条记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */
    <V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

    /** * 根据 Wrapper 条件,查询总记录数 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    int count(Wrapper<T> queryWrapper);

    /** * 查询总记录数 * * @see Wrappers#emptyWrapper() */
    default int count() {
   
        return count(Wrappers.emptyWrapper());
    }

    /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    List<T> list(Wrapper<T> queryWrapper);

    /** * 查询所有 * * @see Wrappers#emptyWrapper() */
    default List<T> list() {
   
        return list(Wrappers.emptyWrapper());
    }

    /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);

    /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */
    default IPage<T> page(IPage<T> page) {
   
        return page(page, Wrappers.emptyWrapper());
    }

    /** * 查询列表 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);

    /** * 查询所有列表 * * @see Wrappers#emptyWrapper() */
    default List<Map<String, Object>> listMaps() {
   
        return listMaps(Wrappers.emptyWrapper());
    }

    /** * 查询全部记录 */
    default List<Object> listObjs() {
   
        return listObjs(Function.identity());
    }

    /** * 查询全部记录 * * @param mapper 转换函数 */
    default <V> List<V> listObjs(Function<? super Object, V> mapper) {
   
        return listObjs(Wrappers.emptyWrapper(), mapper);
    }

    /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    default List<Object> listObjs(Wrapper<T> queryWrapper) {
   
        return listObjs(queryWrapper, Function.identity());
    }

    /** * 根据 Wrapper 条件,查询全部记录 * * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} * @param mapper 转换函数 */
    <V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);

    /** * 翻页查询 * * @param page 翻页对象 * @param queryWrapper 实体对象封装操作类 {@link com.baomidou.mybatisplus.core.conditions.query.QueryWrapper} */
    IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);

    /** * 无条件翻页查询 * * @param page 翻页对象 * @see Wrappers#emptyWrapper() */
    default IPage<Map<String, Object>> pageMaps(IPage<T> page) {
   
        return pageMaps(page, Wrappers.emptyWrapper());
    }

    /** * 获取对应 entity 的 BaseMapper * * @return BaseMapper */
    BaseMapper<T> getBaseMapper();

    /** * 以下的方法使用介绍: * * 一. 名称介绍 * 1. 方法名带有 query 的为对数据的查询操作, 方法名带有 update 的为对数据的修改操作 * 2. 方法名带有 lambda 的为内部方法入参 column 支持函数式的 * * 二. 支持介绍 * 1. 方法名带有 query 的支持以 {@link ChainQuery} 内部的方法名结尾进行数据查询操作 * 2. 方法名带有 update 的支持以 {@link ChainUpdate} 内部的方法名为结尾进行数据修改操作 * * 三. 使用示例,只用不带 lambda 的方法各展示一个例子,其他类推 * 1. 根据条件获取一条数据: `query().eq("column", value).one()` * 2. 根据条件删除一条数据: `update().eq("column", value).remove()` * */

    /** * 链式查询 普通 * * @return QueryWrapper 的包装类 */
    default QueryChainWrapper<T> query() {
   
        return new QueryChainWrapper<>(getBaseMapper());
    }

    /** * 链式查询 lambda 式 * <p>注意:不支持 Kotlin </p> * * @return LambdaQueryWrapper 的包装类 */
    default LambdaQueryChainWrapper<T> lambdaQuery() {
   
        return new LambdaQueryChainWrapper<>(getBaseMapper());
    }

    /** * 链式更改 普通 * * @return UpdateWrapper 的包装类 */
    default UpdateChainWrapper<T> update() {
   
        return new UpdateChainWrapper<>(getBaseMapper());
    }

    /** * 链式更改 lambda 式 * <p>注意:不支持 Kotlin </p> * * @return LambdaUpdateWrapper 的包装类 */
    default LambdaUpdateChainWrapper<T> lambdaUpdate() {
   
        return new LambdaUpdateChainWrapper<>(getBaseMapper());
    }

    /** * <p> * 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法 * 此次修改主要是减少了此项业务代码的代码量(存在性验证之后的saveOrUpdate操作) * </p> * * @param entity 实体对象 */
    default boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper) {
   
        return update(entity, updateWrapper) || saveOrUpdate(entity);
    }

}

ServiceImpl.java

**
 * IService 实现类( 泛型:M 是 mapper 对象,T 是实体 , PK 是主键泛型 )
 *
 * @author hubin
 * @since 2018-06-23
 */
@SuppressWarnings("unchecked")
public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {
   

    protected Log log = LogFactory.getLog(getClass());

    @Autowired
    protected M baseMapper;

    @Override
    public M getBaseMapper() {
   
        return baseMapper;
    }

    /** * 判断数据库操作是否成功 * * @param result 数据库操作返回影响条数 * @return boolean */
    protected boolean retBool(Integer result) {
   
        return SqlHelper.retBool(result);
    }

    protected Class<T> currentModelClass() {
   
        return (Class<T>) ReflectionKit.getSuperClassGenericType(getClass(), 1);
    }

    /** * 批量操作 SqlSession */
    protected SqlSession sqlSessionBatch() {
   
        return SqlHelper.sqlSessionBatch(currentModelClass());
    }

    /** * 释放sqlSession * * @param sqlSession session */
    protected void closeSqlSession(SqlSession sqlSession) {
   
        SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(currentModelClass()));
    }

    /** * 获取 SqlStatement * * @param sqlMethod ignore * @return ignore */
    protected String sqlStatement(SqlMethod sqlMethod) {
   
        return SqlHelper.table(currentModelClass()).getSqlStatement(sqlMethod.getMethod());
    }

    @Override
    public boolean save(T entity) {
   
        return retBool(baseMapper.insert(entity));
    }

    /** * 批量插入 * * @param entityList ignore * @param batchSize ignore * @return ignore */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
   
        String sqlStatement = sqlStatement(SqlMethod.INSERT_ONE);
        try (SqlSession batchSqlSession = sqlSessionBatch()) {
   
            int i = 0;
            for (T anEntityList : entityList) {
   
                batchSqlSession.insert(sqlStatement, anEntityList);
                if (i >= 1 && i % batchSize == 0) {
   
                    batchSqlSession.flushStatements();
                }
                i++;
            }
            batchSqlSession.flushStatements();
        }
        return true;
    }

    /** * TableId 注解存在更新记录,否插入一条记录 * * @param entity 实体对象 * @return boolean */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public boolean saveOrUpdate(T entity) {
   
        if (null != entity) {
   
            Class<?> cls = entity.getClass();
            TableInfo tableInfo = TableInfoHelper.getTableInfo(cls);
            Assert.notNull(tableInfo, "error: can not execute. because can not find cache of TableInfo for entity!");
            String keyProperty = tableInfo.getKeyProperty();
            Assert.notEmpty(keyProperty, "error: can not execute. because can not find column for id from entity!");
            Object idVal = ReflectionKit.getMethodValue(cls, entity, tableInfo.getKeyProperty());
            return StringUtils.checkValNull(idVal) || Objects.isNull(getById((Serializable) idVal)) ? save(entity) : updateById(entity);
        }
        return false;
    }

Servcie层写法

public interface EmployeeService extends IService<Employee>{
   }

public class EmployeeServiceImpl extends ServiceImpl<EmployeeMapper,Employee> implements EmployeeService{
   }

七、分页插件

1.插件机制:
Mybatis 通过插件(Interceptor) 可以做到拦截四大对象相关方法的执行,根据需求,完成相关数据的动态改变。
Executor
StatementHandler
ParameterHandler
ResultSetHandler

2.插件原理
四大对象的每个对象在创建时,都会执行 interceptorChain.pluginAll(),会经过每个插件对象的 plugin()方法,目的是为当前的四大对象创建代理。代理对象就可以拦截到四大对象相关方法的执行,因为要执行四大对象的方法需要经过代理

分页插件

com.baomidou.mybatisplus.plugins.PaginationInterceptor

@Configuration
public class MyBatisPlusConfig {
   
    //注册插件 
    @Bean
    public PaginationInterceptor paginationInterceptor() {
   
        return new PaginationInterceptor();
    }
}
Page<Employee> employeeIPage =(Page<Employee>) employeeMapper.selectPage(new Page<>(2, 2), null);
employeeIPage.getRecords().forEach(System.out::println);
public class Page<T> implements IPage<T> {
   

    private static final long serialVersionUID = 8545996863226528798L;

    /** * 查询数据列表 */
    private List<T> records = Collections.emptyList();

    /** * 总数 */
    private long total = 0;
    /** * 每页显示条数,默认 10 */
    private long size = 10;

    /** * 当前页 */
    private long current = 1;

    /** * 排序字段信息 */
    private List<OrderItem> orders = new ArrayList<>();

    /** * 自动优化 COUNT SQL */
    private boolean optimizeCountSql = true;
    /** * 是否进行 count 查询 */
    private boolean isSearchCount = true;

    public Page() {
   
    }

    /** * 分页构造函数 * * @param current 当前页 * @param size 每页显示条数 */
    public Page(long current, long size) {
   
        this(current, size, 0);
    }

    public Page(long current, long size, long total) {
   
        this(current, size, total, true);
    }

    public Page(long current, long size, boolean isSearchCount) {
   
        this(current, size, 0, isSearchCount);
    }

    public Page(long current, long size, long total, boolean isSearchCount) {
   
        if (current > 1) {
   
            this.current = current;
        }
        this.size = size;
        this.total = total;
        this.isSearchCount = isSearchCount;
    }

    /** * 是否存在上一页 * * @return true / false */
    public boolean hasPrevious() {
   
        return this.current > 1;
    }

    /** * 是否存在下一页 * * @return true / false */
    public boolean hasNext() {
   
        return this.current < this.getPages();
    }

    @Override
    public List<T> getRecords() {
   
        return this.records;
    }

    @Override
    public Page<T> setRecords(List<T> records) {
   
        this.records = records;
        return this;
    }

    @Override
    public long getTotal() {
   
        return this.total;
    }

    @Override
    public Page<T> setTotal(long total) {
   
        this.total = total;
        return this;
    }

    @Override
    public long getSize() {
   
        return this.size;
    }

    @Override
    public Page<T> setSize(long size) {
   
        this.size = size;
        return this;
    }

    @Override
    public long getCurrent() {
   
        return this.current;
    }

    @Override
    public Page<T> setCurrent(long current) {
   
        this.current = current;
        return this;
    }

    /** * 获取当前正序排列的字段集合 * <p> * 为了兼容,将在不久后废弃 * * @return 正序排列的字段集合 * @see #getOrders() * @deprecated 3.2.0 */
    @Override
    @Nullable
    @Deprecated
    public String[] ascs() {
   
        return CollectionUtils.isNotEmpty(orders) ? mapOrderToArray(OrderItem::isAsc) : null;
    }

    /** * 查找 order 中正序排序的字段数组 * * @param filter 过滤器 * @return 返回正序排列的字段数组 */
    private String[] mapOrderToArray(Predicate<OrderItem> filter) {
   
        List<String> columns = new ArrayList<>(orders.size());
        orders.forEach(i -> {
   
            if (filter.test(i)) {
   
                columns.add(i.getColumn());
            }
        });
        return columns.toArray(new String[0]);
    }

    /** * 移除符合条件的条件 * * @param filter 条件判断 */
    private void removeOrder(Predicate<OrderItem> filter) {
   
        for (int i = orders.size() - 1; i >= 0; i--) {
   
            if (filter.test(orders.get(i))) {
   
                orders.remove(i);
            }
        }
    }

    /** * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)} * * @param items 条件 * @return 返回分页参数本身 */
    public Page<T> addOrder(OrderItem... items) {
   
        orders.addAll(Arrays.asList(items));
        return this;
    }

    /** * 添加新的排序条件,构造条件可以使用工厂:{@link OrderItem#build(String, boolean)} * * @param items 条件 * @return 返回分页参数本身 */
    public Page<T> addOrder(List<OrderItem> items) {
   
        orders.addAll(items);
        return this;
    }

    /** * 设置需要进行正序排序的字段 * <p> * Replaced:{@link #addOrder(OrderItem...)} * * @param ascs 字段 * @return 返回自身 * @deprecated 3.2.0 */
    @Deprecated
    public Page<T> setAscs(List<String> ascs) {
   
        return CollectionUtils.isNotEmpty(ascs) ? setAsc(ascs.toArray(new String[0])) : this;
    }

    /** * 升序 * <p> * Replaced:{@link #addOrder(OrderItem...)} * * @param ascs 多个升序字段 * @deprecated 3.2.0 */
    @Deprecated
    public Page<T> setAsc(String... ascs) {
   
        // 保证原来方法 set 的语意
        removeOrder(OrderItem::isAsc);
        for (String s : ascs) {
   
            addOrder(OrderItem.asc(s));
        }
        return this;
    }

    /** * 获取需简要倒序排列的字段数组 * <p> * * @return 倒序排列的字段数组 * @see #getOrders() * @deprecated 3.2.0 */
    @Override
    @Deprecated
    public String[] descs() {
   
        return mapOrderToArray(i -> !i.isAsc());
    }

    /** * Replaced:{@link #addOrder(OrderItem...)} * * @param descs 需要倒序排列的字段 * @return 自身 * @deprecated 3.2.0 */
    @Deprecated
    public Page<T> setDescs(List<String> descs) {
   
        // 保证原来方法 set 的语意
        if (CollectionUtils.isNotEmpty(descs)) {
   
            removeOrder(item -> !item.isAsc());
            for (String s : descs) {
   
                addOrder(OrderItem.desc(s));
            }
        }
        return this;
    }

    /** * 降序,这方法名不知道是谁起的 * <p> * Replaced:{@link #addOrder(OrderItem...)} * * @param descs 多个降序字段 * @deprecated 3.2.0 */
    @Deprecated
    public Page<T> setDesc(String... descs) {
   
        setDescs(Arrays.asList(descs));
        return this;
    }

    @Override
    public List<OrderItem> orders() {
   
        return getOrders();
    }

    public List<OrderItem> getOrders() {
   
        return orders;
    }

    public void setOrders(List<OrderItem> orders) {
   
        this.orders = orders;
    }

    @Override
    public boolean optimizeCountSql() {
   
        return optimizeCountSql;
    }

    @Override
    public boolean isSearchCount() {
   
        if (total < 0) {
   
            return false;
        }
        return isSearchCount;
    }

    public Page<T> setSearchCount(boolean isSearchCount) {
   
        this.isSearchCount = isSearchCount;
        return this;
    }

    public Page<T> setOptimizeCountSql(boolean optimizeCountSql) {
   
        this.optimizeCountSql = optimizeCountSql;
        return this;
    }
}

执行分析插件

  1. com.baomidou.mybatisplus.plugins.SqlExplainInterceptor
  2. SQL 执行分析拦截器,只支持 MySQL5.6.3 以上版本
  3. 该插件的作用是分析 DELETE UPDATE 语句,防止小白
    或者恶意进行 DELETE UPDATE 全表操作
  4. 只建议在开发环境中使用,不建议在生产环境使用
  5. 在插件的底层 通过 SQL 语句分析命令:Explain 分析当前的 SQL 语句,
    根据结果集中的 Extra 列来断定当前是否全表操作。
@Configuration
public class MyBatisPlusConfig {
   
    //注册插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
   
        return new PaginationInterceptor();
    }

    //分析插件
    @Bean
    public SqlExplainInterceptor sqlExplainInterceptor(){
   
        SqlExplainInterceptor sqlExplainInterceptor = new SqlExplainInterceptor();
        List<ISqlParser> sqlParserList = new ArrayList<>();
        sqlParserList.add(new BlockAttackSqlParser());
        sqlExplainInterceptor.setSqlParserList(sqlParserList);
        return sqlExplainInterceptor;
    }
}
@Test
    public void fn(){
   
        try {
   
            employeeMapper.delete(null);
        }catch (Exception e){
   
            e.printStackTrace();
            System.out.println("不能全表删除");
        }

    }
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
	at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:78)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:440)
	at com.sun.proxy.$Proxy65.delete(Unknown Source)
	at org.mybatis.spring.SqlSessionTemplate.delete(SqlSessionTemplate.java:303)
	at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:68)
	at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:61)
	at com.sun.proxy.$Proxy68.delete(Unknown Source)
	at com.mybatisplus.mybatisplugsdemo.DeleTest.fn(DeleTest.java:15)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:74)
	at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:84)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
	at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error updating database.  Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
### Cause: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
	at org.apache.ibatis.exceptions.ExceptionFactory.wrapException(ExceptionFactory.java:30)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:199)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.delete(DefaultSqlSession.java:212)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
	... 37 more
Caused by: com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of full table deletion
	at com.baomidou.mybatisplus.core.toolkit.ExceptionUtils.mpe(ExceptionUtils.java:49)
	at com.baomidou.mybatisplus.core.toolkit.Assert.isTrue(Assert.java:38)
	at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:72)
	at com.baomidou.mybatisplus.extension.parsers.BlockAttackSqlParser.processDelete(BlockAttackSqlParser.java:40)
	at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.processParser(AbstractJsqlParser.java:94)
	at com.baomidou.mybatisplus.core.parser.AbstractJsqlParser.parser(AbstractJsqlParser.java:67)
	at com.baomidou.mybatisplus.extension.handlers.AbstractSqlParserHandler.sqlParser(AbstractSqlParserHandler.java:76)
	at com.baomidou.mybatisplus.extension.plugins.SqlExplainInterceptor.intercept(SqlExplainInterceptor.java:63)
	at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
	at com.sun.proxy.$Proxy89.update(Unknown Source)
	at org.apache.ibatis.session.defaults.DefaultSqlSession.update(DefaultSqlSession.java:197)
	... 43 more
不能全表删除

性能分析插件

  1. com.baomidou.mybatisplus.plugins.PerformanceInterceptor
  2. 性能分析拦截器,用于输出每条 SQL 语句及其执行时间
  3. SQL 性能执行分析,开发环境使用,超过指定时间,停止运行。有助于发现问题

性能分析插件是3.2.0 以上版本移除,推荐使用第三方扩展 执行 SQL

乐观锁插件

  1. com.baomidou.mybatisplus.plugins.OptimisticLockerInterceptor
  2. 如果想实现如下需求: 当要更新一条记录的时候,希望这条记录没有被别人更新
  3. 乐观锁的实现原理:
    取出记录时,获取当前 version 2
    更新时,带上这个 version 2
    执行更新时, set version = yourVersion+1 where version = yourVersion
    如果 version 不对,就更新失败
  4. @Version 用于注解实体字段,必须要有。

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

特别说明:

  • 支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
  • 整数类型下 newVersion = oldVersion + 1
  • newVersion 会回写到 entity
  • 仅支持 updateById(id)update(entity, wrapper) 方法
  • update(entity, wrapper) 方法下, wrapper 不能复用!!!
    @Test
    public void fn (){
   
        //跟新操作
        Employee employee = new Employee();
        employee.setId(10);
        employee.setLastName("tome");
        employee.setEmail("tom@qq.com");
        employee.setGender(1);
        employee.setAge(22);
        employee.setVersion(3);

        employeeMapper.update(employee,null);
    }
UPDATE tbl_employee  SET last_name=?,
gender=?,
version=?,
age=?,
email=?  
 
 WHERE (version = ?)
UPDATE tbl_employee  SET last_name='tome',
gender=1,
version=4,
age=22,
email='tom@qq.com'  
 
 WHERE (version = 3);
update tbl_user set name = 'update',version = 3 where id = 100 and version = 2

# 条件中version是我们的。 set 中的version是数据库返回来的。

八、自定义全局操作 SQL注入

SQL 自动注入器接口 ISqlInjector

@Configuration
public class MySqlInjector implements ISqlInjector {
   
    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
   
        //将EmployeeMapper中定义deleteAll,处理成对已的MapperStatment对象,加入到configuration对线中

        



    }
}
/** * SQL 自动注入器 * * @author hubin * @since 2018-04-07 */
public abstract class AbstractSqlInjector implements ISqlInjector {
   

    private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
   
        Class<?> modelClass = extractModelClass(mapperClass);
        if (modelClass != null) {
   
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
   
                List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                if (CollectionUtils.isNotEmpty(methodList)) {
   
                    TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                    // 循环注入自定义方法
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
                } else {
   
                    logger.debug(mapperClass.toString() + ", No effective injection method was found.");
                }
                mapperRegistryCache.add(className);
            }
        }
    }

    /** * <p> * 获取 注入的方法 * </p> * * @param mapperClass 当前mapper * @return 注入的方法集合 * @since 3.1.2 add mapperClass */
    public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass);

    /** * 提取泛型模型,多泛型的时候请将泛型T放在第一位 * * @param mapperClass mapper 接口 * @return mapper 泛型 */
    protected Class<?> extractModelClass(Class<?> mapperClass) {
   
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
   
            if (type instanceof ParameterizedType) {
   
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (ArrayUtils.isNotEmpty(typeArray)) {
   
                    for (Type t : typeArray) {
   
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
   
                            break;
                        } else {
   
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
    }
}

九、逻辑删除

  • application.yml 加入配置(如果你的默认值和mp默认的一样,该配置可无):
mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: flag  #全局逻辑删除字段值 3.3.0开始支持,详情看下面。
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
  • 实体类字段上加上@TableLogic注解
@Data
@TableName("tb_user")
public class User{
   
    @TableId(type=IdType.AUTO)
    private Integer id; 
    private String name;
    
	@TableLogic
	private Integer logicfalg;    //logic_falg
}


说明:

  • 字段支持所有数据类型(推荐使用 Integer,Boolean,LocalDateTime)
  • 如果使用LocalDateTime,建议逻辑未删除值设置为字符串null,逻辑删除值只支持数据库函数例如now()
  • 效果: 使用mp自带方法删除和查找都会附带逻辑删除功能
# 删除 
update user set deleted=1 where id =1 and deleted=0
# 查找 
select * from user where deleted=0

十、公共字段自动填充

实现元对象处理器接口:com.baomidou.mybatisplus.core.handlers.MetaObjectHandler

https://mp.baomidou.com/guide/auto-fill-metainfo.html

insertFill(MetaObject metaObject)

updateFill(MetaObject metaObject)

metaobject: 元对象. 是 Mybatis 提供的一个用于更加方便,更加优雅的访问对象的属性,
给对象的属性设置值 的一个对象. 还会用于包装对象. 支持对 Object 、Map、Collection
等对象进行包装
本质上 metaObject 获取对象的属性值或者是给对象的属性设置值,最终是要
通过 Reflector 获取到属性的对应方法的 Invoker, 最终 invoke.

/** * 元对象字段填充控制器抽象类,实现公共字段自动写入 * * @author hubin * @since 2016-08-28 */
public interface MetaObjectHandler {
   

    /** * 乐观锁常量 * * @deprecated 3.1.1 {@link Constants#MP_OPTLOCK_ET_ORIGINAL} */
    String MP_OPTLOCK_ET_ORIGINAL = "MP_OPTLOCK_ET_ORIGINAL";

    /** * 插入元对象字段填充(用于插入时对公共字段的填充) * * @param metaObject 元对象 */
    void insertFill(MetaObject metaObject);

    /** * 更新元对象字段填充(用于更新时对公共字段的填充) * * @param metaObject 元对象 */
    void updateFill(MetaObject metaObject);

    /** * 通用填充 * * @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) {
   
        if (Objects.nonNull(fieldVal)) {
   
            if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)) {
   
                metaObject.setValue(fieldName, fieldVal);
            } else if (metaObject.hasGetter(Constants.ENTITY)) {
   
                Object et = metaObject.getValue(Constants.ENTITY);
                if (et != null) {
   
                    MetaObject etMeta = SystemMetaObject.forObject(et);
                    if (etMeta.hasSetter(fieldName)) {
   
                        etMeta.setValue(fieldName, fieldVal);
                    }
                }
            }
        }
        return this;
    }

    /** * insert 时填充,只会填充 fill 被标识为 INSERT 与 INSERT_UPDATE 的字段 * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @since 3.0.7 */
    default MetaObjectHandler setInsertFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
   
        return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.INSERT);
    }

    /** * update 时填充,只会填充 fill 被标识为 UPDATE 与 INSERT_UPDATE 的字段 * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @since 3.0.7 */
    default MetaObjectHandler setUpdateFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject) {
   
        return setFieldValByName(fieldName, fieldVal, metaObject, FieldFill.UPDATE);
    }

    /** * Common method to set value for java bean. * <p>如果包含前缀 et 使用该方法,否则可以直接 metaObject.setValue(fieldName, fieldVal);</p> * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @param fieldFill 填充策略枚举 * @since 3.0.7 */
    default MetaObjectHandler setFieldValByName(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) {
   
        if (Objects.nonNull(fieldVal)) {
   
            if (metaObject.hasSetter(fieldName) && metaObject.hasGetter(fieldName)
                && isFill(fieldName, fieldVal, metaObject, fieldFill)) {
   
                metaObject.setValue(fieldName, fieldVal);
            } else if (metaObject.hasGetter(Constants.ENTITY)) {
   
                Object et = metaObject.getValue(Constants.ENTITY);
                if (et != null) {
   
                    MetaObject etMeta = SystemMetaObject.forObject(et);
                    if (etMeta.hasSetter(fieldName) && isFill(fieldName, fieldVal, etMeta, fieldFill)) {
   
                        etMeta.setValue(fieldName, fieldVal);
                    }
                }
            }
        }
        return this;
    }

    /** * get value from java bean by propertyName * <p>如果包含前缀 et 使用该方法,否则可以直接 metaObject.setValue(fieldName, fieldVal);</p> * * @param fieldName java bean property name * @param metaObject parameter wrapper * @return 字段值 */
    default Object getFieldValByName(String fieldName, MetaObject metaObject) {
   
        if (metaObject.hasGetter(fieldName)) {
   
            return metaObject.getValue(fieldName);
        } else if (metaObject.hasGetter(Constants.ENTITY_DOT + fieldName)) {
   
            return metaObject.getValue(Constants.ENTITY_DOT + fieldName);
        }
        return null;
    }

    /** * 填充判断 * <li> 如果是主键,不填充 </li> * <li> 根据字段名找不到字段,不填充 </li> * <li> 字段类型与填充值类型不匹配,不填充 </li> * <li> 字段类型需在TableField注解里配置fill: @TableField(value="test_type", fill = FieldFill.INSERT), 没有配置或者不匹配时不填充 </li> * v_3.1.0以后的版本(不包括3.1.0),子类的值也可以自动填充,Timestamp的值也可以填入到java.util.Date类型里面 * * @param fieldName java bean property name * @param fieldVal java bean property value * @param metaObject meta object parameter * @param fieldFill 填充策略枚举 * @return 是否进行填充 * @since 3.0.7 */
    default boolean isFill(String fieldName, Object fieldVal, MetaObject metaObject, FieldFill fieldFill) {
   
        TableInfo tableInfo = metaObject.hasGetter(Constants.MP_OPTLOCK_ET_ORIGINAL) ?
            TableInfoHelper.getTableInfo(metaObject.getValue(Constants.MP_OPTLOCK_ET_ORIGINAL).getClass())
            : TableInfoHelper.getTableInfo(metaObject.getOriginalObject().getClass());
        if (Objects.nonNull(tableInfo)) {
   
            Optional<TableFieldInfo> first = tableInfo.getFieldList().stream()
                //v_3.1.1+ 设置子类的值也可以通过
                .filter(e -> e.getProperty().equals(fieldName) && e.getPropertyType().isAssignableFrom(fieldVal.getClass()))
                .findFirst();
            if (first.isPresent()) {
   
                FieldFill fill = first.get().getFieldFill();
                return fill.equals(fieldFill) || FieldFill.INSERT_UPDATE.equals(fill);
            }
        }
        return false;
    }

    /** * 是否开启了插入填充 */
    default boolean openInsertFill() {
   
        return true;
    }

    /** * 是否开启了更新填充 */
    default boolean openUpdateFill() {
   
        return true;
    }
}

开发步骤

  1. 注解填充字段 @TableFile(fill = FieldFill.INSERT) 查看 FieldFill
  2. 自定义公共字段填充处理器
  3. MP 全局注入 自定义公共字段填充处理器
@Data
@TableName("tb_user")
public class User extends Model<User> {
   
    @TableId(value = "id",type = IdType.AUTO)
    private Integer id;
    @TableField(fill = FieldFill.UPDATE)
    private String name;
    private Integer age;

   /* protected Serializable pkVal() { return id; }*/

}
/** * 自定义填充处理器 */
@Component
@Slf4j
public class MyMetaObjectHandler implements MetaObjectHandler {
   
    @Override
    public void insertFill(MetaObject metaObject) {
   
    //获取到需要填充字段的值
        Object fieldVale = getFieldValByName("name", metaObject);
        if(fieldVale == null){
   
            System.out.println("插入操作,满足填充条件");
            setFieldValByName("name","xxx", metaObject);
        }
    }

    @Override
    public void updateFill(MetaObject metaObject) {
   
        /* 获取到需要填充字段的值 */
        Object fieldVale = getFieldValByName("name", metaObject);
        if(fieldVale == null){
   
            System.out.println("修改操作,满足填充条件");
            setFieldValByName("name","xxx", metaObject);
        }
    }
}

    @Test
    public void updateMeta(){
   
        User user = new User();
        user.setId(3);
        user.setAge(23);

        userMapper.update(user,new QueryWrapper<User>().eq("id",1));
    }

十一、Oracle主键Sequence

MySQL: 支持主键自增。 IdType.Auto
Oracle: 序列(Sequence)

https://mp.baomidou.com/guide/sequence.html#spring-boot

  1. 实体类配置主键 Sequence @KeySequence(value=”序列名”,clazz=xxx.class 主键属性类型)
  2. 全局 MP 主键生成策略为 IdType.INPUT
  3. 全局 MP 中配置 Oracle 主键 Sequence
    com.baomidou.mybatisplus.incrementer.OracleKeyGenerator
  4. 可以将@keySequence 定义在父类中,可实现多个子类对应的多个表公用一个 Sequence
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/100753.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • 免费申请国外免费域名超详细教程

    免费申请国外免费域名超详细教程1.首先申请免费域名网站:https://my.freenom.com/domains.php2.填入域名,这里我们以xcflag为列(尽量选择复杂一点的或者五个字母以上的域名,因为简单的有些域名是需要收费的),点击检查可用性。3.可以看到很多免费的域名(用的谷歌翻译插件,翻译有时候不是很准确,free翻译过来应该是免费而不是自由,之后会写一些关于谷歌插件的笔记,详细讲解)4.我们选择xcflag.tk点击立即获取,稍等一会点击购物车查看绿色按钮5.默认三个月试用,这里下拉框我们选择十二个月

  • 火炬之光模型导出(Unity载入火炬之光的模型)

    火炬之光模型导出(Unity载入火炬之光的模型)

    2021年11月13日
  • Java的控制台输入输出语句[通俗易懂]

    Java的控制台输入输出语句[通俗易懂]输出语句Java中常用的输出语句有以下三种System.out.println();System.out.print();System.out.printf();System.out.println();是最常用的输出语句,它会把括号里的内容转换成字符串输出到输出窗口(控制台),并且换行,当输出的是一个基本数据类型时,会自动转换成字符串,如果输出的是一个对象,会自动调用对象的toString

  • Java基础学习教程,eclipse简单使用教程(Java集成开发工具)

    Java基础学习教程,eclipse简单使用教程(Java集成开发工具)使用集成开发工具eclipse1、java的集成开发工具很多,包括:eclipse、IntellijIDEA、netbeans….. eclipse: IBM开发的。eclipse翻译为:日食。寓意吞并SUN公司(SUN是太阳。)最终没有成功,SUN公司在2009年的时候被oracle甲骨文公司收购。eclipse在以前的开发中使用非常多,但是由于IDEA工具的出现,让eclipse的用户大大减少,目前eclipse占市场份额30%。IDEA占市场份额60%,剩下10%是其他的开

  • linux重启nginx服务命令_windows nginx启动命令

    linux重启nginx服务命令_windows nginx启动命令Linux重启nginx

  • Oracle数据恢复顾问(Data Recovery Advisor)「建议收藏」

    Oracle数据恢复顾问(Data Recovery Advisor)「建议收藏」Oracle数据恢复顾问用于当数据发生错误或故障时,进行自动收集数据故障信息,并生成恢复脚本,用于完成数据恢复。数据恢复顾问也可以主动检查故障。在这种模式下,它可以在数据库进程发现数据损坏并发出错误之前进行潜在的检测并分析数据故障。数据故障可能非常严重。例如,如果您当前的日志文件丢失,则无法启动你的数据库。。。。。

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号