前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Spring Data JPA 最佳实践

Spring Data JPA 最佳实践

作者头像
芋道源码
发布于 2019-12-13 10:12:25
发布于 2019-12-13 10:12:25
2.9K00
代码可运行
举报
文章被收录于专栏:芋道源码1024芋道源码1024
运行总次数:0
代码可运行
  • 前言
  • 扩展接口用法
  • Spring Data Jpa最佳实践
    • 一、继承SimpleJpaRepository实现类
    • 二、集成QueryDsl结构化查询
    • 三、集成p6spy打印执行的sql
  • 结语

  • 本文系基友「KL」投稿
  • 原文地址:kailing.pub/article/index/arcid/263.html
  • Gitee 地址:gitee.com/kailing

? 欢迎胖友给艿艿投稿。只有输入没有输出的学习,是个 BUG 。

前言

Spring Data Jpa框架的目标是显著减少实现各种持久性存储的数据访问层所需的样板代码量。Spring Data Jpa存储库抽象中的中央接口是Repository。它需要领域实体类以及领域实体ID类型作为类型参数来进行管理。该接口主要用作标记接口,以捕获要使用的类型并帮助您发现扩展该接口的接口。CrudRepository、JpaRepository是更具体的数据操作抽象,一般我们在项目中使用的时候定义我们的领域接口然后继承CrudRepository或JpaRepository即可实现实现基础的CURD方法了,但是这种用法有局限性,不能处理超复杂的查询,而且稍微复杂的查询代码写起来也不是很优雅,所以下面看看怎么最优雅的解决这个问题。

扩展接口用法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/11
 */
@Repository
public interface SendLogRepository extends JpaRepository<SendLog,Integer> {

    /**
     * 派生的通过解析方法名称的查询
     * @param templateName
     * @return
     */
    List<SendLog> findSendLogByTemplateName(String templateName);

    /**
     * HQL
     * @param templateName
     * @return
     */
    @Query(value ="select SendLog from  SendLog s where s.templateName = :templateName")
    List<SendLog> findByTempLateName(String templateName);

    /**
     * 原生sql
     * @param templateName
     * @return
     */
    @Query(value ="select s.* from  sms_sendlog s where s.templateName = :templateName",nativeQuery = true)
    List<SendLog> findByTempLateNameNative(String templateName);
}

优点:

  • 1、这种扩展接口的方式是最常见的用法,继承JpaRepository接口后,立马拥有基础的CURD功能
  • 2、还可以通过特定的方法名做解析查询,这个可以算spring Data Jpa的最特殊的特性了。而且主流的IDE对这种使用方式都有比较好的自动化支持,在输入要解析的方法名时会给出提示。
  • 3、可以非常方便的以注解的形式支持HQL和原生SQL

缺陷:

  • 1、复杂的分页查询支持不好

缺陷就一条,这种扩展接口的方式要实现复杂的分页查询,有两种方式,而且这两种方式代码写起来都不怎么优雅,而且会把大量的条件拼接逻辑写在调用查询的service层。

第一种实例查询(Example Query)方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public void testExampleQuery() {
        SendLog log = new SendLog();
        log.setTemplateName("kl");
        /*
         * 注意:withMatcher方法的propertyPath参数值填写领域对象的字段值,而不是实际的表字段
         */
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withMatcher("templateName", match -> match.contains());
        Example example = Example.of(log, matcher);

        Pageable pageable = PageRequest.of(0, 10);
        Page<SendLog> logPage = repository.findAll(example, pageable);
    }

上面代码实现的语义是模糊查询templateName等于"kl"的记录并分页,乍一看这个代码还过得去哈,其实当查询的条件多一点,这种代码就会变得又臭又长,而且只支持基础的字符串类型的字段查询,如果查询条件有时间筛选的话就不支持了,在复杂点多表关联的话就更GG了,所以这种方式不合格直接上黑名单了。

