使用JBang构建Spring Boot Rest API教程

在 Java 开发领域,Spring Boot 已成为创建健壮、可扩展且可维护的 Web 应用程序的代名词。传统上,构建 Spring Boot 应用程序需要设置一个具有复杂目录结构、多个配置文件和各种依赖项的项目。然而,随着 JBang(一种轻量级 Java 脚本编写工具)的出现,您可以简化此过程并仅使用单个 Java 文件构建 Spring Boot Rest Api。在这篇博文中,我们将指导您完成在单个 Java 文件中使用 JBang 创建 Spring Boot Rest Api 的步骤。

JBang是什么?
JBang 是一个命令行工具,允许您直接从源文件运行 Java 代码,而不需要复杂的项目设置或编译。它对于创建轻量级脚本和简化开发过程特别有用。

在我们深入开发过程之前,请确保您的系统上安装了 JBang。您可以从JBang的官方网站安装它。

您可以克隆https://github.com/dmakariev/examples存储库。

git clone https://github.com/dmakariev/examples.git
cd examples/jbang/spring-boot-hello-world

入门
让我们创建一个简单的 Spring Boot Rest 服务来提供“Hello, World!”服务使用 JBang 发送消息。按着这些次序:

1、初始化新的 JBang 脚本
为您的项目创建一个新目录并使用终端导航到该目录。然后,创建一个新的 JBang 脚本文件.java,扩展名为springbootHelloWorld.java.

$ mkdir spring-boot-hello
$ cd spring-boot-hello
$ touch springbootHelloWorld.java

2、编写 Spring Boot 代码
在您喜欢的文本编辑器或集成开发环境 (IDE) 中打开该springbootHelloWorld.java文件并添加以下代码。

//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 21
//DEPS org.springframework.boot:spring-boot-starter-web:3.1.4



import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.bind.annotation.RequestParam;

@SpringBootApplication
@RestController
public class springbootHelloWorld {

    public static void main(String[] args) {
        SpringApplication.run(springbootHelloWorld.class, args);
    }

    @GetMapping(
"/")
    public String sayHi(@RequestParam(required = false, defaultValue =
"World") String name) {
        return
"Hello, " + name + "!";
    }
}

执行以下操作:

  • 准备要像 shell 脚本一样执行的文件
  • 定义应用程序所需的 Java 版本(Java 17 是 Spring Boot 3.x 的最低版本,但 Java 21 是当前的 LTS )
  • 导入必要的 Spring Boot 依赖项。
  • 定义 Spring Boot 应用程序类。
  • 定义一个带有返回“Hello, World!”的单个端点的 REST 控制器。

运行应用程序
保存文件并返回到您的终端。导航到包含文件的目录springbootHelloWorld.java并执行以下命令:

jbang springbootHelloWorld.java

或者
sh springbootHelloWorld.java

执行允许可执行权限
chmod +x springbootHelloWorld.java

你甚至可以像这样执行应用程序
./springbootHelloWorld.java


在上述所有情况下,JBang 将下载所需的 Spring Boot 依赖项并启动应用程序。您将看到指示 Spring Boot 应用程序正在运行的输出。

打开您的网络浏览器并导航至http://localhost:8080。您应该看到“Hello, World!”浏览器中显示的消息。

用JBang 来创建 Spring Boot完整单体
仅使用单个 Java 文件(用于后端)和单个 HTML 文件(用于前端)以及 JBang 来创建 Spring Boot Monolith。

这种方法对于快速原型设计、轻量级应用程序或当您想要降低开发环境的复杂性时非常方便。随着您的应用程序变得越来越复杂,您始终可以过渡到更传统的项目结构。JBang提供了一种灵活、高效的方法来开发 Java 应用程序,而无需进行重量级的项目设置。

1、初始化目录
为项目创建一个新目录,并使用终端导航到该目录。然后,创建 :

  • 一个扩展名为 .java 的 JBang 脚本空文件,如 springbootJpaVue.java。
  • 一个扩展名为 .html 的空文件,用于 Vue.js UI 应用程序,如 index-fetch.html。
  • 一个空的 Dockerfile
  • 一个空的 Docker Compose 文件 compose.yaml

