SpringBoot整合MyBatis分页

news2024/12/23 18:55:07

在这里插入图片描述

SpringBoot整合MyBatis分页

    • 一、pagehelper分页
      • 1、添加相关依赖
      • 2、添加相关配置
      • 3、添加分页配置
      • 4、添加代码
      • 5、测试
    • 二、拦截器分页
      • 1、添加相关配置
      • 2、添加拦截器代码和配置
      • 3、添加代码
      • 4、测试
      • 4、测试

本文目标: SpringBoot整合Mybatis分页的两种方式,一是利用PageHelper来实现,二是采用拦截器分页。

MyBatis框架分页实现方式:

  1. SQL分页,利用原生的sql关键字limit来实现(不推荐)
  2. 利用interceptor来拼接sql,实现和limit一样的功能(不推荐)
  3. 利用PageHelper来实现(简单)
  4. 拦截器分页(数据量大时,实现拦截器就很有必要了)

注意:分页的实现,是基于SpringBoot整合MyBatis 之上。

一、pagehelper分页

1、添加相关依赖

首先,我们需要在 pom.xml 文件中添加分页插件依赖包。

pom.xml

<!-- pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.5</version>
</dependency>

2、添加相关配置

然后在 application.yml 配置文件中添加分页插件有关的配置。

application.yml

pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql

3、添加分页配置

分页查询请求封装类

PageRequest.java

import lombok.Data;

@Data
public class PageRequest {
    // 当前页码
    private int pageNum;
    // 每页数量
    private int pageSize;
}

分页查询结果封装类

PageResult.java

import lombok.Data;
import java.util.List;

@Data
public class PageResult {
    // 当前页码
    private int pageNum;
    // 每页数量
    private int pageSize;
    // 记录总数
    private long totalSize;
    // 页码总数
    private int totalPages;
    // 数据模型
    private List<?> content;
}

分页查询相关工具类。

PageUtils.java

import com.github.pagehelper.PageInfo;

public class PageUtils {
    /**
     * 将分页信息封装到统一的接口
     * @param pageInfo
     * @return
     */
    public static PageResult getPageResult(PageInfo<?> pageInfo) {
        PageResult pageResult = new PageResult();
        pageResult.setPageNum(pageInfo.getPageNum());
        pageResult.setPageSize(pageInfo.getPageSize());
        pageResult.setTotalSize(pageInfo.getTotal());
        pageResult.setTotalPages(pageInfo.getPages());
        pageResult.setContent(pageInfo.getList());
        return pageResult;
    }
}

4、添加代码

UserMapper.xml

<!--  查询分页  -->
<select id="getAllUserByPage" resultMap="BaseResultMap">
    <include refid="Base_Column_List" />
    from db_user
</select>

UserMapper.java

// 分页
List<User> getAllUserByPage();

service(IUserService.java && UserServiceImpl.java)

// IUserService
    /**
     * 分页查询接口
     * 这里统一封装了分页请求和结果,避免直接引入具体框架的分页对象, 如MyBatis或JPA的分页对象
     * 从而避免因为替换ORM框架而导致服务层、控制层的分页接口也需要变动的情况,替换ORM框架也不会
     * 影响服务层以上的分页接口,起到了解耦的作用
     * @param pageRequest 自定义,统一分页查询请求
     * @return PageResult 自定义,统一分页查询结果
     */
    PageResult getAllUserByPage(PageRequest pageRequest);
    
//  UserServiceImpl
    /** 分页查询 */
    @Override
    public PageResult getAllUserByPage(PageRequest pageRequest) {
        return PageUtils.getPageResult(getPageInfo(pageRequest));
    }

    /**
     * 调用分页插件完成分页
     * @param pageRequest
     * @return
     */
    private PageInfo<User> getPageInfo(PageRequest pageRequest) {
        int pageNum = pageRequest.getPageNum();
        int pageSize = pageRequest.getPageSize();
        PageHelper.startPage(pageNum, pageSize);
        List<User> sysMenus = userMapper.getAllUserByPage();
        return new PageInfo<User>(sysMenus);
    }