第二种继承JpaSpecificationExecutor方式:

JPA 2引入了一个标准API,您可以使用它来以编程方式构建查询。Spring Data JPA提供了使用JPA标准API定义此类规范的API。这种方式首先需要继承JpaSpecificationExecutor接口,下面我们用这种方式实现和上面相同语义的查询:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    public void testJpaSpecificationQuery() {
        String templateName = "kk";
        Specification specification = (Specification) (root, query, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.like(root.get("templateName"),templateName);
            query.where(predicate);
            return predicate;
        };

        Pageable pageable = PageRequest.of(0, 2);
        Page<SendLog> logPage = sendLogRepository.findAll(specification, pageable);
    }

这种方式显然更对味口了吧,而且也支持复杂的查询条件拼接,比如日期等。唯一的缺憾是领袖对象的属性字符串需要手写了,而且接口只会提供findAll(@Nullable Specification spec, Pageable pageable)方法,各种复杂查询逻辑拼接都要写在service层。对于架构分层思想流行了这么多年外加强迫症的人来说实在是不能忍,如果单独封装一个Dao类编写复杂的查询又显的有点多余和臃肿

Spring Data Jpa最佳实践

在详细介绍最佳实践前,先思考和了解一个东西,Spring Data Jpa是怎么做到继承一个接口就能实现各种复杂查询的呢?这里其实是一个典型的代理模式的应用,只要继承了最底层的Repository接口,在应用启动时就会帮你生成一个代理实例,而真正的目标类才是最终执行查询的类,这个类就是:SimpleJpaRepository,它实现了JpaRepository、JpaSpecificationExecutor的所有接口,所以只要基于SimpleJpaRepository定制Repository基类,就能拥有继承接口一样的查询功能,而且可以在实现类里编写复杂的查询方法了。

一、继承SimpleJpaRepository实现类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/8
 */
public abstract class BaseJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> {

    public EntityManager em;

    BaseJpaRepository(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
    }
}

构造一个SimpleJpaRepository实例,只需要一个领域对象的类型,和EntityManager 实例即可,EntityManager在Spring的上下文中已经有了,会自动注入。领域对象类型在具体的实现类中注入即可。如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/11
 */
@Repository
public class SendLogJpaRepository extends BaseJpaRepository<SendLog,Integer> {

    public SendLogJpaRepository(EntityManager em) {
        super(SendLog.class, em);
    }
    /**
     * 原生查询
     * @param templateName
     * @return
     */
    public SendLog findByTemplateName(String templateName){
        String sql = "select * from send_log where templateName = :templateName";
        Query query =em.createNativeQuery(sql);
        query.setParameter("templateName",templateName);
        return (SendLog) query.getSingleResult();
    }

    /**
     * hql查询
     * @param templateName
     * @return
     */
    public SendLog findByTemplateNameNative(String templateName){
        String hql = "from SendLog where templateName = :templateName";
        TypedQuery<SendLog> query =em.createQuery(hql,SendLog.class);
        query.setParameter("templateName",templateName);
       return query.getSingleResult();
    }

    /**
     *  JPASpecification 实现复杂分页查询
     * @param logDto
     * @param pageable
     * @return
     */
    public Page<SendLog> findAll(SendLogDto logDto,Pageable pageable) {
        Specification specification = (Specification) (root, query, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.conjunction();
            if(!StringUtils.isEmpty(logDto.getTemplateName())){
                predicate.getExpressions().add( criteriaBuilder.like(root.get("templateName"),logDto.getTemplateName()));
            }
            if(logDto.getStartTime() !=null){
                predicate.getExpressions().add(criteriaBuilder.greaterThanOrEqualTo(root.get("createTime").as(Timestamp.class),logDto.getStartTime()));
            }
            query.where(predicate);
            return predicate;
        };
        return  findAll(specification, pageable);
    }
}

