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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 介绍如何在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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
6天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
28 4
SpringBoot入门(4) - 添加内存数据库H2
|
8天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
20 2
SpringBoot入门(4) - 添加内存数据库H2
|
1天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
32 12
|
10天前
|
SQL Java 数据库
Spring Boot与Flyway:数据库版本控制的自动化实践
【10月更文挑战第19天】 在软件开发中,数据库的版本控制是一个至关重要的环节,它确保了数据库结构的一致性和项目的顺利迭代。Spring Boot结合Flyway提供了一种自动化的数据库版本控制解决方案,极大地简化了数据库迁移管理。本文将详细介绍如何使用Spring Boot和Flyway实现数据库版本的自动化控制。
12 2
|
21天前
|
Java 关系型数据库 MySQL
springboot学习五:springboot整合Mybatis 连接 mysql数据库
这篇文章是关于如何使用Spring Boot整合MyBatis来连接MySQL数据库,并进行基本的增删改查操作的教程。
34 0
springboot学习五:springboot整合Mybatis 连接 mysql数据库
|
Java 数据库 Spring
SpringBoot从数据库加载配置信息
版权声明:本文首发 http://asing1elife.com ,转载请注明出处。 https://blog.csdn.net/asing1elife/article/details/82811803 ...
2797 0
|
20天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
111 1
|
4天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
80 62
|
2天前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
9 2
|
5天前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。