controller(UserController.java)

// pagehelper 分页 post 请求
@PostMapping("/findPage")
public Result findPage(@RequestBody PageRequest pageQuery) {
return Result.success(userService.getAllUserByPage(pageQuery));
}

5、测试

测试工具:postman

测试路径:http://localhost:8081/findPage

结果图:

img

二、拦截器分页

实现原理主要是在数据库执行session查询的过程中,修改sql语句,先查询记录总条数,然后再分页查询数据记录,再把数据整合成分页数据形式就可以了。

img

1、添加相关配置

application-dev.yml

mybatis:
  mapper-locations: classpath:mapper/*Mapper.xml
  type-aliases-package: com.mmdz.entity
# sql 打印
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  config-location: classpath:mybatis/mybatis-config.xml

注意:要注释 configuration ,否则会报错 IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together

// 配置重复导致冲突

Caused by: java.lang.IllegalStateException: Property 'configuration' and 'configLocation' can not specified with together
 at org.springframework.util.Assert.state(Assert.java:76) ~[spring-core-5.3.1.jar:5.3.1]
 at org.mybatis.spring.SqlSessionFactoryBean.afterPropertiesSet(SqlSessionFactoryBean.java:488) ~[mybatis-spring-2.0.6.jar:2.0.6]
    at org.mybatis.spring.SqlSessionFactoryBean.getObject(SqlSessionFactoryBean.java:633) ~[mybatis-spring-2.0.6.jar:2.0.6]

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>
    <settings>
        <setting name="cacheEnabled" value="false" /><!-- 暂时禁用缓存 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/><!-- 打印sql-->
    </settings>
    <plugins>
        <plugin interceptor="com.mmdz.common.interceptor.PaginationInterceptor"></plugin>
    </plugins>
</configuration>

2、添加拦截器代码和配置

PaginationInterceptor.java

**注意:**这个类拦截StatementHandler类的prepare方法,解析获取请求方法参数。如果是单个参数,且参数类型为SimplePage,则采用分页模式。或者有多个参数,并且其中一个参数被标注为page(即接口中@Param(“page”)),也启用分页模式。其中getCountSql(String sql)方法和getPageSql(String sql, SimplePage page)方法需要根据不同数据库进行修改,我用Mysql。

import com.mmdz.common.interceptor.page.SimplePage;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

@Intercepts(
        { @Signature(type = StatementHandler.class,
                method = "prepare", args = { Connection.class , Integer.class}) })
public class PaginationInterceptor implements Interceptor {

    private static final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
    private static String dialect = "mysql";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 获得拦截的对象
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // 待执行的sql的包装对象
        BoundSql boundSql = statementHandler.getBoundSql();
        // 判断是否是查询语句
        if (isSelect(boundSql.getSql())) {
            // 获得参数集合
            Object params = boundSql.getParameterObject();

            if (params instanceof Map) { // 请求为多个参数,参数采用Map封装
                return complexParamsHandler(invocation, boundSql, (Map<?, ?>) params);
            } else if (params instanceof SimplePage) { // 单个参数且为Page,则表示该操作需要进行分页处理
                return simpleParamHandler(invocation, boundSql, (SimplePage) params);
            }
        }
        return invocation.proceed();
    }

    private Object complexParamsHandler(Invocation invocation, BoundSql boundSql, Map<?, ?> params) throws Throwable {
        //判断参数中是否指定分页
        if (containsPage(params)) {
            return pageHandlerExecutor(invocation, boundSql, (SimplePage) params.get("page"));
        } else {
            return invocation.proceed();
        }
    }

    private boolean containsPage(Map<?, ?> params) {
        if(params==null){
            return false;
        }else if(!params.containsKey("page")){
            return false;
        }
        Object page = params.get("page");
        if(page==null){
            return false;
        }else if(page instanceof SimplePage){
            return true;
        }
        return false;
    }

    private boolean isSelect(String sql) {
        if (!StringUtils.isEmpty(sql) && sql.toUpperCase().trim().startsWith("SELECT")) {
            return true;
        }
        return false;
    }

    private Object simpleParamHandler(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
        return pageHandlerExecutor(invocation, boundSql, page);
    }

    private Object pageHandlerExecutor(Invocation invocation, BoundSql boundSql, SimplePage page) throws Throwable {
        // 获得数据库连接
        Connection connection = (Connection) invocation.getArgs()[0];
        // 使用Mybatis提供的MetaObject,该对象主要用于获取包装对象的属性值
        MetaObject statementHandler = MetaObject.forObject(invocation.getTarget(), DEFAULT_OBJECT_FACTORY,
                DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);

        // 获取该sql执行的结果集总数
        int maxSize = getTotalSize(connection, (MappedStatement) statementHandler.getValue("delegate.mappedStatement"),
                boundSql);

        // 生成分页sql
        page.setTotalRecord(maxSize);
        String wrapperSql = getPageSql(boundSql.getSql(), page);

        MetaObject boundSqlMeta = MetaObject.forObject(boundSql, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY,
                DEFAULT_REFLECTOR_FACTORY);
        // 修改boundSql的sql
        boundSqlMeta.setValue("sql", wrapperSql);
        return invocation.proceed();
    }

    private int getTotalSize(Connection connection, MappedStatement mappedStatement, BoundSql boundSql) {
        String countSql = getCountSql(boundSql.getSql());
        PreparedStatement countStmt;
        ResultSet rs;
        List<AutoCloseable> closeableList = new ArrayList<AutoCloseable>();

        try {
            countStmt = connection.prepareStatement(countSql);
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,
                    boundSql.getParameterMappings(), boundSql.getParameterObject());
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());
            rs = countStmt.executeQuery();

            if (rs.next()) {
                return rs.getInt(1);
            }
            closeableList.add(countStmt);
            closeableList.add(rs);
        } catch (SQLException e) {
            logger.error("append an exception[{}] when execute sql[{}] with {}", e, countSql,
                    boundSql.getParameterObject());
        } finally {
            for (AutoCloseable closeable : closeableList) {
                try {
                    if (closeable != null)
                        closeable.close();
                } catch (Exception e) {
                    logger.error("append an exception[{}] when close resource[{}] ", e, closeable);
                }
            }
        }
        return 0;
    }

    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
                               Object parameterObject) throws SQLException {
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
        parameterHandler.setParameters(ps);
    }

    @Override
    public Object plugin(Object target) {
        // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的次数
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }

    @Override
    public void setProperties(Properties properties) {

    }

    public String getCountSql(String sql) {
        if("mysql".equals(dialect)){
            return "select count(0) from (" + sql + ") as total";
        }
        return sql;
    }

    public String getPageSql(String sql, SimplePage page) {
        if(page.getPage()<=0){
            page.setPage(1);
        }
        if(page.getRows()<=0){
            page.setRows(20);
        }
        int startRow = (page.getPage()-1)*page.getRows();

        if(startRow>=page.getTotalRecord()){
            page.setPage(1);
            startRow=0;
        }
        if("mysql".equals(dialect)){
            return sql+" limit "+startRow+", "+page.getRows();
        }
        return sql;
    }
}

SimplePage.java

**注意:**这个类是分页对象所需的最简单的类,只要包含页码、每页条数、总条数和数据记录,就可以推算出所有需要的分页参数。所以用这个类来给拦截器做参数判断,若需要更多的页码信息可以重写一个分页类继承这个SimplePage即可。

import lombok.Getter;
import lombok.Setter;
import java.io.Serializable;
import java.util.List;

@Getter
@Setter
public class SimplePage implements Serializable {

    protected static final long serialVersionUID = 5136213157391895517L;

    protected int page = 1;// 页码,默认是第一页
    protected int rows = 10;// 每页显示的记录数,默认是10
    protected int totalRecord;// 总记录数
    protected List data;// 当前页记录

    public SimplePage setData(List data) {
        this.data = data;
        return this;
    }
}

Page.java

**注意:**这个Page类主要是丰富SimplePage类,最重要的就是 setData(List data)方法,由这个方法来丰富一些变量数据。

import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
public class Page extends SimplePage {
    private static final long serialVersionUID = -6190845403265328029L;

    private boolean isFirstPage = true;//是否是第一页
    private boolean isLastPage = false;//是否是最后一页
    private int pageCount = 0;//当前页总记录数
    private int totalPage = 0;//总页数
    private int prePage = 1;//上一页页码
    private int nextPage = 1;//下一页页码

    public Page() {
        super();
    }

    public Page(int page, int rows) {
        super();
        setPage(page);
        setRows(rows);
    }

    @Override
    public Page setData(List data){
        super.setData(data);
        if(data!=null && data.size()>0){
            pageCount = data.size();
            if(this.page==1){
                isFirstPage=true;
            }else{
                isFirstPage=false;
            }
            //***
            totalPage = (int)Math.ceil(totalRecord/(double)rows);
            //***
            if(page==totalPage){
                isLastPage = true;
            }else{
                isLastPage = false;
            }
            //***
            if(isFirstPage){
                prePage = 1;
            }else{
                prePage = page-1;
            }
            //***
            if(isLastPage){
                nextPage = 1;
            }else{
                nextPage = page+1;
            }
        }else{
            isLastPage = true;
        }
        return this;
    }
}

3、添加代码

UserMapper.xml

<select id="findPage" resultMap="BaseResultMap">
    <include refid="Base_Column_List" />
    from db_user
</select>

UserMapper.java

**注意:**如果这个查询方法只有分页参数page,没有别的参数,可以不写@Param(“page”),若有多个参数必须用@Param标明参数名,这是拦截器判断分页的依据。

/** @Param("page")是应分页插件要求编写的 */
List<User> findPage(@Param("page") Page page);