通过继承BaseJpaRepository,使SendLogJpaRepository拥有了JpaRepository、JpaSpecificationExecutor接口中定义的所有方法功能。而且基于抽象基类中EntityManager实例,也可以非常方便的编写HQL和原生SQL查询等。最赏心悦目的是不仅拥有了最基本的CURD等功能,而且超复杂的分页查询也不分家了。只是JpaSpecification查询方式还不是特别出彩,下面继续最佳实践

二、集成QueryDsl结构化查询

Querydsl是一个框架,可通过其流畅的API来构造静态类型的类似SQL的查询。这是Spring Data Jpa文档中对QueryDsl的描述。Spring Data Jpa对QueryDsl的扩展支持的比较好,基本可以无缝集成使用。Querydsl定义了一套和JpaSpecification类似的接口,使用方式上也类似,由于QueryDsl多了一个maven插件,可以在编译期间生成领域对象操作实体,所以在拼接复杂的查询条件时相比较JpaSpecification显的更灵活好用,特别在关联到多表查询的时候。下面看下怎么集成:

1、快速集成

因为之前有写过最简单的QueryDsl集成方式,所以这里就不在赘述了,具体参见《Querydsl结构化查询之jpa》,

2、丰富BaseJpaRepository基类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/8
 */
public abstract class BaseJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> {

    public EntityManager em;
    protected final QuerydslJpaPredicateExecutor<T> jpaPredicateExecutor;

    BaseJpaRepository(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
        this.jpaPredicateExecutor = new QuerydslJpaPredicateExecutor<>(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em, SimpleEntityPathResolver.INSTANCE, getRepositoryMethodMetadata());
    }
}

在BaseJpaRepository基类中新增了QuerydslJpaPredicateExecutor实例,它是Spring Data Jpa基于QueryDsl的一个实现。用来执行QueryDsl的Predicate相关查询。集成QueryDsl后,复杂分页查询的画风就变的更加清爽了,如:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * QSendLog实体是QueryDsl插件自动生成的,插件会自动扫描加了@Entity的实体,生成一个用于查询的EntityPath类
     */
    private  final  static QSendLog sendLog = QSendLog.sendLog;

    public Page<SendLog> findAll(SendLogDto logDto, Pageable pageable) {
        BooleanExpression expression = sendLog.isNotNull();
        if (logDto.getStartTime() != null) {
            expression = expression.and(sendLog.createTime.gt(logDto.getStartTime()));
        }
        if (!StringUtils.isEmpty(logDto.getTemplateName())) {
            expression = expression.and(sendLog.templateName.like("%"+logDto.getTemplateName()+"%"));
        }
        return jpaPredicateExecutor.findAll(expression, pageable);
    }

到目前为止,实现相同的复杂分页查询,代码已经非常的清爽和优雅了,在复杂的查询在这种模式下也变的非常的清晰。但是,这还不是十分完美的。还有两个问题需要解决下:

  • QuerydslJpaPredicateExecutor实现的方法不支持分页查询同时又有字段排序。下面是它的接口定义,可以看到,要么分页查询一步到位但是没有排序,要么排序查询返回List列表自己封装分页。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface QuerydslPredicateExecutor<T> {
    Optional<T> findOne(Predicate predicate);
    Iterable<T> findAll(Predicate predicate);
    Iterable<T> findAll(Predicate predicate, Sort sort);
    Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
    Iterable<T> findAll(OrderSpecifier<?>... orders);
    Page<T> findAll(Predicate predicate, Pageable pageable);
    long count(Predicate predicate);
    boolean exists(Predicate predicate);
}
  • 复杂的多表关联查询QuerydslJpaPredicateExecutor不支持

3、最终的BaseJpaRepository形态

Spring Data Jpa对QuerDsl的支持毕竟有限,但是QueryDsl是有这种功能的,像上面的场景就需要特别处理了。最终改造的BaseJpaRepository如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * @author: kl @kailing.pub
 * @date: 2019/11/8
 */
