10 | JpaSpecificationExecutor 实现的原理是什么

news2024/11/27 8:29:25

在开始讲解之前,请先思考几个问题:

  1. JpaSpecificationExecutor 如何创建?
  2. 它的使用方法有哪些?
  3. toPredicate 方法如何实现?

带着这些问题,我们开始探索。先看一个例子感受一下 JpaSpecificationExecutor 具体的用法。

JpaSpecificationExecutor 使用案例

我们假设一个后台管理页面根据 name 模糊查询、sex 精准查询、age 范围查询、时间区间查询、address 的 in 查询这样一个场景,来查询 user 信息,我们看看这个例子应该怎么写。

第一步:创建 User 和 UserAddress 两个实体。 代码如下:

复制代码

package com.example.jpa.example1;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import javax.persistence.*;
import java.io.Serializable;
import java.time.Instant;
import java.util.Date;
import java.util.List;
/**
* 用户基本信息表
**/
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "addresses")
public class User implements Serializable {
   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   private Long id;
   private String name;
   private String email;
   @Enumerated(EnumType.STRING)
   private SexEnum sex;
   private Integer age;
   private Instant createDate;
   private Date updateDate;
   @OneToMany(mappedBy = "user")
   @JsonIgnore
   private List<UserAddress> addresses;
}
enum SexEnum {
   BOY,GIRL
}
package com.example.jpa.example1;
import lombok.*;
import javax.persistence.*;
/**
 * 用户地址表
 */
@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "user")
public class UserAddress {
   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   private Long id;
   private String address;
   @ManyToOne(cascade = CascadeType.ALL)
   private User user;
}

第二步:创建 UserRepository 继承 JpaSpecificationExecutor 接口。

复制代码

package com.example.jpa.example1;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface UserRepository extends JpaRepository<User,Long>, JpaSpecificationExecutor<User> {
}

第三步:创建一个测试用例进行测试。

复制代码

@DataJpaTest
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class UserJpeTest {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private UserAddressRepository userAddressRepository;
    private Date now = new Date();
    /**
     * 提前创建一些数据
     */
    @BeforeAll
    @Rollback(false)
    @Transactional
    void init() {
        User user = User.builder()
                .name("jack")
                .email("123456@126.com")
                .sex(SexEnum.BOY)
                .age(20)
                .createDate(Instant.now())
                .updateDate(now)
                .build();
        userAddressRepository.saveAll(Lists.newArrayList(UserAddress.builder().user(user).address("shanghai").build(),
                UserAddress.builder().user(user).address("beijing").build()));
    }
    @Test
    public void testSPE() {
        //模拟请求参数
        User userQuery = User.builder()
                .name("jack")
                .email("123456@126.com")
                .sex(SexEnum.BOY)
                .age(20)
                .addresses(Lists.newArrayList(UserAddress.builder().address("shanghai").build()))
                .build();
                //假设的时间范围参数
        Instant beginCreateDate = Instant.now().plus(-2, ChronoUnit.HOURS);
        Instant endCreateDate = Instant.now().plus(1, ChronoUnit.HOURS);
        //利用Specification进行查询
        Page<User> users = userRepository.findAll(new Specification<User>() {
            @Override
            public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                List<Predicate> ps = new ArrayList<Predicate>();
                if (StringUtils.isNotBlank(userQuery.getName())) {
                    //我们模仿一下like查询,根据name模糊查询
                    ps.add(cb.like(root.get("name"),"%" +userQuery.getName()+"%"));
                }
                if (userQuery.getSex()!=null){
                    //equal查询条件,这里需要注意,直接传递的是枚举
                    ps.add(cb.equal(root.get("sex"),userQuery.getSex()));
                }
                if (userQuery.getAge()!=null){
                    //greaterThan大于等于查询条件
                    ps.add(cb.greaterThan(root.get("age"),userQuery.getAge()));
                }
                if (beginCreateDate!=null&&endCreateDate!=null){
                    //根据时间区间去查询创建
                    ps.add(cb.between(root.get("createDate"),beginCreateDate,endCreateDate));
                }
                if (!ObjectUtils.isEmpty(userQuery.getAddresses())) {
                    //联表查询,利用root的join方法,根据关联关系表里面的字段进行查询。
                    ps.add(cb.in(root.join("addresses").get("address")).value(userQuery.getAddresses().stream().map(a->a.getAddress()).collect(Collectors.toList())));
                }
                return query.where(ps.toArray(new Predicate[ps.size()])).getRestriction();
            }
        }, PageRequest.of(0, 2));
        System.out.println(users);
    }
}