UserServiceImpl.java

/**
* 拦截器分页
* @param page
* @return
*/
@Override
public Page findPage(Page page) {
    List<User> list = userMapper.findPage(page);
    page.setData(list);
    return page;
}

IUserService.java

/**
* 拦截器分页
* @param page
* @return
*/
Page findPage(Page page);

UserController.java

// 拦截器 分页 post 请求
@PostMapping("/findPage2")
public Result findPage(@RequestBody Page page){
    return Result.success(userService.findPage(page));
}

4、测试

测试工具:postman

测试路径:http://localhost:8081/findPage2

结果图:

pl.java**

/**
* 拦截器分页
* @param page
* @return
*/
@Override
public Page findPage(Page page) {
    List<User> list = userMapper.findPage(page);
    page.setData(list);
    return page;
}

IUserService.java

/**
* 拦截器分页
* @param page
* @return
*/
Page findPage(Page page);

UserController.java

// 拦截器 分页 post 请求
@PostMapping("/findPage2")
public Result findPage(@RequestBody Page page){
    return Result.success(userService.findPage(page));
}

4、测试

测试工具:postman

测试路径:http://localhost:8081/findPage2

结果图:

img

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/686571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python基础八

目录 一、Python条件控制语句 1.执行过程 2.if 语句 if中常用的操作运算符: if 嵌套 3.match...case语句 二、Python循环控制语句 1.while循环 while无限循环 while 循环使用 else 语句 2.for 循环语句 for...in...循环 for循环使用else语句 for...in range()…