public abstract class BaseJpaRepository<T, ID> extends SimpleJpaRepository<T, ID> {

    protected final JPAQueryFactory jpaQueryFactory;
    protected final QuerydslJpaPredicateExecutor<T> jpaPredicateExecutor;
    protected final EntityManager em;
    private final EntityPath<T> path;
    protected final Querydsl querydsl;

    BaseJpaRepository(Class<T> domainClass, EntityManager em) {
        super(domainClass, em);
        this.em = em;
        this.jpaPredicateExecutor = new QuerydslJpaPredicateExecutor<>(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em, SimpleEntityPathResolver.INSTANCE, getRepositoryMethodMetadata());
        this.jpaQueryFactory = new JPAQueryFactory(em);
        this.path = SimpleEntityPathResolver.INSTANCE.createPath(domainClass);
        this.querydsl = new Querydsl(em, new PathBuilder<T>(path.getType(), path.getMetadata()));
    }

    protected Page<T> findAll(Predicate predicate, Pageable pageable, OrderSpecifier<?>... orders) {
        final JPAQuery countQuery = jpaQueryFactory.selectFrom(path);
        countQuery.where(predicate);
        JPQLQuery<T> query = querydsl.applyPagination(pageable, countQuery);
        query.orderBy(orders);
        return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
    }
}

新增了findAll(Predicate predicate, Pageable pageable, OrderSpecifier… orders)方法,用于支持复杂分页查询的同时又有字段排序的查询场景。其次的改动是引入了JPAQueryFactory实例,用于多表关联的复杂查询。使用方式如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
    /**
     * QSendLog实体是QueryDsl插件自动生成的,插件会自动扫描加了@Entity的实体,生成一个用于查询的EntityPath类
     */
    private  final  static QSendLog qSendLog = QSendLog.sendLog;
    private  final static QTemplate qTemplate = QTemplate.template;

    public Page<SendLog> findAll(SendLogDto logDto, Template template, Pageable pageable) {
        JPAQuery  countQuery = jpaQueryFactory.selectFrom(qSendLog).leftJoin(qTemplate);
        countQuery.where(qSendLog.templateCode.eq(qTemplate.code));
        if(!StringUtils.isEmpty(template.getName())){
            countQuery.where(qTemplate.name.eq(template.getName()));
        }
        JPQLQuery query = querydsl.applyPagination(pageable, countQuery);
        return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchCount);
    }

三、集成p6spy打印执行的sql

上面的功能以及十分完美了,但是谈到最佳实践似乎少了一个打印SQL的功能。在使用Jpa的结构化语义构建复杂查询时,经常会因为各种原因导致查询的结果集不是自己想要的,但是又没法排查,因为不知道最终执行的sql是怎么样的。Spring Data Jpa也有打印sql的功能,但是比较鸡肋,它打印的是没有替换查询参数的sql,没法直接复制执行。所以这里推荐一个工具p6spy,p6spy是一个打印最终执行sql的工具,而且可以记录sql的执行耗时。使用起来也比较方便,简单三步集成:

1、引入依赖

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
            <dependency>
                <groupId>p6spy</groupId>
                <artifactId>p6spy</artifactId>
                <version>${p6spy.version}</version>
            </dependency>

2、修改数据源链接字符串

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
jdbc:mysql://127.0.0.1:3306 改成 jdbc:p6spy:mysql://127.0.0.1:3306

3、添加配置spy.propertis配置

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
appender=com.p6spy.engine.spy.appender.Slf4JLogger
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
customLogMessageFormat = executionTime:%(executionTime)| sql:%(sqlSingleLine)

这个是最简化的自定义打印的配置,更多配置可参考:https://p6spy.readthedocs.io/en/latest/configandusage.html

结语