我们看一下执行结果。

复制代码

Hibernate: select user0_.id as id1_1_, user0_.age as age2_1_, user0_.create_date as create_d3_1_, user0_.email as email4_1_, user0_.name as name5_1_, user0_.sex as sex6_1_, user0_.update_date as update_d7_1_ from user user0_ inner join user_address addresses1_ on user0_.id=addresses1_.user_id where (user0_.name like ?) and user0_.sex=? and user0_.age>20 and (user0_.create_date between ? and ?) and (addresses1_.address in (?)) limit ?

此 SQL 的参数如下:

Drawing 0.png

此 SQL 就是查询 User inner Join user_address 之后组合成的查询 SQL,基本符合我们的预期,即不同的查询条件。我们通过这个例子大概知道了 JpaSpecificationExecutor 的用法,那么它具体是什么呢?

JpaSpecificationExecutor 语法详解

我们依然通过看 JpaSpecificationExecutor 的源码,来了解一下它的几个使用方法,如下所示:

复制代码

public interface JpaSpecificationExecutor<T> {
   //根据 Specification 条件查询单个对象,要注意的是,如果条件能查出来多个会报错
   T findOne(@Nullable Specification<T> spec);
   //根据 Specification 条件,查询 List 结果
   List<T> findAll(@Nullable Specification<T> spec);
   //根据 Specification 条件,分页查询
   Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
   //根据 Specification 条件,带排序的查询结果
   List<T> findAll(@Nullable Specification<T> spec, Sort sort);
   //根据 Specification 条件,查询数量
   long count(@Nullable Specification<T> spec);
}

其返回结果和 Pageable、Sort,我们在前面课时都有介绍过,这里我们重点关注一下 Specification。看一下 Specification 接口的代码。

Drawing 1.png

通过看其源码就会发现里面提供的方法很简单。其中,下面一段代码表示组合的 and 关系的查询条件。

复制代码

default Specification<T> and(@Nullable Specification<T> other) {
   return composed(this, other, (builder, left, rhs) -> builder.and(left, rhs));
}

下面是静态方法,创建 where 后面的 Predicate 集合。

复制代码

static <T> Specification<T> where(@Nullable Specification<T> spec)

下面是默认方法,创建 or 条件的查询参数。

复制代码

default Specification<T> or(@Nullable Specification<T> other)

这是静态方法,创建 Not 的查询条件。

复制代码

static <T> Specification<T> not(@Nullable Specification<T> spec)

上面这几个方法比较简单,我就不一一细说了,我们主要看一下需要实现的方法:toPredicate。

复制代码

Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

toPredicate 这个方法是我们用到的时候需要自己去实现的,接下来我们详细介绍一下。

首先我们在刚才的 Demo 里面设置一个断点,看到如下界面。

Drawing 2.png

这里可以分别看到 Root 的实现类是 RootImpl,CriteriaQuery 的实现类是 CriteriaQueryImpl,CriteriaBuilder 的实现类是 CriteriaBuilderImpl。

复制代码

javax.persistence.criteria.Root
javax.persistence.criteria.CriteriaQuery
javax.persistence.criteria.CriteriaBuilder

其中,上面三个接口是 Java Persistence API 定义的接口。

复制代码

org.hibernate.query.criteria.internal.path.RootImpl
rg.hibernate.query.criteria.internal.CriteriaQueryImpl
org.hibernate.query.criteria.internal.CriteriaBuilderImpl