2023年3季度DAMA-CDGA/CDGP数据治理认证即将开班

DAMA认证为数据管理专业人士提供职业目标晋升规划&#xff0c;彰显了职业发展里程碑及发展阶梯定义&#xff0c;帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力&#xff0c;促进开展工作实践应用及实际问题解决&#xff0c;形成企业所需的新数字经济下的核心职业…

你知道ai绘画生成器怎么弄吗

在数字时代&#xff0c;艺术也开始走向了自动化。随着人工智能技术的进步&#xff0c;ai绘画软件已经开始逐渐普及。它们可以利用先进的神经网络算法&#xff0c;学习人类艺术家的风格和技巧&#xff0c;从而生成出高度逼真的艺术作品。这些作品虽然没有人类艺术家的创意和灵魂…

Linux系统编程(终端和进程的关系)

文章目录 前言一、终端和控制台二、TTY和PTY三、终端的类型四、Gnome Terminal伪终端总结 前言 本篇文章带大家学习终端和进程的关系&#xff0c;终端相信大家都听过&#xff0c;那么真的理解终端是什么吗&#xff1f;应该有很多同学对于终端只是有一个模糊的概念。那么这篇文…

【算法题】合并两个有序链表、删除字符串 s1 中在字符串 s2 中出现的字符、求一个论坛一天的在线人数分布

