SpringBoot如何使用JPA操作数据库?-阿里云开发者社区

开发者社区> 阿靖哦> 正文

SpringBoot如何使用JPA操作数据库?

简介: 介绍如何在SpringBoot中使用JPA,并介绍JPA的相关知识点和原理
+关注继续查看

Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套 JPA 应用框架,底层使用了 Hibernate 的 JPA 技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用SpringDataJPA可以极大提高开发效率!让我们解脱DAO层的操作,基本上所有CRUD都可以依赖于它来实现

本文将介绍如何在SpringBoot中的使用案例和原理分析

一、SpringBoot使用

1、导入依赖

swagger和common为附加包,不使用的话可以不需要导的哈

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <scope>runtime</scope>
</dependency>
<dependency>
   <groupId>cn.gjing</groupId>
   <artifactId>tools-starter-swagger</artifactId>
   <version>1.3.0</version>
</dependency>
<dependency>
   <groupId>cn.gjing</groupId>
   <artifactId>tools-common</artifactId>
   <version>1.2.5</version>
</dependency>

2、配置文件

server:
  port: 8082
spring:
  application:
    name: jpa-demo
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/demo?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2b8
    password: root
    username: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.zaxxer.hikari.HikariDataSource
    hikari:
      minimum-idle: 1
      maximum-pool-size: 5
      idle-timeout: 30000
      connection-timeout: 20000
  jpa:
    # 是否打印sql
    show-sql: true
    hibernate:
      # 开启自动建表功能,一般选update,每次启动会对比实体和数据表结构是否相同,不相同会更新
      ddl-auto: update
    # 设置创表引擎为Innodb,不然默认为MyiSam
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

3、创建实体类

/**
 * @author Gjing
 **/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "jpa_user")
@EntityListeners(AuditingEntityListener.class)
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name", columnDefinition = "varchar(10) not null comment '用户名'")
    @ApiModelProperty(name = "userName",value = "用户名")
    private String userName;

    @Column(name = "user_age", columnDefinition = "int not null comment '年龄'")
    @ApiModelProperty(name = "userAge",value = "年龄")
    private Integer userAge;

    @Column(name = "user_phone", columnDefinition = "varchar(11) not null comment '手机号'")
    @ApiModelProperty(name = "userPhone", value = "手机号")
    private String userPhone;

    @Column(name = "create_time", columnDefinition = "datetime")
    @CreatedDate
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    private Date createTime;

    @Column(name = "update_time", columnDefinition = "datetime")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    @LastModifiedDate
    private Date updateTime;
}

4、定义Repository层

    /**
     * 通过手机号查询
     * @param userPhone 手机号
     * @return user
     */
    User findByUserPhone(String userPhone);
    /**
     * 根据用户名分页查询
     * @param userName 用户名
     * @param pageable 分页对象
     * @return page
     */
    Page<User> findByUserName(String userName, Pageable pageable);

    /**
     * 根据用户id更新用户
     * @param userId 用户id
     * @param userName 用户名
     */
    @Query("update User u set u.userName = ?1 where u.id = ?2")
    @Modifying
    @Transactional(rollbackFor = Exception.class)
    void updateById(String userName, Long userId);

    /**
     * 查询指定用户
     * @param userPhone 用户号码
     * @return user
     */
    @Query("select u from User u where u.userPhone = ?1")
    User findUserByUserPhone(String userPhone);

5、定义service层

/**
 * @author Gjing
 **/
@Service
public class UserService {
    @Resource
    private UserRepository userRepository;

    /**
     * 保存用户
     *
     * @param userDto 用户传输对象
     */
    public void saveUser(UserDto userDto) {
        User userDb = userRepository.findByUserPhone(userDto.getUserPhone());
        if (userDb != null) {
            throw new ServiceException("用户已存在");
        }
        User user = userRepository.saveAndFlush(User.builder().userName(userDto.getUserName())
                .userAge(userDto.getUserAge())
                .userPhone(userDto.getUserPhone())
                .build());
    }

    /**
     * 分页查询用户列表
     *
     * @param pageable 分页条件
     * @return PageResult
     */
    public PageResult<List<User>> listForUser(Pageable pageable) {
        Page<User> userPage = userRepository.findAll(pageable);
        return PageResult.of(userPage.getContent(), userPage.getTotalPages());
    }

    /**
     * 删除用户
     *
     * @param userId 用户id
     */
    public void deleteUser(Long userId) {
        userRepository.findById(userId).ifPresent(u -> userRepository.delete(u));
    }

    /**
     * 更新用户
     *
     * @param userName 用户名
     * @param userId   用户id
     */
    public void updateUser(String userName, Long userId) {
        userRepository.updateById(userName, userId);
    }