而这个三个实现类都是由 Hibernate 进行实现的,也就是说 JpaSpecificationExecutor 封装了原本需要我们直接操作 Hibernate 中 Criteria 的 API 方法。

下面分别解释上述三个参数。

Root`` root

代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那 root 里面就是这张表里面的字段,而这些字段只是 JPQL 的实体字段而已。我们可以通过里面的 Path get(String attributeName),来获得我们想要操作的字段。

复制代码

类似于我们上面的:root.get("createDate")等操作
CriteriaQuery<?> query

代表一个 specific 的顶层查询对象,它包含着查询的各个部分,比如 select 、from、where、group by、order by 等。CriteriaQuery 对象只对实体类型或嵌入式类型的 Criteria 查询起作用。简单理解为,它提供了查询 ROOT 的方法。常用的方法有如下几种:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

复制代码

正如我们上面where的用法:query.where(.....)一样

这个语法比较简单,我们在其方法后面加上相应的参数即可。下面看一个 group by 的例子。

Drawing 4.png

如上图所示,我们加入 groupyBy 某个字段,SQL 也会有相应的变化。那么我们再来看第三个参数。

CriteriaBuilder cb

CriteriaBuilder 是用来构建 CritiaQuery 的构建器对象,其实就相当于条件或者条件组合,并以 Predicate 的形式返回。它基本上提供了所有常用的方法,如下所示:

Drawing 5.png

我们直接通过此类的 Structure 视图就可以看到都有哪些方法。其中,and、any 等用来做查询条件的组合;类似 between、equal、exist、ge、gt、isEmpty、isTrue、in 等用来做查询条件的查询,类似下图的一些方法。

Drawing 6.png

而其中 Expression 很简单,都是通过 root.get(…) 某些字段即可返回,正如下面的用法。

复制代码

Predicate p1=cb.like(root.get(“name”).as(String.class), “%”+uqm.getName()+“%”);
Predicate p2=cb.equal(root.get("uuid").as(Integer.class), uqm.getUuid());
Predicate p3=cb.gt(root.get("age").as(Integer.class), uqm.getAge());

我们利用 like、equal、gt 可以生产 Predicate,而 Predicate 可以组合查询。比如我们预定它们之间是 and 或 or 的关系:Predicate p = cb.and(p3,cb.or(p1,p2));

我们让 p1 和 p2 是 or 的关系,并且得到的 Predicate 和 p3 又构成了 and 的关系。你可以发现它的用法还是比较简单的,正如我们开篇所说的 Junit 中 test 里面一样的写法。

关于 JpaSpecificationExecutor 的语法我们就介绍完了,其实它里面的功能相当强大,只是我们发现 Spring Data JPA 介绍得并不详细,只是一笔带过,可能他们认为写框架的人才能用到,所以介绍得不多。

如果你想了解更多语法的话,可以参考 Hibernate 的文档:https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#criteria。我们再来看看 JpaSpecificationExecutor 的实现原理。

JpaSpecificationExecutor 原理分析

我们先看一下 JpaSpecificationExecutor 的类图。

Drawing 7.png

从图中我们可以看得出来:

  1. JpaSpecificationExecutor 和 JpaRepository 是平级接口,而它们对应的实现类都是 SimpleJpaRepository;
  2. Specification 被 ExampleSpecification 和 JpaSpecificationExector 使用,用来创建查询;
  3. Predicate 是 JPA 协议里面提供的查询条件的根基;
  4. SimpleJpaRepository 利用 EntityManager 和 criteria 来实现由 JpaSpecificationExector 组合的 query。

那么我们再直观地看一下 JpaSpecificationExecutor 接口里面的方法 findAll 对应的 SimpleJpaRepository 里面的实现方法 findAl,我们通过工具可以很容易地看到相应的实现方法,如下所示:

Drawing 8.png