$ mkdir spring-boot-jpa-vue
$ cd spring-boot-jpa-vue
$ touch springbootJpaVue.java
$ touch index-fetch.html
$ touch Dockerfile
$ touch compose.yaml

2、编写 Spring Boot 代码
在您喜欢的文本编辑器或集成开发环境(IDE)中打开 springbootJpaVue.java 文件,然后添加以下代码。

//usr/bin/env jbang "$0" "$@" ; exit $?
//JAVA 22
//DEPS org.springframework.boot:spring-boot-dependencies:3.2.4@pom
//DEPS org.springframework.boot:spring-boot-starter-web
//DEPS org.springframework.boot:spring-boot-starter-data-jpa
//DEPS org.springframework.boot:spring-boot-starter-actuator
//DEPS com.h2database:h2
//DEPS org.postgresql:postgresql
//DEPS org.projectlombok:lombok
//DEPS org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0

//JAVA_OPTIONS -Dserver.port=8080
//JAVA_OPTIONS -Dspring.datasource.url=jdbc:h2:mem:person-db;MODE=PostgreSQL;
//JAVA_OPTIONS -Dspring.h2.console.enabled=true -Dspring.h2.console.settings.web-allow-others=true
//JAVA_OPTIONS -Dmanagement.endpoints.web.exposure.include=health,env,loggers
//FILES META-INF/resources/index.html=index-fetch.html

//REPOS mavencentral,sb_snapshot=https://repo.spring.io/snapshot,sb_milestone=https://repo.spring.io/milestone
package com.makariev.examples.jbang;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.Optional;

import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;


@SpringBootApplication
public class springbootJpaVue {

    public static void main(String[] args) {
        SpringApplication.run(springbootJpaVue.class, args);
    }

}

@Component
@RequiredArgsConstructor
class InitialRecords {

    private final PersonRepository personRepository;

    @EventListener(ApplicationReadyEvent.class)
    public void exercise() {

        if (personRepository.count() > 0) {
            return;
        }
        List.of(
                new Person(1L,
"Ada", "Lovelace", 1815),
                new Person(2L,
"Niklaus", "Wirth", 1934),
                new Person(3L,
"Donald", "Knuth", 1938),
                new Person(4L,
"Edsger", "Dijkstra", 1930),
                new Person(5L,
"Grace", "Hopper", 1906),
                new Person(6L,
"John", "Backus", 1924)
        ).forEach(personRepository::save);
    }
}

@RestController
class HiController {

    @GetMapping(
"/hi")
    public String sayHi(@RequestParam(required = false, defaultValue =
"World") String name) {
        return
"Hello, " + name + "!";
    }
}

@RestController
@RequestMapping(
"/api/persons")
@RequiredArgsConstructor
class PersonController {

    private final PersonRepository personRepository;

    @GetMapping
    public Page<Person> findAll(Pageable pageable) {
        return personRepository.findAll(pageable);
    }

    @GetMapping(
"{id}")
    public Optional<Person> findById(@PathVariable(
"id") Long id) {
        return personRepository.findById(id);
    }

    @PostMapping
    public Person create(@RequestBody Person person) {
        return personRepository.save(person);
    }

    @PutMapping(
"{id}")
    public Person updateById(@PathVariable(
"id") Long id, @RequestBody Person person) {
        var loaded = personRepository.findById(id).orElseThrow();
        loaded.setFirstName(person.getFirstName());
        loaded.setLastName(person.getLastName());
        loaded.setBirthYear(person.getBirthYear());
        return personRepository.save(loaded);
    }

    @DeleteMapping(
"/{id}")
    public void deleteById(@PathVariable(
"id") Long id) {
        personRepository.deleteById(id);
    }
}

@Data
@Entity
@Table(name =
"person")
@NoArgsConstructor
@AllArgsConstructor
class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private int birthYear;
}

interface PersonRepository extends JpaRepository<Person, Long> {
}