    /**
     * 根据手机号查询
     *
     * @param userPhone 手机号
     * @return user
     */
    public User findByUserPhone(String userPhone) {
        return userRepository.findUserByUserPhone(userPhone);
    }

    /**
     * 动态查询
     *
     * @param age      岁数
     * @param userName 用户名
     * @return list
     */
    public List<User> dynamicFind(Integer age, String userName) {
        Specification<User> specification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            if (ParamUtils.isNotEmpty(age)) {
                predicateList.add(criteriaBuilder.equal(root.get("userAge"), age));
            }
            if (ParamUtils.isNotEmpty(userName)) {
                predicateList.add(criteriaBuilder.equal(root.get("userName"), userName));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        return userRepository.findAll(specification);
    }

6、定义接口

/**
 * @author Gjing
 **/
@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    @PostMapping("/user")
    @ApiOperation(value = "增加用户", httpMethod = "POST")
    @NotNull
    public ResponseEntity saveUser(@RequestBody UserDto userDto) {
        userService.saveUser(userDto);
        return ResponseEntity.ok("添加成功");
    }

    @DeleteMapping("/user/{user_id}")
    @ApiOperation(value = "删除用户", httpMethod = "DELETE")
    public ResponseEntity deleteUser(@PathVariable("user_id") Long userId) {
        userService.deleteUser(userId);
        return ResponseEntity.ok("删除成功");
    }

    @GetMapping("/user_page")
    @ApiOperation(value = "分页查询用户列表", httpMethod = "GET")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page", value = "页数(0开始)", required = true, dataType = "int", paramType = "query", defaultValue = "0"),
            @ApiImplicitParam(name = "size", value = "条数", required = true, dataType = "int", paramType = "query", defaultValue = "5")
    })
    @NotNull
    public ResponseEntity<PageResult<List<User>>> listForUser(Integer page, Integer size) {
        return ResponseEntity.ok(userService.listForUser(PageRequest.of(page, size, Sort.Direction.DESC,"id")));
    }

    @GetMapping("/user/{user_phone}")
    @ApiOperation(value = "根据手机号查询", httpMethod = "GET")
    public ResponseEntity<User> findUser(@PathVariable("user_phone") String userPhone) {
        return ResponseEntity.ok(userService.findByUserPhone(userPhone));
    }

    @PutMapping("/user")
    @ApiOperation(value = "更新用户信息", httpMethod = "PUT")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userId", value = "用户id", required = true, dataType = "long", paramType = "Query"),
            @ApiImplicitParam(name = "userName", value = "用户名", required = true, dataType = "String", paramType = "Query")
    })
    @NotNull
    public ResponseEntity updateUser(Long userId, String userName) {
        userService.updateUser(userName, userId);
        return ResponseEntity.ok("更新成功");
    }

    @GetMapping("/user_list")
    @ApiOperation(value = "动态查询用户", httpMethod = "GET")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "userAge", value = "用户年龄", dataType = "int", paramType = "Query"),
            @ApiImplicitParam(name = "userName", value = "用户名", dataType = "String", paramType = "Query")
    })
    public ResponseEntity<List<User>> userList(Integer userAge, String userName) {
        return ResponseEntity.ok(userService.dynamicFind(userAge, userName));
    }
}

这样启动后就能对数据进行增删查的功能了,其中我们dao层的方法并没有定义过多方法,因为JPA内部已经帮我们提供了基本的CRUD功能

二、原理分析

为啥我们可以使用一些我们并没有定义的功能呢,接下来就一起一探究竟吧

1、执行过程

我们启动之前的案例,对其中任意一个方法打个端点,然后执行的时候
debug.jpg

会发现我们自己定义的userRepository被注入了一个代理类,也就是jpaRepository的实现类simpleJpaRepository,继续debug,发现,当进入使用dao层方法的时候,会进入到这个代理类,然后经过一些拦截器,最终进入到QueryExecutorMethodInterceptor.doInvoke这个方法中,这个拦截器主要做的事情就是判断方法类型,然后执行对应的操作,我们定义的findByUserPhone属于自定义查询,继续debug会发现进入了getExecution()获取查询策略方法,在执行execute()会先选择一个对应的策略,

    protected JpaQueryExecution getExecution() {
        if (this.method.isStreamQuery()) {
            return new StreamExecution();
        } else if (this.method.isProcedureQuery()) {
            return new ProcedureExecution();
        } else if (this.method.isCollectionQuery()) {
            return new CollectionExecution();
        } else if (this.method.isSliceQuery()) {
            return new SlicedExecution(this.method.getParameters());
        } else if (this.method.isPageQuery()) {
            return new PagedExecution(this.method.getParameters());
        } else {
            return (JpaQueryExecution)(this.method.isModifyingQuery() ? new ModifyingExecution(this.method, this.em) : new SingleEntityExecution());
        }
    }