你要知道,得到 TypeQuery 就可以直接操作JPA协议里面相应的方法了,那么我们看下 getQuery(spec,pageable)的实现过程。

Drawing 9.png

之后一步一步 debug 就可以了。

Drawing 10.png

到了上图所示这里,就可以看到:

  1. Specification`` spec 是我们测试用例写的 specification 的匿名实现类;
  2. 由于是方法传递,所以到第 693 行断点的时候,才会执行我们在测试用里面写的 Specification;
  3. 我们可以看到这个方法最后是调用的 EntityManager,而 EntitytManger 是 JPA 操作实体的核心原理,我在下一课时讲自定义 Repsitory 的时候再详细介绍;
  4. 从上面的方法实现过程中我们可以看得出来,所谓的JpaSpecificationExecutor原理,用一句话概况,就是利用Java Persistence API定义的接口和Hibernate的实现,做了一个简单的封装,方便我们操作JPA协议中 criteria 的相关方法。

到这里,原理和使用方法,我们基本介绍完了。你可能会有疑问:这个感觉有点重要,但是一般用不到吧?那么接下来我们看看 JpaSpecificationExecutor 的实战应用场景是什么样的。

JpaSpecificationExecutor 实战应用场景

其实JpaSpecificationExecutor 的目的不是让我们做日常的业务查询,而是给我们提供了一种自定义 Query for rest 的架构思路,如果做日常的增删改查,肯定不如我们前面介绍的 Defining Query Methods 和 @Query 方便。

那么来看下,实战过程中如何利用 JpaSpecificationExecutor 写一个框架。

MySpecification 自定义

我们可以自定义一个Specification 的实现类,它可以实现任何实体的动态查询和各种条件的组合。

复制代码

package com.example.jpa.example1.spe;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.*;
public class MySpecification<Entity> implements Specification<Entity> {
   private SearchCriteria criteria;
   public MySpecification (SearchCriteria criteria) {
      this.criteria = criteria;
   }
   /**
    * 实现实体根据不同的字段、不同的Operator组合成不同的Predicate条件
    *
    * @param root            must not be {@literal null}.
    * @param query           must not be {@literal null}.
    * @param builder  must not be {@literal null}.
    * @return a {@link Predicate}, may be {@literal null}.
    */
   @Override
   public Predicate toPredicate(Root<Entity> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
      if (criteria.getOperation().compareTo(Operator.GT)==0) {
         return builder.greaterThanOrEqualTo(
               root.<String> get(criteria.getKey()), criteria.getValue().toString());
      }
      else if (criteria.getOperation().compareTo(Operator.LT)==0) {
         return builder.lessThanOrEqualTo(
               root.<String> get(criteria.getKey()), criteria.getValue().toString());
      }
      else if (criteria.getOperation().compareTo(Operator.LK)==0) {
         if (root.get(criteria.getKey()).getJavaType() == String.class) {
            return builder.like(
                  root.<String>get(criteria.getKey()), "%" + criteria.getValue() + "%");
         } else {
            return builder.equal(root.get(criteria.getKey()), criteria.getValue());
         }
      }
      return null;
   }
}

我们通过 `` 泛型,解决不同实体的动态查询(当然了,我只是举个例子,这个方法可以进行无限扩展)。我们通过 SearchCriteria 可以知道不同的字段是什么、值是什么、如何操作的,看一下代码:

复制代码

package com.example.jpa.example1.spe;
import lombok.*;
/**
 * @author jack,实现不同的查询条件,不同的操作,针对Value;
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SearchCriteria {
   private String key;
   private Operator operation;
   private Object value;
}

其中的 Operator 也是我们自定义的。

复制代码

package com.example.jpa.example1.spe;
public enum Operator {
   /**
    * 等于
    */
   EQ("="),
   /**
    * 等于
    */
   LK(":"),
   /**
    * 不等于
    */
   NE("!="),
   /**
    * 大于
    */
   GT(">"),
   /**
    * 小于
    */
   LT("<"),
   /**
    * 大于等于
    */
   GE(">=");
   Operator(String operator) {
      this.operator = operator;
   }
   private String operator;
}