最后的BaseJpaRepository功能上基本满足了所有的查询需求,又做了基础查询和复杂查询的不分离,不至于把大量的复杂查询拼接逻辑写到service层,或者是新建的复杂查询类里。彻底解决了文首提出的那些问题。基于QueryDsl的复杂查询代码逻辑清晰,结构优雅,极力推荐使用。最后,在安利下p6spy,一个非常实用的打印sql的工具,可以帮助排查分析JPA最终生成执行的sql语句,其打印的sql语句可以直接复制到mysql管理工具中执行的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 芋道源码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
腾讯云 COS 文件 Web 直传
dandelion1990
2024/01/03
7570
利用STS临时密钥服务快速搭建直传页面的实践
作者简介 吴硕卫:腾讯云技术支持工程师,现负责腾讯云存储产品的技术支持专项工作。 为了实现权限分离,提供更细颗粒度的权限控制,有效的控制帐号生效周期,本文通过腾讯云 CAM 产品的 STS(临时访问凭证)来实现部署,调试,验证等一系列的操作体验。 主要介绍基于腾讯云对象存储 COS,如何使用 COS 签名工具和 HTTP 请求工具 Postman 来验证临时密钥的有效性,以及如何快速实现一个 Web 端页面的文件直传功能。服务器上只需要生成和管理访问密钥,无需关心细节,文件数据都存放在腾讯云 COS
云存储
2020/08/07
3.7K0
JavaScript 结合 Go 实现 临时密钥(STS)
最近有个项目,需要通过网页上传文件到对象存储中,在查看COS快速入门时,文档建议使用获取临时密钥:
2023/08/15
4620
对象储存cos-腾讯云对象储存cos
COS是腾讯云提供的对象存储服务,功能非常强大,可以作为文件服务器,CDN加速,静态网页服务,还提供了图片的常用操作。
张天华
2019/08/01
24.8K0
对象储存cos-腾讯云对象储存cos
微信小程序上传图片到腾讯COS存储桶实战
作者:火种 最近为了实现在小程序上传图片到腾讯COS存储桶,把官方提供的demo,看了一遍又一遍,试了又试,错了又错,踏过一个又一个坑,终于从不懂,到懵懂,到懂。>.<,好累,在此过程就不赘述,直接开始,希望和我一样的小白也可能轻松用上腾讯COS存储桶。
加菲猫的VFP
2022/05/26
3.9K0
微信小程序上传图片到腾讯COS存储桶实战
腾讯云COS对象存储的简单使用
叮当哥之前买了一年的腾讯云服务器,昨日偶然发现腾讯云送了叮当哥半年的cos对象存储服务器,于是就撸起袖子传了几张珍藏的大图上去,现将其上传的简单使用步骤总结一波(其它操作参加官方SDK文档API)。
宋先生
2019/07/18
19.2K1
HTTP请求的ECONNRESET
某部分客户业务使用cos的node.js的sdk来进行上传下载等操作,近期客户端偶尔触发上传文件报错{ error: { code: 'ECONNRESET' } } 的异常。
sheazhang
2020/05/01
21.3K0
HTTP请求的ECONNRESET
利用STS临时密钥服务快速搭建直传页面的实践
为了实现权限分离,提供更细隔离度的权限控制,有效的控制帐号生效周期,本文通过腾讯云CAM产品的STS(临时访问凭证)来实现部署,调试,验证等一系列的操作体验。
吴硕卫
2020/07/28
6.3K2
利用STS临时密钥服务快速搭建直传页面的实践
腾讯云 COS 访问方法
签名即输入 SecretId、SecretKey、有效时间时间戳,原始请求,得到以下签名内容的过程:
dandelion1990
2024/01/02
1.6K0
腾讯云 COS 访问方法
智能结构化OCR实现个人小账本demo(基于NodeJS)
智能结构化(Smart Structure Optical Character Recognition )融合了业界领先的深度学习技术、图像检测技术以及 OCR 大模型能力,能够实现不限版式的结构化信息抽取。本文以NodeJS为例,实现一个基于智能结构化OCR的个人小账本demo。
治电小白菜
2024/12/26
2480
智能结构化OCR实现个人小账本demo(基于NodeJS)
COS SDK有Flutter和React Native版本啦
对象存储COS简介 对象存储(Cloud Object Storage,COS)是腾讯云提供的一种存储海量文件的分布式存储服务,用户可通过网络随时存储和查看数据。腾讯云 COS 使所有用户都能使用具备高扩展性、低成本、可靠和安全的数据存储服务。 COS 通过控制台、API、SDK 和工具等多样化方式简单、快速地接入,实现了海量数据存储和管理。通过 COS 可以进行任意格式文件的上传、下载和管理。腾讯云提供了直观的 Web 管理界面,同时遍布全国范围的 CDN 节点可以对文件下载进行加速。 导语 Flu
云存储
2023/05/18
8960
COS SDK有Flutter和React Native版本啦
存储腾讯云(例:上传图片)
服务器属于个人的,需要一定的权限才能自由上传图片,这个负责权限验证的其实就是秘钥。拥有秘钥是进行上传的基础条件。
玖柒的小窝
2021/10/26
16.9K0
存储腾讯云(例:上传图片)
腾讯云智能体开发平台 API 的开发实战:基于RAG的论文阅读助手
科研工作者,尤其是那些奋战在知识前沿的探索者们,正面临着信息洪流的巨大挑战。每天都有如潮水般涌来的学术论文,这既是机遇,也是重负。
Lion 莱恩呀
2025/03/02
5581
腾讯云智能体开发平台 API 的开发实战:基于RAG的论文阅读助手
对象存储 Node.js SDK cos-nodejs-sdk-v5 Typescript 声明文件
用到腾讯云对象存储,使用Node.js SDK cos-nodejs-sdk-v5,没有 typescript 的声明文件,自己写了一个。
苦少
2020/05/11
2K0
使用腾讯云 SCF 云函数压缩 COS 对象存储文件
腾讯云serverless团队
2017/10/17
16.9K2
在Minio以STS方式获得临时凭据上传文件
一个常见的场景是:需要让客户端来上传图片,面临的安全性问题。给与客户端永久凭据无疑是很大风险的,我们还可以选择“给予一个临时凭据,这个凭据关联到一个 授权的用户,我们可以随时停用和修改这个用户具有的权限”
张云飞Vir
2023/04/19
7.5K0
在Minio以STS方式获得临时凭据上传文件
智能识别,一键兑换:腾讯云OCR智能结构化高级版识别在零售行业的应用实践
https://cloud.tencent.com/developer/article/2323707
快乐的小白
2025/01/12
2310
智能识别,一键兑换:腾讯云OCR智能结构化高级版识别在零售行业的应用实践
简化车辆登记流程:利用腾讯云OCR实现自动化信息识别
项目中有一块,需要用到上传车牌车牌号到系统里,用了下腾讯云的ocr车牌号识别做了个小功能。通过腾讯云的orc识别,将车牌号录入到后台。
快乐的小白
2023/09/05
5030
简化车辆登记流程:利用腾讯云OCR实现自动化信息识别
Vue Cos 上传
cos_utils.js工具函数 /* npm i cos-js-sdk-v5 --save //文件对象上传: utils.cos_utils.putObject( { bucket:'spoongsb-1253997188', region:'ap-chengdu' },'lecturer-files',fileObj,(progerss)=>{ console.log(progerss) },(data
前端小鑫同学
2022/12/24
1.1K0
一文带你看懂 前后端之间图片的上传与回显
上传文件需要发送请求。在这些请求中,浏览器将数据拆分为小的“块”,然后通过连接逐个发送这些块。这是必要的,因为文件可能过大而无法一次性发送作为一个庞大的有效负载。
用户10143704
2024/03/22
3K0
推荐阅读
相关推荐
腾讯云 COS 文件 Web 直传
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档