如上述代码所示,根据method变量实例化时的查询设置方式,实例化不同的JpaQueryExecution子类实例去运行。我们的findByUserPhone最终落入了SingleEntityExecution返回单个实例的Execution, 继续debug会发现,进入了createQuery()方法,正是在这个方法里进行了Sql的拼装。
仔细观看这个类的代码时,会发现在构造方法中,有JpaQueryMethod类,这其实就是接口中带有@Query注解方法的全部信息,包括注解,类名,实参等的存储类。前面提到的QueryExecutorMethodInterceptor类,里面出现了一个private final Map<Method, RepositoryQuery> queries;,查看RepositoryQuery会发现里面有个QueryMethod,由此可以得出,一个RepositoryQuery代表了Repository接口中的一个方法,根据方法头上注解不同的形态,将每个Repository接口中的方法分别映射成相对应的RepositoryQuery实例。
实例所有类型

  • NamedQuery:使用javax.persistence.NamedQuery注解访问数据库的形式,内部就会根据此注解选择创建一个NamedQuery实例;
  • NativeJpaQuery:方法头上@Query注解的nativeQuery属性如果显式的设置为true,也就是使用原生SQL,此时就会创建NativeJpaQuery实例;
  • PartTreeJpaQuery:方法头上未进行@Query注解,就会使用JPA识别的方式进行sql语句拼接,此时内部就会创建一个PartTreeJpaQuery实例;
  • SimpleJpaQuery:方法头上@Query注解的nativeQuery属性缺省值为false,也就是使用JPQL,此时会创建SimpleJpaQuery实例;
  • StoredProcedureJpaQuery:在Repository接口的方法头上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是调用存储过程的方式访问数据库,此时在jpa内部就会根据@Procedure注解而选择创建一个StoredProcedureJpaQuery实例;

2、启动流程

在启动的时候会实例化一个Repositories,它会去扫描所有的class,然后找出由我们定义的、继承自org.springframework.data.repository.Repositor的接口,然后遍历这些接口,针对每个接口依次创建如下几个实例:

  • SimpleJpaRepository:进行默认的 DAO 操作,是所有 Repository 的默认实现;
  • JpaRepositoryFactoryBean:装配 bean,装载了动态代理Proxy,会以对应的DAO的beanName为key注册到DefaultListableBeanFactory中,在需要被注入的时候从这个bean中取出对应的动态代理Proxy注入给DAO;
  • JdkDynamicAopProxy:动态代理对应的InvocationHandler,负责拦截DAO接口的所有的方法调用,然后做相应处理,比如findByUserPhone()被调用的时候会先经过这个类的invoke方法;

JpaRepositoryFactoryBean.getRepository()方法被调用的过程中,还是在实例化QueryExecutorMethodInterceptor这个拦截器的时候,spring 会去为我们的方法创建一个PartTreeJpaQuery,在它的构造方法中同时会实例化一个PartTree对象。PartTree定义了一系列的正则表达式,全部用于截取方法名,通过方法名来分解查询的条件,排序方式,查询结果等等,这个分解的步骤是在进程启动时加载 Bean 的过程中进行的,当执行查询的时候直接取方法对应的PartTree用来进行sql的拼装,然后进行DB的查询,返回结果。

简要概括就是

在启动的时候扫描所有继承自 Repository 接口的 DAO 接口,然后为其实例化一个动态代理,同时根据它的方法名、参数等为其装配一系列DB操作组件,在需要注入的时候为对应的接口注入这个动态代理,在 DAO 方法被调用的时会走这个动态代理,然后经过一系列的方法拦截路由到最终的 DB 操作执行器JpaQueryExecution,然后拼装 sql,执行相关操作,返回结果。

三、JPA相关知识点

1、基本查询

JPA查询主要有两种方式,继承JpaRepository后使用默认提供的,也可以自己自定义。以下列举了一部分

  • 默认的
    List<T> findAll();

    List<T> findAll(Sort var1);

    List<T> findAllById(Iterable<ID> var1);

    <S extends T> List<S> saveAll(Iterable<S> var1);

    void flush();

    <S extends T> S saveAndFlush(S var1);

    void deleteInBatch(Iterable<T> var1);

    void deleteAllInBatch();

    T getOne(ID var1);

    <S extends T> List<S> findAll(Example<S> var1);

    <S extends T> List<S> findAll(Example<S> var1, Sort var2);
  • 自定义
    User findByUserPhone(String userPhone);