在 Operator 枚举里面定义了逻辑操作符(大于、小于、不等于、等于、大于等于……也可以自己扩展),并在 MySpecification 里面进行实现。那么我们来看看它是怎么用的,写一个测试用例试一下。

复制代码

/**
 * 测试自定义的Specification语法
 */
@Test
public void givenLast_whenGettingListOfUsers_thenCorrect() {
    MySpecification<User> name =
        new MySpecification<User>(new SearchCriteria("name", Operator.LK, "jack"));
MySpecification<User> age =
        new MySpecification<User>(new SearchCriteria("age", Operator.GT, 2));
List<User> results = userRepository.findAll(Specification.where(name).and(age));
    System.out.println(results.get(0).getName());
}

你就会发现,我们在调用findAll 组合 Predicate 的时候就会非常简单,省去了各种条件的判断和组合,而省去的这些逻辑可以全部在我们的框架代码 MySpecification 里面实现。

那么如果把这个扩展到 API 接口层面会是什么样的结果呢?我们来看下。

利用 Specification 创建 search 为查询条件的 Rest API 接口

先创建一个 Controller,用来接收 search 这样的查询条件:类似 userssearch=lastName:doe,age>25 的参数。

复制代码

package com.example.jpa.example1.web;
import com.example.jpa.example1.User;
import com.example.jpa.example1.UserRepository;
import com.example.jpa.example1.spe.SpecificationsBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
public class UserController {
    @Autowired
    private UserRepository repo;
    @RequestMapping(method = RequestMethod.GET, value = "/users")
    @ResponseBody
    public List<User> search(@RequestParam(value = "search") String search) {
        Specification<User> spec = new SpecificationsBuilder<User>().buildSpecification(search);
        return repo.findAll(spec);
    }
}

Controller 里面非常简单,利用 SpecificationsBuilder 生成我们需要的 Specification 即可。那么我们看看 SpecificationsBuilder 里面是怎么写的。

复制代码

package com.example.jpa.example1.spe;
import com.example.jpa.example1.User;
import org.springframework.data.jpa.domain.Specification;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
 * 处理请求参数
 * @param <Entity>
 */
public class SpecificationsBuilder<Entity> {
   private final List<SearchCriteria> params;

   //初始化params,保证每次实例都是一个新的ArrayList
   public SpecificationsBuilder() {
      params = new ArrayList<SearchCriteria>();
   }

   //利用正则表达式取我们search参数里面的值,解析成SearchCriteria对象
   public Specification<Entity> buildSpecification(String search) {
      Pattern pattern = Pattern.compile("(\\w+?)(:|<|>)(\\w+?),");
      Matcher matcher = pattern.matcher(search + ",");
      while (matcher.find()) {
         this.with(matcher.group(1), Operator.fromOperator(matcher.group(2)), matcher.group(3));
      }
      return this.build();
   }
   //根据参数返回我们刚才创建的SearchCriteria
   private SpecificationsBuilder with(String key, Operator operation, Object value) {
      params.add(new SearchCriteria(key, operation, value));
      return this;
   }
   //根据我们刚才创建的MySpecification返回所需要的Specification
   private Specification<Entity> build() {
      if (params.size() == 0) {
         return null;
      }
      List<Specification> specs = params.stream()
            .map(MySpecification<User>::new)
            .collect(Collectors.toList());
      Specification result = specs.get(0);
      for (int i = 1; i < params.size(); i++) {
         result = Specification.where(result)
               .and(specs.get(i));
      }
      return result;
   }
}

通过上面的代码,我们可以看到通过自定义的 SpecificationsBuilder,来处理请求参数 search 里面的值,然后转化成我们上面写的 SearchCriteria 对象,再调用 MySpecification 生成我们需要的 Specification,从而利用 JpaSpecificationExecutor 实现查询效果。

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

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

相关文章

09 | JpaSpecificationExecutor 解决了哪些问题