合并两个有序链表、删除字符串 s1 中在字符串 s2 中出现的字符、求一个论坛一天的在线人数分布 一、合并两个有序链表1.1、题目描述1.2、思路1.3、代码实现1.4、小结 二、删除字符串 s1 中在字符串 s2 中出现的字符2.1、题目描述2.2、思路2.3、代码实现2.4、小结 三、求一个论坛…

项目进度类知识要点

单代号网络图 六标时法正推法逆推法 六标时法最早开始时间(ES)工期最早完成时间(EF)活动名称/活动编号最迟开始时间(LS)浮动时间(总时差)最迟完成时间(LF) 关键路径、计算项目的总工期 关键路径是最大长度关键路径上的活动是关键活动关键活动之和为总工期 关键路径变化问题…

华为18级工程师三年心血终成趣谈网络协议文档(附大牛讲解)

前言 虽然在大学的时候大家都学过网络协议 &#xff0c;但是肯定感觉网络协议的知识点非常多 &#xff0c;非常复杂。学的时候就浑浑噩噩&#xff0c;真正到了实践中更是糊里糊涂&#xff0c;一旦工作中遇到了网络问题&#xff0c;除了会简单地 ping 几下 &#xff0c;基本没有…

安装Jmeter

Jmeter是Java语言开发,所以需要java环境,所以先安装jdk 1.安装JDK(1.8版本以上) 下载&#xff1a; https://www.oracle.com/java/technologies/javase/javase-jdk8-downloads.html 找到对应的版本: 点击exe文件安装,跟着向导下一步 2.安装jmeter 下载 Jmeter &#xff1a; 版…

【Java】JVM(六)

垃圾回收 分代回收理论 当前商业虚拟机的垃圾回收器&#xff0c;大多遵循“分代收集”的理论来进行设计&#xff0c;这个理论大体上是这么描述的&#xff1a; 1、绝大部分的对象都是朝生夕死。 2、熬过多次垃圾回收的对象就越难回收。 根据以上两个理论&#xff0c;朝生夕…

【Java】JVM学习(四)

对象的分配 JVM中对象的创建过程 对象的内存分配 虚拟机遇到一条new指令时&#xff0c;首先检查是否被类加载器加载&#xff0c;如果没有&#xff0c;那必须先执行相应的类加载过程。 类加载就是把class加载到JVM的运行时数据区的过程。 1&#xff09;检查加载 首先检查这…

从选题、创作、编辑、推广到优化,23个必用的内容营销工具

咱们做内容营销的&#xff0c;要懂营销懂产品&#xff0c;看得懂技术语言&#xff0c;写得了行业洞察&#xff0c;做出来的内容要有创意还要接地气&#xff0c;专业内容也不能落下&#xff0c;除了会写&#xff0c;还要会运营会设计会剪视频&#xff0c;简直就是全才嘛。 但是…