自定义的关键字如下

关键字 例子 JPQL
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)

2、分页查询

分页查询在实际使用中非常普遍了,jpa已经帮我们实现了分页的功能,在查询的方法中,需要传入参数Pageable,当查询中有多个参数的时候Pageable建议做为最后一个参数传入。Pageable是Spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,以之前编写的为例:

/**
 * @author Gjing
 **/
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
    /**
     * 分页查询
     * @param pageable 分页对象
     * @return Page<User>
     */
    @Override
    Page<User> findAll(Pageable pageable);

    /**
     * 根据用户名分页查询
     * @param userName 用户名
     * @param pageable 分页对象
     * @return page
     */
    Page<User> findByUserName(String userName, Pageable pageable);
}

接口

    @GetMapping("/list")
    @ApiOperation(value = "分页查询用户列表", httpMethod = "GET")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "page", value = "页数(0开始)", required = true, dataType = "int", paramType = "query", defaultValue = "0"),
            @ApiImplicitParam(name = "size", value = "条数", required = true, dataType = "int", paramType = "query", defaultValue = "5")
    })
    @NotNull
    public ResponseEntity<PageResult<List<User>>> listForUser(Integer page, Integer size) {
        return ResponseEntity.ok(userService.listForUser(PageRequest.of(page, size, Sort.Direction.DESC,"id")));
    }

由上可看出,我们使用了PageRequest.of()构建了pageable对象,并指定了按id进行排序

3、自定义SQL

大部分的SQL都可以根据方法名定义的方式来实现,但是由于某些原因我们想使用自定义的SQL来查询,JPA也是完美支持的;在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying。也可以根据需要添加@Transactional对事务的支持,查询超时的设置等

/**
 * @author Gjing
 **/
@Repository
public interface UserRepository extends JpaRepository<User,Long> {
    /**
     * 通过手机号查询
     * @param userPhone 手机号
     * @return user
     */
    User findByUserPhone(String userPhone);

    /**
     * 分页查询
     * @param pageable 分页对象
     * @return Page<User>
     */
    @Override
    Page<User> findAll(Pageable pageable);

    /**
     * 根据用户名分页查询
     * @param userName 用户名
     * @param pageable 分页对象
     * @return page
     */
    Page<User> findByUserName(String userName, Pageable pageable);

    /**
     * 根据用户id更新用户
     * @param userId 用户id
     * @param userName 用户名
     * @return int
     */
    @Query("update User u set u.userName = ?1 where u.id = ?2")
    @Modifying
    @Transactional(rollbackFor = Exception.class)
    int updateById(String userName, Long userId);

    /**
     * 查询指定用户
     * @param userPhone 用户号码
     * @return user
     */
    @Query("select u from User u where u.userPhone = ?1")
    User findUserByUserPhone(String userPhone);
}

4、动态查询

JPA极大的帮助了我们更方便的操作数据库,但是,在实际场景中,往往会碰到复杂查询的场景,前端会动态传一些参数请求接口,这时候就需要使用到动态查询了。
首先需要在继承一个接口JpaSpecificationExecutor,需要传入一个泛型,填写你的具体实体对象即可,接下来在service层实现一个动态的查询方法

    /**
     * 动态查询
     * @param age 岁数
     * @param userName 用户名
     * @return list
     */
    public List<User> dynamicFind(Integer age, String userName) {
        Specification<User> specification = (Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicateList = new ArrayList<>();
            if (ParamUtil.isNotEmpty(age)) {
                predicateList.add(criteriaBuilder.equal(root.get("userAge"), age));
            }
            if (ParamUtil.isNotEmpty(userName)) {
                predicateList.add(criteriaBuilder.equal(root.get("userName"), userName));
            }
            return criteriaBuilder.and(predicateList.toArray(new Predicate[0]));
        };
        return userRepository.findAll(specification);
    }

这里定义了根据年龄和名称动态查询用户列表,这里只举了个栗子,更多玩法需要自己去拓展研究

5、相关注解

注解 作用
@Entity 表明这是个实体bean
@Table 指定实体对应的数据表,里面可配置数据表名和索引,该索引可以不使用,默认表名为实体类名
@Column 字段,可设置字段的相关属性
@Id 指明这个字段为id
@GeneratedValue ID生成策略
@Transient 指定该字段不用映射到数据库
@Temporal 指定时间精度
@JoinColumn 一对一:本表中指向另一个表的外键。一对多:另一个表指向本表的外键
@OneToOne、@OneToMany、@ManyToOne 对应hibernate配置文件中的一对一,一对多,多对一
@Modifying 搭配@Query使用,查询操作除外
@Query 自定义SQL注解,默认JPQL,如果要使用原生sql,可以指定nativeQuery==true