QueryByExampleExecutor用法 QueryByExampleExecutor&#xff08;QBE&#xff09;是一种用户友好的查询技术&#xff0c;具有简单的接口&#xff0c;它允许动态查询创建&#xff0c;并且不需要编写包含字段名称的查询。 下面是一个 UML 图&#xff0c;你可以看到 QueryByExam…

ReID的各种Loss的pytorch实现

为了提高ReID的性能通常会采用softmax loss 联合 Triplet Loss和Center Loss来提高算法的性能。 本文对Triplet Loss和Cnetr Loss做一个总结&#xff0c;以简洁的方式帮助理解。 Triplet Loss和Center Loss都是从人脸识别领域里面提出来的&#xff0c;后面在各种图像检索任务…

笔记本Win10系统一键重装操作方法

笔记本电脑已经成为大家日常生活和工作中必不可少的工具之一&#xff0c;如果笔记本电脑系统出现问题了&#xff0c;那么就会影响到大家的正常操作。这时候就可以考虑给笔记本电脑重装系统了。接下来小编给大家介绍关于一键重装Win10笔记本电脑系统的详细步骤方法。 推荐下载 系…

遗传算法------微生物进化算法(MGA)

前言 该文章写在GA算法之后&#xff1a;GA算法 遗传算法 (GA)的问题在于没有有效保留好的父母 (Elitism), 让好的父母不会消失掉. Microbial GA (后面统称 MGA) 就是一个很好的保留 Elitism 的算法. 一句话来概括: 在袋子里抽两个球, 对比两个球, 把球大的放回袋子里, 把球小…

Qt中各个功能模块遵循的协议

Qt 中各个模块的协议&#xff0c;是在变化的&#xff0c;并不是一成不变 不同版本&#xff0c;协议有可能会变。同一版本&#xff0c;在不同时间期间&#xff0c;协议也可能会变 具体以官网为准

搜索引擎站群霸屏排名源码系统+关键词排名 前后端完整的搭建教程

开发搜索引擎站群霸屏排名系统是一项重要的策略&#xff0c;通过在搜索引擎中获得多个高排名站点&#xff0c;可以大大提高企业的品牌知名度&#xff0c;从而吸引更多的潜在客户和消费者。而且当潜在客户在搜索结果中看到多个与您的品牌相关的站点时&#xff0c;他们可能会认为…

EtherCAT报文-BRD(广播读)抓包分析

0.工具准备 1.EtherCAT主站 2.EtherCAT从站&#xff08;本文使用步进电机驱动器&#xff09; 3.Wireshark1.EtherCAT报文帧结构 EtherCAT使用标准的IEEE802.3 Ethernet帧结构&#xff0c;帧类型为0x88A4。EtherCAT数据包括2个字节的数据头和44-1498字节的数据。数据区由一个或…

ST-SSL:基于自监督学习的交通流预测模型

文章信息 文章题为“Spatio-Temporal Self-Supervised Learning for Traffic Flow Prediction”&#xff0c;是一篇发表于The Thirty-Seventh AAAI Conference on Artificial Intelligence (AAAI-23)的一篇论文。该论文主要针对交通流预测任务&#xff0c;结合自监督学习&#…

EtherCAT报文-BWR(广播写)抓包分析

0.工具准备 1.EtherCAT主站 2.EtherCAT从站&#xff08;本文使用步进电机驱动器&#xff09; 3.Wireshark1.EtherCAT报文帧结构 EtherCAT使用标准的IEEE802.3 Ethernet帧结构&#xff0c;帧类型为0x88A4。EtherCAT数据包括2个字节的数据头和44-1498字节的数据。数据区由一个或…

【2023研电赛】全国技术竞赛一等奖:基于FPGA的超低时延激光多媒体终端