你知道游戏配音怎么制作吗?教你游戏配音教程怎么做

曾经&#xff0c;有一个叫小明的游戏迷&#xff0c;他对于游戏世界充满了热爱和想象。每当他控制着自己喜爱的角色在游戏中冒险时&#xff0c;他总是希望能够为这些角色赋予独特的声音&#xff0c;让它们真正活起来。然而&#xff0c;他却面临一个问题&#xff1a;游戏配音教程…

【CTF-Reverse中的加密算法】密码算法特征识别,变种密码算法分析

上一章中我们带领大家了解了加密算法——RC4,TEA,Base64算法的原理&#xff0c;但是加密算法远不止这些&#xff0c;需要大家自行去学习&#xff0c;在这一章中&#xff0c;我来带领大家了解密码算法特征识别&#xff0c;变种密码算法分析。 一.密码算法特征识别 1.什么是特征…

详解MySQL的常用数据类型

文章目录 一、MySQL 数据类型1.1、mysql中编码和字符 二、数值类型2.1、整数类型的长度2.2、浮点型 三、字符串类型3.1、字符串类型长度 四、日期和时间类型4.1、DATETIME 五、二进制数据类型六、使用建议 一、MySQL 数据类型 MySQL支持很多数据类型&#xff0c;以便我们能在复…

实现定时任务

1 问题 定时任务中&#xff0c;每天统计一下今日博客的各项数据&#xff0c;并以邮件的形式发送给自己。 2 方法 .首先在某目录下新建任务文件 crontest.cron&#xff0c;用于存在定时任务语句。.相同目录新建 hello.py 文件&#xff0c;并且编辑这个文件写一句简单的 print(He…

【JUC进阶】05. 偏向锁

目录 1、前言 2、偏向锁 2.1、基本原理 2.2、使用场景 3、获取偏向锁 4、何时撤销 4.1、到达安全点 4.2、其他线程尝试竞争偏向锁 4.3、重新计算hashcode 5、小结 1、前言 偏向锁是Java并发编程中一种重要的锁机制&#xff0c;它针对特定的线程进行优化&#xff0c;…

项目集活动—项目集收尾阶段活动

项目集收尾阶段活动从项目集组件完成了所有输出的交付&#xff0c;且项目集开始交付预期效益。某些情况下&#xff0c;项目集治理可能决定在所有组件完成之前就提前执行项目集收尾。无论哪种情况&#xff0c;此阶段项目集活动的目标都是释放项目集资源&#xff0c;支持将剩余项…

区分BOM和DOM,区分window、document、html、body

https://blog.csdn.net/xswl134679/article/details/128795161 JavaScript三大组成部分 1. ECMAScript ECMAScript是JavaScript的语法标准&#xff0c;由ECMA&#xff08;欧洲计算机厂家协会&#xff09;制定的。 2. BOM BOM即浏览器对象模型&#xff08;brower object mode…

Contrastive Learning in Image (CVPR 2023)

文章目录 1. Open Vocabulary Semantic Segmentation with Patch Aligned Contrastive Learning &#xff08;图文匹配&#xff09;1.目标2.任务类型3.解决思路4. 总结 2. MaskCLIP: Masked Self-Distillation Advances Contrastive Language-Image Pretraining &#xff08;图…

数学模型在水环境评价、防洪评价、排污口论证、水质、水量、水生态、水动力等方面的应用

目录 专题一、一维水动力模型模拟一河道水流的应用 专题二、一维复杂河网模型构建及建筑物设置 专题三、一维水质模型在环境影响评价中的应用 专题四、平面二维水动力模型的构建河验证 专题五、平面二维水动力模型在防洪影响评价中的应用 专题六、平面二维水动力水质模型…