6、进阶用法

1、返回指定的VO

很多时候,我们有些字段并不需要,所有我们可以定义一个VO去接JPA查询回来的结果

定义个VO

/**
 * @author Gjing
 **/
@Getter
@Setter
@ToString
@AllArgsConstructor
public class UserVO {
    private Long id;
    private String userName;
    private Integer userAge;
    private String userPhone;
}

在Repository接口增加一个接口

/**
 * @author Gjing
 **/
@Repository
public interface UserRepository extends JpaRepository<User,Long> , JpaSpecificationExecutor<User> {
    /**
     * 分页查询用户
     * @param pageable 分页条件
     * @return Page<UserVO>
     */
    @Query("select new com.gj.domain.vo.UserVO(u.id,u.userName,u.userAge,u.userPhone) from User as u")
    Page<UserVO> findAllUser(Pageable pageable);
}

这里可以看到,我们采用了自定义HQL,语句中我们new了一个我们定义的VO去获取结果,这里有个注意的地方就是,你的VO一定要有构造方法与之对应,这样我们就能返回个VO啦,pageable参数JPA也会帮我们自动去分页查询,是不是很方便呢,那就快用起来吧

7、知识点扩展

1、如果想在往数据库插入数据时候,自动带上添加时间可以在对应字段标注@CreatedDate,想在更新的时候自动添加更新时间可以在对应字段使用@LastModifiedDate,另外必须在启动类使用@EnableJpaAuditing注解以及在对应的实体类使用@EntityListeners(AuditingEntityListener.class),否则无效
2、在@Column中定义字段的默认值,在默认情况下JPA是不会进行默认插值的,这时候,可以在实体类上加个注解@DynamicInsert


本文到此结束啦,篇幅比较长,如果哪里写的有误可以在评论区留言哈,也希望各位可以关注我哦,我会持续发布更多新的文章。本Demo的源代码地址:SprignBoot-Demo

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
【Hadoop Summit Tokyo 2016】使用Amaterasu项目进行数据操作
本讲义出自Yaniv Rodenski与Karel Alfonso在Hadoop Summit Tokyo 2016上的演讲,主要分享了数据管道的相关知识以及其主要作用,并且分享了数据管道建造者的原型、数据操作以及协作等相关内容,还分享了大数据应用的持续集成的案例,最后还介绍了Apache下的开源分布式资源管理框架Mesos的相关内容。
1408 0
如何让接口文档自动生成,SpringBoot中Swagger的使用
在开发过程中,java后端需要与客户端进行交互,需要将后端的接口及参数写成文档给调用者查阅。一个问题也有此而生,需求改动频繁,接口设计也会随之改动,文档修改的不及时会带来很大的问题。 Swagger是一个自动生成文档的工具,可以在线查阅文档,减少了开发人员的负担,下面我们就来看看如何在SpringBoot中使用Swagger。
939 0
Python爬虫入门教程 51-100 Python3爬虫通过m3u8文件下载ts视频-Python爬虫6操作
什么是m3u8文件 M3U8文件是指UTF-8编码格式的M3U文件。M3U文件是记录了一个索引纯文本文件,打开它时播放软件并不是播放它,而是根据它的索引找到对应的音视频文件的网络地址进行在线播放。 原视频数据分割为很多个TS流,每个TS流的地址记录在m3u8文件列表中 比如我这里有一个m3u8文件...
1544 0
Spark学习之键值对(pair RDD)操作(3)
Spark学习之键值对(pair RDD)操作(3) 1. 我们通常从一个RDD中提取某些字段(如代表事件时间、用户ID或者其他标识符的字段),并使用这些字段为pair RDD操作中的键。 2. 创建pair RDD 1)读取本身就是键值对的数据 2)一个普通的RDD通过map()转为pair RDD,传递的函数需要返回键值对。 Python中使用第一个单词作为
1223 0
使用iOS原生sqlite3框架对sqlite数据库进行操作(二)
使用iOS原生sqlite3框架对sqlite数据库进行操作
18 0
Node.js使用mongodb操作MongoDB数据库
Node.js使用mongodb操作MongoDB数据库
24 0
idea操作maven指令启动springboot项目
idea操作maven指令启动springboot项目
3550 0
+关注
阿靖哦
JAVA开发工程师
70
文章
65
问答
来源圈子
更多
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载