该作品参与极术社区组织的研电赛作品征集活动&#xff0c;欢迎同学们投稿&#xff0c;获取作品传播推广&#xff0c;并有丰富礼品哦~ 基于FPGA的超低时延激光多媒体终端 参赛单位&#xff1a;华东师范大学 指导老师&#xff1a;刁盛锡 参赛队员&#xff1a;王泽宇 谢祖炜 秦子淇…

解读 | 自动驾驶系统中的多视点三维目标检测网络

原创 | 文 BFT机器人 01 背景 多视角三维物体检测网络&#xff0c;用于实现自动驾驶场景高精度三维目标检测&#xff0c;该网络使用激光雷达点云和RGB图像进行感知融合&#xff0c;以预测定向的三维边界框&#xff0c;相比于现有技术&#xff0c;取得了显著的精度提升。同时现…

【重要!合规政策更新】英国,儿童玩具相关产品卖家,请及时关注!EN71

合规政策更新&#xff01; 尊敬的卖家&#xff1a; 您好&#xff01; 我们此次联系您是因为您正在销售需要审批流程的商品。为此&#xff0c;亚马逊正在实施审批流程&#xff0c;以确认我们网站上提供的商品类型须符合指定的认证标准。要在亚马逊商城销售这些商品&#xff0c;您…

第六篇Android--ImageView、Bitmap

ImageView&#xff0c;和前面介绍的TextView、EditText&#xff0c;都继承自View都是View的子类。 ImageView 是用于呈现图片的视图。View可以理解为一个视图或控件。 1.简单使用 在drawable-xxhdpi文件夹下放一张图片&#xff1a; xml中把这张图片设置给ImageView&#xff0…

MySQL单表查询基础综合练习

一、单表查询 素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作 等 CREATE TABLE worker ( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL, 工资 float(8,2) NOT NULL, 政治面貌 v…

三勾知识付费(PHP+vue3)微信小程序平台+SAAS+前后端源码

项目介绍 三勾小程序商城基于thinkphp8element-plusuniapp打造的面向开发的小程序商城&#xff0c;方便二次开发或直接使用&#xff0c;可发布到多端&#xff0c;包括微信小程序、微信公众号、QQ小程序、支付宝小程序、字节跳动小程序、百度小程序、android端、ios端。 软件架…

BUUCTF学习(二):一起来撸猫

1、介绍 2、解题 &#xff08;1&#xff09;查看网页源代码 &#xff08;2&#xff09;解读代码内容 &#xff08;3&#xff09;得出结论 网址&#xff1a;一起来撸猫http://df4c147d-c7f4-4aac-a9d6-fdce2606ee18.node4.buuoj.cn:81/?catdog 第二题结束

PyTorch入门教学——在虚拟环境中安装Jupyter

1、简介 Jupyter Notebook是一个开源的web应用程序&#xff0c;可以使用它来创建和共享包含实时代码、方程、可视化和文本的文档。Jupyter Notebook是一个交互式笔记本&#xff0c;可以当作python编译器来使用。 2、安装 在安装Anaconda时是自带了Jupyter Notebook的&#x…

Unity第一人称移动和观察

创建一个可以自由移动的第一人称视角 人物通过WSAD进行前后左右移动&#xff0c;通过鼠标右键进行旋转 Step1:创建一个Player玩家&#xff0c;在节点下加两个子物体&#xff0c;一个摄像头和一个Capsule充当身体 Step2:创建一个脚本挂载在Player节点下&#xff0c;再在这个Pl…

ThreeJS-3D教学十-有宽度的line

webgl中线是没有宽度的&#xff0c;现实的应用中一般做法都是将线拓宽成面来绘制。默认threejs的线宽是无法调节的&#xff0c;需要用有厚度的线 THREE.Line2。 先看效果图&#xff1a; 看下代码&#xff1a; <!DOCTYPE html> <html lang"en"> <he…

2022年03月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python编程&#xff08;1~6级&#xff09;全部真题・点这里 C/C编程&#xff08;1~8级&#xff09;全部真题・点这里 一、单选题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 第1题 关于Python中的列表&#xff0c;下列描述错误的是?&#xff08; …