3、编写 Vue.js 代码
在您喜欢的文本编辑器或集成开发环境(IDE)中打开 index-fetch.html 文件,然后添加以下代码。

<!DOCTYPE html>
<html>
    <head>
        <title>Person CRUD Application</title>
        <style>
            body {
                font-family: -apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
                margin: 0;
                padding: 0;
                background-color: f5f5f5;
            }

            app {
                max-width: 800px;
                margin: 0 auto;
                padding: 20px;
                background-color: fff;
                box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
            }

            h1 {
                text-align: center;
                margin-bottom: 20px;
            }

            ul {
                list-style: none;
                padding: 0;
            }

            li {
                border: 1px solid ddd;
                padding: 10px;
                margin: 10px 0;
                background-color: fff;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            button {
                padding: 5px 10px;
                background-color: #007bff;
                color: fff;
                border: none;
                cursor: pointer;
            }

            form {
                display: flex;
                flex-direction: column;
            }

            input {
                margin: 5px 0;
                padding: 5px;
                border: 1px solid ddd;
            }

            button[type=
"submit"] {
                background-color: #28a745;
            }

            .modal {
                position: fixed;
                z-index: 1;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                background-color: rgba(0, 0, 0, 0.4);
                justify-content: center;
                align-items: center;
            }

            .modal-content {
                background-color: fff;
                border: 1px solid ddd;
                padding: 20px;
                width: 70%;
                margin: 10% auto;
            }

            .pagination {
                display: flex;
                justify-content: center;
                margin-top: 20px;
            }

            .page-item {
                margin: 0 5px;
                cursor: pointer;
            }

            .page-item.active {
                font-weight: bold;
            }
        </style>
        <script src=
"https://cdn.jsdelivr.net/npm/vue@3.4.21/dist/vue.global.prod.js"></script>
    </head>
    <body>
        <div id=
"app">
            <h1>Person CRUD Application</h1>
            <button @click=
"showPersonModal(null)">Add Person</button>
            <ul>
                <li v-for=
"person in persons" :key="person.id">
                    {{ person.firstName }} {{ person.lastName }} ({{ person.birthYear }} year of birth)
                    <button @click=
"showPersonModal(person)">Edit</button>
                    <button @click=
"deletePerson(person.id)">Delete</button>
                </li>
            </ul>
            <div class=
"pagination">
                <span v-for=
"page in totalPages" :key="page" @click="changePage(page)" class="page-item" :class="{ active: currentPage === page }">{{ page }}</span>
            </div>

            <!-- Modal -->
            <div class=
"modal" v-if="modalVisible">
                <div class=
"modal-content">
                    <h2>{{ editMode ? 'Edit' : 'Add' }} Person</h2>
                    <form @submit.prevent=
"savePerson">
                        <input type=
"text" v-model="formData.firstName" placeholder="First Name" required>
                        <input type=
"text" v-model="formData.lastName" placeholder="Last Name" required>
                        <input type=
"number" v-model="formData.birthYear" placeholder="Year of birth" required>
                        <button type=
"submit">{{ editMode ? 'Update' : 'Add' }}</button>
                        <button @click=
"closeModal">Cancel</button>
                    </form>
                </div>
            </div>
        </div>        

        <script>
            const {createApp, ref, computed} = Vue;

            createApp({
                data() {
                    return {
                        persons: [],
                        modalVisible: false,
                        editMode: false,
                        formData: {
                            firstName: '',
                            lastName: '',
                            age: ''
                        },
                        editedPersonId: null,
                        pageSize: 5,
                        currentPage: 1,
                        totalPages: 1
                    };
                },
                methods: {
                    getAllPersons(page) {
                        fetch(`/api/persons?page=${page - 1}&size=${this.pageSize}`)
                                .then(response => response.json())
                                .then(data => {
                                    this.persons = data.content;
                                    this.totalPages = data.totalPages;
                                })
                                .catch(error => {
                                    console.error('Error fetching persons:', error);
                                });
                    },
                    showPersonModal(person) {
                        this.editMode = !!person;
                        this.modalVisible = true;
                        if (person) {
                            this.editedPersonId = person.id;
                            this.formData = {...person};
                        } else {
                            this.resetForm();
                        }
                    },
                    savePerson() {
                        if (this.editMode) {
                            fetch(`/api/persons/${this.editedPersonId}`, {
                                method: 'PUT',
                                headers: {
                                    'Content-Type': 'application/json'
                                },
                                body: JSON.stringify(this.formData)
                            })
                                    .then(() => {
                                        this.getAllPersons(this.currentPage);
                                        this.closeModal();
                                    })
                                    .catch(error => {
                                        console.error('Error updating person:', error);
                                    });
                        } else {
                            fetch('/api/persons', {
                                method: 'POST',
                                headers: {
                                    'Content-Type': 'application/json'
                                },
                                body: JSON.stringify(this.formData)
                            })
                                    .then(() => {
                                        this.getAllPersons(this.currentPage);
                                        this.closeModal();
                                    })
                                    .catch(error => {
                                        console.error('Error adding person:', error);
                                    });
                        }
                    },
                    deletePerson(personId) {
                        fetch(`/api/persons/${personId}`, {
                            method: 'DELETE'
                        })
                                .then(() => {
                                    this.getAllPersons(this.currentPage);
                                })
                                .catch(error => {
                                    console.error('Error deleting person:', error);
                                });
                    },
                    closeModal() {
                        this.modalVisible = false;
                        this.editMode = false;
                        this.resetForm();
                    },
                    resetForm() {
                        this.formData = {
                            firstName: '',
                            lastName: '',
                            age: ''
                        };
                        this.editedPersonId = null;
                    },
                    changePage(page) {
                        this.currentPage = page;
                        this.getAllPersons(page);
                    }
                },
                mounted() {
                    this.getAllPersons(this.currentPage);
                }
            }).mount('app');
        </script>
    </body>
</html>

4、编写 Dockerfile
用你喜欢的文本编辑器打开 Dockerfile 文件,添加以下代码。

FROM public.ecr.aws/docker/library/amazoncorretto:21-alpine AS build

RUN apk --no-cache add bash
RUN apk --no-cache add curl
RUN mkdir /app
WORKDIR /app
COPY . /app

RUN curl -Ls https://sh.jbang.dev | bash -s - export portable springbootJpaVue.java

FROM public.ecr.aws/docker/library/amazoncorretto:21-alpine
RUN mkdir /app/
RUN mkdir /app/lib
COPY --from=build /app/springbootJpaVue.jar /app/springbootJpaVue.jar
COPY --from=build /app/lib
/* /app/lib/
WORKDIR /app

ENTRYPOINT ["java","-jar","springbootJpaVue.jar"]

编写 Docker Compose 文件
用你喜欢的文本编辑器打开 compose.yaml 文件,然后添加以下代码。

services:
  backend:
    build: .
    ports:
      - 8080:8088
    environment:
      - SERVER_PORT=8088
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/example
      - SPRING_DATASOURCE_USERNAME=postgres
      - SPRING_DATASOURCE_PASSWORD=pass-example
      - SPRING_JPA_HIBERNATE_DDL_AUTO=update
    networks:
      - spring-postgres
  db:
    image: postgres
    restart: always
    volumes:
      - db-data:/var/lib/postgresql/data
    networks:
      - spring-postgres
    environment:
      - POSTGRES_DB=example
      - POSTGRES_PASSWORD=pass-example
    expose:
      - 5432
  pgadmin:
    container_name: pgadmin
    image: dpage/pgadmin4
    environment:
      PGADMIN_DEFAULT_EMAIL: admin_not_used@user.com
      PGADMIN_DEFAULT_PASSWORD: admin_not_used
      PGADMIN_CONFIG_SERVER_MODE: 'False'
    volumes:
       - pgadmin:/var/lib/pgadmin
    ports:
      - "5050:80"
    networks:
      - spring-postgres
    restart: always      
volumes:
  db-data:
  pgadmin:
networks:
  spring-postgres:

运行应用程序
我们创建了 Spring Boot Monolith 应用程序。它由两个源文件和两个用于 docker 的配置文件组成。

  • springbootJpaVue.java 是后端,作为 Spring Boot Java 应用程序实现,其中还包含一些默认值
  • index-fetch.html 是前台,使用 Vue.js 作为独立脚本实现standalone script

这两个文件的关联方式是使用 JBang 指令
//FILES META-INF/resources/index.html=index-fetch.html

应用程序有一个可以存储在数据库中的 jpa 实体 Person。

返回终端:导航到包含 springbootJpaVue.java 的目录

应用程序可配置为使用以下两种数据库之一运行:

  • H2 内存中的数据库

$ jbang -Dspring.datasource.url=jdbc:h2:mem:person-db \
 springbootJpaVue.java

  • H2 数据库文件系统 - 数据库数据存储在文件中

$ jbang -Dspring.datasource.url=jdbc:h2:file:./person-db-data \
-Dspring.jpa.hibernate.ddl-auto=update \ 

springbootJpaVue.java

  • Postgres,它需要 Postgres 的本地主机实例

$ jbang -Dspring.datasource.url=jdbc:postgresql://localhost:5432/example \
-Dspring.datasource.username=postgres \
-Dspring.datasource.password=postgres \
-Dspring.jpa.hibernate.ddl-auto=update springbootJpaVue.java

运行默认设置文件,并执行以下任意命令:
$ jbang springbootJpaVue.java
$ sh springbootJpaVue.java

如您通过执行允许可执行权限:
$ chmod +x springbootJpaVue.java
你甚至可以像这样执行应用程序
$ ./springbootJpaVue.java


你可以建立一个fatJar
$ jbang export fatjar springbootJpaVue.java
然后运行它
$ jbang springbootJpaVue-fatjar.jar
或者像普通的java应用程序一样
$ java -jar springbootJpaVue-fatjar.jar

您可以创建一个包含所有依赖项的./lib文件夹的可移动的 jar 文件
$ jbang export portable springbootJpaVue.java
然后运行它
$ jbang springbootJpaVue.jar
或者像普通的java应用程序一样
$ java -jar springbootJpaVue.jar

docker compose:
$ docker compose up

在上述所有情况下,JBang 将下载所需的 Spring Boot 依赖项并启动应用程序。您将看到指示 Spring Boot 应用程序正在运行的输出。


CRUD
要创建新人员,请使用 POST 方法并将人员数据作为 JSON 正文:

$ curl -X POST -H "Content-Type: application/json" \
-d '{"firstName":"Katherine", "lastName":"Johnson", "birthYear":1919}' \
http://localhost:8080/api/persons


要获取所有人的列表,请使用 GET 方法:
$ curl -X GET http://localhost:8080/api/persons

要通过 id 获取特定人员,请使用 GET 方法并将 id 作为路径变量:
$ curl -X GET http://localhost:8080/api/persons/1

要按 ID 更新现有人员,请使用 PUT 方法并将人员数据作为 JSON 正文:

$ curl -X PUT -H "Content-Type: application/json" \
-d '{"firstName":"Katherine", "lastName":"Johnson", "birthYear":1918}' \
http://localhost:8080/api/persons/1

在带有 HTTPIE 的终端/CLI 中
您可以从此处下载替代终端/CLI 客户端https://httpie.io/cli

要创建新人员,请使用 POST 方法并将人员数据作为 JSON 正文:

$ http POST http://localhost:8080/api/persons firstName=Alice lastName=Smith birthYear=1996


要获取所有人的列表,请使用 GET 方法:

$ http GET http://localhost:8080/api/persons

要通过 id 获取特定人员,请使用 GET 方法并将 id 作为路径变量:

$ http GET http://localhost:8080/api/persons/1

要按 ID 更新现有人员,请使用 PUT 方法并将人员数据作为 JSON 正文:

$ http PUT http://localhost:8080/api/persons/1 firstName=Bob lastName=Jones birthYear=1990

要通过 id 删除现有人员,请使用 DELETE 方法并将 id 作为路径变量:

$ http DELETE http://localhost:8080/api/persons/1

实施细节
Spring Data Jpa 依赖项
要启用 JPA(即 Java/Jakarta 持久性 API),我们需要

//DEPS org.springframework.boot:spring-boot-starter-data-jpa:3.1.4

我们还需要一个数据库,因此我们将添加 H2 数据库的依赖关系,该部分变为

//DEPS org.springframework.boot:spring-boot-starter-web:3.1.4
//DEPS org.springframework.boot:spring-boot-starter-data-jpa:3.1.4
//DEPS com.h2database:h2:2.2.224

为了最大限度地减少样板代码,我们还将添加 Lombok

//DEPS org.springframework.boot:spring-boot-starter-web:3.1.4
//DEPS org.springframework.boot:spring-boot-starter-data-jpa:3.1.4
//DEPS com.h2database:h2:2.2.224
//DEPS org.projectlombok:lombok:1.18.30

JBang支持文件导入.pom,我们将依赖项更改为

//DEPS org.springframework.boot:spring-boot-dependencies:3.1.4@pom
//DEPS org.springframework.boot:spring-boot-starter-web
//DEPS org.springframework.boot:spring-boot-starter-data-jpa
//DEPS com.h2database:h2
//DEPS org.projectlombok:lombok

正如您所看到的,依赖版本已被删除,Spring Boot 版本仅定义一次。

持久性:Person实体和存储库
这是 JPA 实体和数据存储库

@Data
@Entity
@Table(name = "person")
@NoArgsConstructor
@AllArgsConstructor
class Person {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String firstName;
    private String lastName;
    private int birthYear;
}

interface PersonRepository extends JpaRepository<Person, Long> {
}

PersonController


@RestController
@RequestMapping("/api/persons")
@RequiredArgsConstructor
class PersonController {

    private final PersonRepository personRepository;

    @GetMapping
    public Page<Person> findAll(Pageable pageable) {
        return personRepository.findAll(pageable);
    }

    @GetMapping("{id}")
    public Optional<Person> findById(@PathVariable("id") Long id) {
        return personRepository.findById(id);
    }

    @PostMapping
    public Person create(@RequestBody Person person) {
        return personRepository.save(person);
    }

    @PutMapping("{id}")
    public Person updateById(@PathVariable("id") Long id, @RequestBody Person person) {
        var loaded = personRepository.findById(id).orElseThrow();
        loaded.setFirstName(person.getFirstName());
        loaded.setLastName(person.getLastName());
        loaded.setBirthYear(person.getBirthYear());
        return personRepository.save(loaded);
    }

    @DeleteMapping("/{id}")
    public void deleteById(@PathVariable("id") Long id) {
        personRepository.deleteById(id);
    }
}

OpenAPI 支持并启用 Swagger UI
我们正在使用该springdoc项目。要启用它,我们所要做的就是添加以下依赖项

//DEPS org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0

重新启动应用程序后,您将 在以下 URL获得Swagger UIhttp://localhost:8080/swagger-ui/index.html

启用 H2 控制台应用程序
H2 控制台应用程序允许您使用浏览器界面访问 SQL 数据库。要激活它,我们需要在依赖项部分之后添加以下配置

//JAVA_OPTIONS -Dspring.h2.console.enabled=true
//JAVA_OPTIONS -Dspring.h2.console.settings.web-allow-others=true
//JAVA_OPTIONS -Dspring.datasource.url=jdbc:h2:mem:person-db;MODE=PostgreSQL;

访问应用程序

  • http://localhost:8080/:使用 Vue.JS 构建的基于 Web 的用户界面
  • http://localhost:8080/h2-console:H2 SQL 控制台应用程序
  • http://localhost:8080/v3/api-docs:开放API定义
  • http://localhost:8080/swagger-ui/index.html:swagger
  • http://localhost:8080/actuator:Spring Boot 执行器端点
  • http://localhost:5050/:当使用 docker compose 执行时,该应用程序提供对 Web 版本的 PgAdmin 的访问,使您可以使用浏览器界面访问 SQL 数据库。
  • http://localhost:8080/hi:应该看到“Hello, World!”浏览器中显示的消息。