SpeedMent入门&集成SpringBoot&Stream常见操作

speedment:速度是Stream ORM Java工具包和运行时 Java Stream ORM Speedment是一个开源Java Stream ORM工具包和运行时。 该工具包将分析现有SQL数据库的元数据,并自动创建数据模型的Java表示形式。 这个强大的ORM使您能够使用标准Java流创建可扩展且高效的Java应用程序,而无需键入SQL或使用任何新的API。 Speedment最初是由位于帕洛阿尔托的研究人员和工程师开发的,其目的是通过利用Java Stream API简化和简化Java数据库应用程序的开发。 Speedment已获得对商业友好的Apache 2许可。 鼓励用户做出贡献。 请随时请求新功能,提出改进建议并提交错误报告。 阅读有 立即下载

当您熟悉 Streams 的操作时,您可能已经注意到与 SQL 构造的相似之处。 它们中的一些或多或少直接映射到 Stream 操作,例如 LIMIT 和 COUNT。开源项目 Speedment 利用这种相似性,使用纯 Java 对任何关系数据库提供类型安全的访问。

官网:https://speedment.com/

工具

JDK 8.0.40或更高版本
ApacheMaven 3.3.9或更高版本

开始前准备

  1. 解压下载的demo.zip
    解压后会有个空的main文件夹,后续生成的代码将会放入其中
    到pom所在的文件下执行 mvn speedment:tool

这里的pom最好替换成如下的(主要是数据源换成mysql):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.speedment</groupId>
    <artifactId>documentation-examples</artifactId>
    <version>3.0.21</version>
    <packaging>jar</packaging>
    
    <name>Speedment - Documentation - Examples</name>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <speedment.version>3.2.10</speedment.version>
    </properties>    
    
    <build>
        <plugins>
            <plugin>
                <groupId>com.speedment</groupId>
                <artifactId>speedment-maven-plugin</artifactId>
                <version>${speedment.version}</version>
                
                <dependencies>
                     <dependency>
                         <groupId>mysql</groupId>
                         <artifactId>mysql-connector-java</artifactId>
                         <version>5.1.39</version>
                         <scope>runtime</scope>
                     </dependency>
                 </dependencies>
                
            </plugin> 
        </plugins>
    </build>
    
    <dependencies>
        <dependency>
            <groupId>com.speedment</groupId>
            <artifactId>runtime</artifactId>
            <version>${speedment.version}</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
    
	 
  <repositories>
    <repository>
      <id>speedment-enterprise</id>
      <name>Speedment Enterprise Repositories</name>
      <url>http://repo.speedment.com/nexus/content/repositories/releases/</url>
    </repository>
  </repositories>
  
  <pluginRepositories>
    <pluginRepository>
      <id>speedment-enterprise</id>
      <name>Speedment Enterprise Repositories</name>
      <url>http://repo.speedment.com/nexus/content/repositories/releases/</url>
    </pluginRepository>
  </pluginRepositories>
</project>

本人在这一步非常脸黑的遇到了三个错误 没有遇到的请跳过:

  • 第一个是找不到 localRepository,这个的话去settings.xml中找到localRepository配置下就ok了
  • 第二个是镜像的问题,本地用的是私服,然后setting中配置成阿里镜像,然后发现还是不行,发现是倚赖版本太高,在pom中还是添加了speedMent的库

在这里插入图片描述

  • 第三个是根据上面的窗口里的提示 我使用了 mvn speedment:tool -X switch
    为了查看具体的出错信息,结果又出现下面的错误:

在这里插入图片描述去掉后缀 执行原语句然后成功,真是出师不利啊。

除了使用命令外也可将zip解压后用idea打开,选择右侧的speedment:tool运行即可
在这里插入图片描述

  1. 执行完后会弹出如下窗口
    在这里插入图片描述

官方说免费版的能够支持的数据库大小最大为 500M 所以点击start free
填写相关信息进行注册

然后就会弹出连接数据库的界面,下面的教程中大多是翻译的,上述步骤完成后执行下面的步骤一,跳到步骤二的连接数据库部分。

教程1-HelloSpeedment

在本教程中,我们将编写一个小程序,它能够询问用户的姓名和年龄,并将其保存在MySQL数据库中。首先,我们将初始化数据库。

步骤1:建立数据库

CREATE DATABASE hellospeedment;
USE hellospeedment;

我们还将添加一个用于存储信息的表。

CREATE TABLE IF NOT EXISTS `user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(32) NOT NULL,
    `age` int(5) NOT NULL,
    PRIMARY KEY (`id`),
    UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;

步骤2:创建一个新的Speedment Maven项目

接下来,我们需要建立一个依赖Speedment的Maven项目,并连接到新创建的数据库。

创建自定义项目启动程序
去我们的起始器生成自定义的pom.xml通过填写项目详细信息来执行项目。

连接到数据库
Speedment使用JSON配置文件从数据库生成Java代码。JSON文件将使用Speedment工具创建。然后执行 mvn speedment:tool 或者在IDE中单击它。

启动后,使用该工具连接到数据库。
在这里插入图片描述

Speedment会分析底层数据源的元数据,并自动创建直接反映底层数据源结构(即“域模型”)的代码。只需简单地按下生成-按钮。
在这里插入图片描述

搞定了!Speedment为数据库中的每个表生成了模型!如果没出错,你就应该得到这样的结构:
在这里插入图片描述

步骤3:编写应用程序

现在我们已经准备好编写应用程序了。打开作为项目启动程序一部分的main.java文件,并编辑它,如下所示: (这个Main.java自己创建)

package com.company.speedment.test.hellospeedment;

/**
 *
 * @author Your name
 */
public class Main {
    public static void main(String... params) {
        HellospeedmentApplication app = new HellospeedmentApplicationBuilder()
            .withBundle(MySqlBundle.class)
            .build();
        UserManager users = app.getOrThrow(UserManager.class);
    }
}

为了告诉Speedment它将与什么数据库交互,在创建应用程序实例时必须添加连接器包。这是通过调用withBundle(CONNECTOR_BUNDLE)如前面的示例所示。因为我们使用MySQL,所以我们将添加MySqlBundle.

这个HellospeedmentApplication实例由Speedment自动生成,并将连接到数据库并为您处理所有初始化工作!如果MySQL数据库中有密码,则需要手动设置该密码,如下所示:

HellospeedmentApplication app = new HellospeedmentApplicationBuilder()
    .withBundle(MySqlBundle.class)
    .withPassword("YourSuperSecretPassword")
    .build();

Speedment非常重视您的安全性,不会将您的密码存储在任何配置文件中。

现在我们将监听用户的输入。为此,我们使用Scanner在标准输入通道上。

final Scanner scn = new Scanner(System.in);

使用Scanner,我们可以向用户询问姓名和年龄。在本教程中,我们将不检查输入的有效性,因此我们只是假设用户输入的所有内容都是正确的。

System.out.print("What is your name? ");
final String name = scn.nextLine();

System.out.print("What is your age? ");
final int age = scn.nextInt();

现在,我们希望将这些信息持久化到数据库中。这是通过创建新实体、设置其值和调用persist()在上面。

users.persist(
    users.create()
        .setName(name)
        .setAge(age)
);

我们也可以对查询做出反应,如果有任何问题,我们可以打印一条消息。例如,如果名称已经被持久化,就可能发生这种情况。

try {
    User user = users.create()
        .setName(name)
        .setAge(age);
    user = users.persist(user);
    System.out.print("Hello, " + user.getName() + "!");
} catch (SpeedmentException se) {
   System.out.print("Why are you so persistent?");
   se.printStackTrace();
}

如果您想确切地知道您的persist()命令,您可以按以下方式设置日志记录给应用程序。

HellospeedmentApplication app = new HellospeedmentApplicationBuilder()
    .withBundle(MySqlBundle.class)
    .withLogging(PERSIST)
    .build();
UserManager users = app.getOrThrow(UserManager.class);

users.persist(
    users.create()
        .setName(name)
        .setAge(age)
);

这可能提供以下输出:

What is your name? 洺润
What is your age? 18
2020-09-08T02:11:41.491Z DEBUG [main] (#PERSIST) - INSERT INTO `hellospeedment`.`user` (`id`,`name`,`age`) VALUES (?,?,?) [Long 0, String 洺润, Integer 18]
Hello, 洺润!

如果再次输入相同的名字,可能会得到以下输出:

What is your name? 洺润
What is your age? 16
2020-09-08T02:12:44.720Z DEBUG [main] (#PERSIST) - INSERT INTO `hellospeedment`.`user` (`id`,`name`,`age`) VALUES (?,?,?) [Long 0, String 洺润, Integer 16]
Why are you so persistent? 

这里执行完后数据库里可以查看是否已经落表

完整代码如下:

/**
 * @Auther: 洺润
 * @Date: 2020/9/8 
 * @Description:
 */
public class Main {
    final static Scanner scn = new Scanner(System.in);

    public static void main(String... params) {
        HellospeedmentApplication app = new HellospeedmentApplicationBuilder()
                .withBundle(MySqlBundle.class)
                .withPassword("root")
                .withLogging(PERSIST)
                .build();
        UserManager users = app.getOrThrow(UserManager.class);

        System.out.print("What is your name? ");
        final String name = scn.nextLine();

        System.out.print("What is your age? ");
        final int age = scn.nextInt();

        try {
            User user = users.create()
                    .setName(name)
                    .setAge(age);
            user = users.persist(user);
            System.out.print("Hello, " + user.getName() + "!");
        } catch (SpeedmentException se) {
            System.out.print("Why are you so persistent?");
            se.printStackTrace();
        }
    }
}

教程2-First Stream

本教程介绍了在Speedment应用程序中的一些基本流操作。示例使用的数据来自,Sakila数据库

下载下来导入就完事了, 这里俺用mysql的,导入sakila数据库,前几步都是同样的玩法

步骤1:建立数据库

可以下载Sakila数据库这里。安装说明如下这里.

步骤2:创建一个新的SpeedmentMaven项目

如果这是您第一次使用Speedment,我们将在快速启动指南中解释如何设置一个新的SpeedmentMaven项目启动一个新的SpeedmentMaven项目和连接到数据库.
(注意要新建一个文件夹 要不生成代码时就会把之前的给覆盖掉)

步骤3:编写应用程序

前面的教程详细描述了如何设置Speedment应用程序。基本上,一个简单的示例可以归结为以下步骤。建造app,然后Manager应用程序逻辑所需的app最后 ,app在完成应用程序逻辑时关闭。

    public static void main(String... params) {
        SakilaApplication app = new SakilaApplicationBuilder()
            .withBundle(MySqlBundle.class)
            .withPassword("MyPassword")
            .build();

        FilmManager films = app.getOrThrow(FilmManager.class);
        ActorManager actors = app.getOrThrow(ActorManager.class);
        FilmActorManager filmActors = app.getOrThrow(FilmActorManager.class);

        // your application logic here

        app.close();
    }

步骤4计算流中的项目数

作为第一个程序,我们来计算PG-13级影片的数量,这是通过使用filtering the stream ,然后counting来完成的。(肯定就是那俩api了)

long count = films.stream()
    .filter(Film.RATING.equal("PG-13"))
    .count();

System.out.format("There are %d PG-13 films in the DB", count);

通过在创建应用程序时向应用程序添加日志设置,我们可以跟踪Speedment运行时的内部操作。以下应用程序将在数据库中生成PG-13电影的计数,并记录所执行的SQL操作。

public static void main(String... params) {
    SakilaApplication app = new SakilaApplicationBuilder()
        .withBundle(MySqlBundle.class)
        .withPassword("sakila")
        .withLogging(ApplicationBuilder.LogType.STREAM)
        .build();

    FilmManager films = app.getOrThrow(FilmManager.class);

    long count = films.stream()
        .filter(Film.RATING.equal("PG-13"))
        .count();

    System.out.format("There are %d PG-13 films in the DB", count);
	
   //这里的close()方法是个迷 我用的stop方法 好像也没得选
    app.close();
}

输出结果

There are 223 PG-13 films in the DB

流实际上是描述结果的声明性语句(具有PG-13评级的电影),而不是关于要执行的操作和顺序的命令语句。因此,Speedment运行时可以自由地以它认为合适的任何方式计算所需的结果,而运行这段代码将导致数据库执行以下SQL查询。

官方的

SELECT COUNT(*) FROM sakia.film WHERE ('rating'  = 'PG-13' COLLATE utf8_bin)

我控制台输出的

SELECT COUNT(*) FROM (SELECT `film_id`,`title`,`description`,`release_year`,`language_id`,`original_language_id`,`rental_duration`,`rental_rate`,`length`,`replacement_cost`,`rating`,`special_features`,`last_update` FROM `sakila`.`film` WHERE (`rating` = ? COLLATE utf8_bin)) AS A, values:[PG-13]

步骤5:通过主键查找基本胶片

虽然数据库中的项目计数可以通过Speedment完全委托给数据库引擎,但是一个典型的用例也需要在-JVM中进行数据操作。作为第一个例子,请考虑以下使用电影ID查找影片的代码。可以说,作为最基本的查找类型,完成此任务的代码非常简单;在ID上过滤电影流,然后将该流的电影映射到我们正在寻找的描述类型。

Scanner scn = new Scanner(System.in);
System.out.println("Please enter Film ID ");
final int id = Integer.decode(scn.nextLine().trim());

final Optional<String> title = films.stream()
    .filter(Film.FILM_ID.equal(id))      // find the films we are looking for
    .map(Film.TITLE)                     // switch from a stream of films to one of titles
    .findAny();                          // we want the first and only match for this unique key

if (title.isPresent()) {
    System.out.format("Film ID %d has title %s.", id, title.get());
} else {
    System.out.format("Film ID not found.", id);
}

app.close();

为了清晰起见,没有进行适当的输入验证。
在这里插入图片描述

步骤6:理解流优化

Speedment运行时框架处理Speedment流的执行,并且由于应用程序中的流是一个声明式构造,只要最终结果保持不变,Speedment运行时就可以以任何方式优化流。

作为这种优化的示例,请考虑以下代码片段:

long count = films.stream()
    .filter(Film.RATING.equal("PG-13"))
    .filter(Film.LENGTH.greaterOrEqual(75))
    .map(Film.TITLE)
    .sorted()
    .count();

System.out.printf("Found %d films", count);

此流的结果是由两个不同谓词筛选的项的计数(就是那两个filter),这些项被映射到String并最终排序。读者可能会注意到,从电影到电影标题的映射以及电影标题的排序都不会影响流中项目的数量。Speedment运行时将得出相同的结论,并优化排序和映射操作,再次将所有工作留给数据库引擎,并按照以下路线进行数据库查询

SELECT COUNT(*) FROM 'sakila'.'film' WHERE ('rating' = 'PG-13' COLLATE utf8_bin) AND ('length' >= 75)

显然,当流构建为上面所述的单个语句序列时,只有当应用程序的设计人员创建了次优代码时,优化才会有所帮助。然而,非终止流是一个逻辑结构,可以作为参数传递,因此很有可能允许流结构的构造是分析输入数据的结果,只有在某些条件保持不变时才会添加某些部分。知道框架将在执行前优化流,它允许应用程序设计器关注功能正确性,而不是所有创建最佳流的执行路径。
上面这些我没太搞懂 ~ 大佬可以自行去官网看看 回头与我讨论

步骤7:电影分类

作为一个更详细的例子,下面的代码片段收集电影流并按分级对它们进行分组。这段代码演示了Speedment如何让任何数据库应用程序立即访问Java流的全部功能和功能。

films.stream()
    .collect(Collectors.groupingBy(Film.RATING,
        Collectors.counting())).forEach(
            (rating, count) -> System.out.printf("There are %d %s rated films.%n", count, rating));

输出如下。

There are 223 PG-13 rated films.
There are 195 R rated films.
There are 210 NC-17 rated films.
There are 178 G rated films.
There are 194 PG-rated films.

步骤8:通过演员名称查找电影

对于SpeedmentStreams,一个稍微高级一些的用例是寻找具有给定姓氏的演员的电影。为了实现这一点,应用程序逻辑将被分成两部分。首先,我们计算selectedActorIds名字匹配的演员。然后,我们过滤有关角色ID中的电影和演员之间的关系,最后将流中的项映射为所需的输出格式。

		FilmManager films = app.getOrThrow(FilmManager.class);
        ActorManager actors = app.getOrThrow(ActorManager.class);
        FilmActorManager filmActors = app.getOrThrow(FilmActorManager.class);

        Scanner scn = new Scanner(System.in);
        System.out.println("Please enter actor last name ");
        final String actorName = scn.nextLine().trim();
        Set<Integer> selectedActorIds = actors.stream()
                .filter(Actor.LAST_NAME.equalIgnoreCase(actorName))
                .mapToInt(Actor.ACTOR_ID)                             // turning the stream into a stream of actor IDs
                .boxed()                                              // turning IntStream into Stream<Integer>
                .collect(toSet());

        if (selectedActorIds.isEmpty()) {
            System.out.println("No actor with last name " + actorName + " found.");
        } else {
            System.out.println("Films with actor with last name " + actorName + ":");
            filmActors.stream()
                    .filter(FilmActor.ACTOR_ID.in(selectedActorIds))
                    .map(films.finderBy(FilmActor.FILM_ID))  // the stream of films we are looking for
                    .map(Film.TITLE.getter())                // the stream of film titles
                    .sorted()
                    .forEach(title -> System.out.println(" " + title));
        }

示例输出
下面是应用程序输出的一个示例,其中我们查找了FileID 14,然后查找了一个演员名为Akroyd的电影。

Please enter Film ID 
14

Film ID 14 has title ALICE FANTASIA
Please enter actor last name 
Akroyd

Films with an actor with last name Akroyd:
 APOLLO TEEN
 BACKLASH UNDEFEATED
 BETRAYED REAR
 ...

教程3-SpeedMent集成SpringBoot

在前面的教程中介绍了基本的Speedment代码生成和应用程序逻辑之后,本教程在这些知识的基础上展示了如何使用SpringBoot为Speedment应用程序创建RESTAPI。

步骤1:建立数据库

可以下载Sakila数据库这里。安装说明如下这里(依然用上面下载的那个)

步骤2:创建一个新的Maven项目

与前面的教程一样,创建一个新的Java 8 Maven项目和pom.xml-存档。对于本教程,我们还添加了SpringBoot框架依赖项。在下面,我们假设一个MySQL数据库,因为上面链接的Sakila数据库是MySQL。

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.2.0.RELEASE</version>
	</parent>

	<build>
		<plugins>
			  <plugin>
					<groupId>com.speedment</groupId>
					<artifactId>speedment-maven-plugin</artifactId>
					<version>3.2.5</version>
					
					<dependencies>
						 <dependency>
							 <groupId>mysql</groupId>
							 <artifactId>mysql-connector-java</artifactId>
							 <version>5.1.39</version>
							 <scope>runtime</scope>
						 </dependency>
					 </dependencies>
					
				</plugin> 

			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<dependencies>
		<dependency>
			<groupId>com.speedment</groupId>
			<artifactId>runtime</artifactId>
			<version>3.2.5</version>
			<type>pom</type>
		</dependency>
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.39</version>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>com.fasterxml.jackson.datatype</groupId>
			<artifactId>jackson-datatype-jdk8</artifactId>
		</dependency>
	</dependencies>

步骤3:Spring集成

基于Spring引导的应用程序的起点是SpringApplication.run方法。下面的主类是引导应用程序所需的全部内容。

@SpringBootApplication
public class Main {
    public static void main(String[] args) throws UnsupportedEncodingException, FileNotFoundException {
        SpringApplication.run(Main.class, args);
    }
}

要向应用程序添加任何有意义的功能,我们首先创建RestController。使用Spring注释,我们声明了一个类,它将管理电影其余端点的服务器端。控制器定义四个不同的端点;
所有电影的列表(/),
按ID查找胶片(/{id}),
通过任何具有给定名称的演员进行电影查找(/byactor/{actorName}),
按演员名称分组的电影查找(/byactors/{actorName})并对演员名称部分进行筛选。

@RestController
@RequestMapping("/film")
public class FilmsController {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    private final FilmActorManager filmActors;
    private final ActorManager actors;
    private final FilmManager films;

    public FilmsController(SakilaApplication app) {
        films = app.getOrThrow(FilmManager.class);
        filmActors = app.getOrThrow(FilmActorManager.class);
        actors = app.getOrThrow(ActorManager.class);
    }

    @GetMapping("")
    List<Film> getFilms() {
        return films.stream().collect(toList());
    }

    @GetMapping("{id}")
    Film getFilmById(@PathVariable int id) {
        return films.stream().filter(Film.FILM_ID.equal(id)).findAny().orElse(null);
    }

    @GetMapping("byactor/{actorName}")
    List<Film> getFilmByActor(@PathVariable String actorName) {
        Integer actorId = actors.stream()
                .filter(Actor.LAST_NAME.equalIgnoreCase(actorName))
                .mapToInt(Actor.ACTOR_ID)
                .boxed()
                .findAny()
                .orElse(null);

        return actorId == null ? emptyList() : filmActors.stream()
                .filter(FilmActor.ACTOR_ID.equal(actorId))
                .map(films.finderBy(FilmActor.FILM_ID))
                .collect(toList());
    }

    @GetMapping("byactors/{actorName}")
    Map<String, List<String>> getFilmByActors(@PathVariable String actorName) {
        Set<Integer> actorIds = actors.stream()
                .filter(Actor.LAST_NAME.containsIgnoreCase(actorName)
                    .or(Actor.FIRST_NAME.containsIgnoreCase(actorName)))
                .mapToInt(Actor.ACTOR_ID)
                .distinct()
                .boxed()
                .collect(toSet());

        return actorIds.isEmpty() ? Collections.EMPTY_MAP : filmActors.stream()
                .filter(FilmActor.ACTOR_ID.in(actorIds))
                .collect(
                        groupingBy(actors.finderBy(FilmActor.ACTOR_ID)
                                .andThen(a -> a.getFirstName() + " " + a.getLastName()),
                                mapping(
                                        films.finderBy(FilmActor.FILM_ID)
                                            .andThen(GeneratedFilm::getTitle),
                                        toList()
                                )
                        )
                );
    }
}

如控制器的构造函数所示,它依赖于实例化的SpeedmentSakilaApplication。在没有任何关于如何这样做的进一步说明的情况下,Spring框架将尝试在创建控制器之前实例化这样的对象。正如前面的教程所示,Speedment应用程序是使用构建器模式创建的,为了告诉Spring如何创建配置类。在Configuration类中,我们还提供了一个JSON格式构建器,当JSON编码上面的控制器类返回的数据结构时,Spring将使用它。应用程序在没有它的情况下可以工作,但是添加一个格式化程序允许我们自定义格式,对于本教程,我们需要缩进的JSON。

@Configuration
public class Setup {
    @Bean
    public SakilaApplication createApplication() {
        return new SakilaApplicationBuilder()
                .withBundle(MySqlBundle.class)
                .withPassword("MyPassword")
                .build();
    }

    @Bean
    public Jackson2ObjectMapperBuilder jacksonBuilder() {
        return new Jackson2ObjectMapperBuilder().indentOutput(true);
    }
}

有一个main-方法作为一个执行起点,一个控制器用于RESTAPI背后的逻辑,还有一个用于设置事物的配置类,所有这些都提供给Spring,以确定运行时依赖关系,并启动一个向客户端公开我们的API的HTTP服务器。

示例输出
查询单个胶片的ID可以如下所示。

curl http://localhost:8080/film/14
{
  "filmId": 14,
  "title": "ALICE FANTASIA",
  "description": "A Emotional Drama of a A Shark And a Database Administrator who must Vanquish a Pioneer in Soviet Georgia",
  "releaseYear": "2006-01-01",
  "languageId": 1,
  "originalLanguageId": null,
  "rentalDuration": 6,
  "rentalRate": 0.99,
  "length": 94,
  "replacementCost": 23.99,
  "rating": "NC-17",
  "specialFeatures": "Trailers,Deleted Scenes,Behind the Scenes",
  "lastUpdate": 1139979822000
}

如果要求所有电影的主角都是一个姓鲍尔的演员,就会有一长串我们在这里简称的电影。grep 电影片名。

curl -s http://localhost:8080/film/byactor/ball | grep title
  "title" : "ALONE TRIP",
  "title" : "ANGELS LIFE",
  "title" : "ANTITRUST TOMATOES",
  "title" : "BALLOON HOMEWARD",
  "title" : "BINGO TALENTED",
  "title" : "BIRDCAGE CASPER",
  "title" : "BRIGHT ENCOUNTERS",
  "title" : "CABIN FLASH",
  "title" : "CAT CONEHEADS",
  "title" : "COMANCHEROS ENEMY",
  "title" : "DESERT POSEIDON",
  "title" : "DESPERATE TRAINSPOTTING",
  "title" : "EXTRAORDINARY CONQUERER",
  "title" : "GHOST GROUNDHOG",
  "title" : "GREEDY ROOTS",
  "title" : "HILLS NEIGHBORS",
  "title" : "HOTEL HAPPINESS",
  "title" : "HUNTER ALTER",
  "title" : "JADE BUNCH",
  "title" : "KING EVOLUTION",
  "title" : "LOVERBOY ATTACKS",
  "title" : "MAGNIFICENT CHITTY",
  "title" : "MASK PEACH",
  "title" : "NATURAL STOCK",
  "title" : "NONE SPIKING",
  "title" : "PATRIOT ROMAN",
  "title" : "PERDITION FARGO",
  "title" : "SCARFACE BANG",
  "title" : "SENSE GREEK",
  "title" : "TRAMP OTHERS",
  "title" : "TROUBLE DATE",
  "title" : "UNFAITHFUL KILL",
  "title" : "WIND PHANTOM",

几个演员查找电影的输出比较简短,因此,例如,列出名为AykRod的演员的电影会产生以下结果:为了可读性,每个电影列表都被截断。

curl -s http://localhost:8080/film/byactors/Akroyd

{
  "KIRSTEN AKROYD" : [ "BOULEVARD MOB", "BRAVEHEART HUMAN", "BUCKET BROTHERHOOD", ... ],
  "DEBBIE AKROYD" : [ "APOLLO TEEN", "CLUB GRAFFITI", "FAMILY SWEET", ... ],
  "CHRISTIAN AKROYD" : [ "BACKLASH UNDEFEATED", "BETRAYED REAR", "CAPER MOTIONS", ... ]
}

参考

  • Java中函数式编程的谓词函数(Predicates)
    https://blog.youkuaiyun.com/u010832551/article/details/74739933
  • 成为 Java Stream 高手https://gitchat.youkuaiyun.com/activity/5db7cfc1a8131649a41b1f76?utm_source=chatAppChannel&spm=1002.2030.3001.4075#1createstreams
  • speedment 官方文档
    https://speedment.github.io/speedment-doc/tutorials.html#tutorial-1—hello-speedment

写在最后

SpeedMent是本人在学习Stream时发现的,非常好奇,于是查看相关文档并将相关操作过程记录于本文,但要说SpeedMent的具体开发步骤,上生产,还需进一步研究,而且该技术推广都不高,所以目前还是玩玩的心态来学,还可以拿来练习流的使用。

附:Stream 常见操作

import org.junit.Test;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.util.stream.Collectors.counting;
import static java.util.stream.Collectors.toList;
import static sun.swing.MenuItemLayoutHelper.max;

/**
 * @Auther: 洺润
 * @Date: 2020/9/7
 * @Description:
 */
public class StreamTest {
//    public String[] str =

    //开胃菜
    @Test
    public void m1() {

        //筛选L开头的字符串 并进行排序
        List<String> list = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                .filter(s -> s.startsWith("L"))
                .map(String::toUpperCase)
                .sorted()
                .collect(toList());
        list.forEach(System.out::println);
    }

    @Test
    public void m2() {
        //intStream
        IntStream intStream = IntStream.of(1, 2, 3);
        IntStream intStream1 = IntStream.rangeClosed(1, 9); //产生[1,9]
        IntStream iterate = IntStream.iterate(1, i -> i * 2);
        IntStream chars = "ABC".chars();
        IntStream ints = new Random().ints();

        //数组转换为Stream
        String[] strings = {"Monkey", "Lion", "Giraffe", "Lemur"};
        Stream<String> strings1 = Stream.of(strings);
        Stream<String> monkey = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");

        //集合转换为Stream
        List<String> list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
        Stream<String> streamFromList = list.stream();
        Set<String> set = new HashSet<>(list);
        Stream<String> streamFromSet = set.stream();

        //将文件中的每一行作为单独的元素保存
        try {
            List<String> strings2 = Files.lines(Paths.get("C:\\Users\\Administrator\\Desktop\\1.txt"))
                    .collect(toList());
//            strings2.forEach(System.out::println);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //中间操作
    @Test
    public void m3() {
        //过滤操作 filter
        IntStream stringStream = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
                .filter(s -> s.startsWith("L"))
                .limit(2)//limit操作 实际上创建了一个新的stream
                .skip(2)//跳过前两个
                .distinct() //去重
                .sorted() //排序
                .sorted(Comparator.comparing(String::length)) //自定义排序
                .map(String::toUpperCase) //做值的转换 能变成值的类型
                .mapToInt(String::length); //转换为Int除此之外还有double long等

        //压扁操作 FlatMap
        // 从类型 T 到返回 R 的 Stream 的函数。函数将元素将其展平为 Stream,flatMap() 将 Stream 的所有元素串联在一起。
        Stream<Character> chars = Stream.of(
                "Monkey", "Lion", "Giraffe", "Lemur"
        ).flatMap(s -> s.chars().mapToObj(i -> (char) i));

    }


    /**
     * 终端操作
     * 在启动终端操作之前,Stream 将不会对源的元素执行任何计算。
     * 这意味着仅在需要时才使用源元素,这是避免不必要工作的明智方法。
     * 这也意味着一旦应用了终端操作,Stream 将被消耗,并且无法再添加其他操作。
     */
    @Test
    public void m4() {
        //Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
        // ForEach 和 ForEachOrdered 操作
        Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion").forEach(System.out::println);
//        Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion").forEachOrdered(System.out::println);

        /**
         * 收集数据的操作 Collecting Elements
         * Streams 的常见用法是构建元素的“存储桶” ,或更具体地说,构建包含特定元素集合的数据结构。
         * 这可以通过在 Stream 的末尾调用终端操作 collect() 来完成,要求它将元素收集到给定的数据结构中
         */
        Set<String> collect = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .collect(Collectors.toSet());
        List<String> collect1 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .collect(toList());
        LinkedList<String> collect2 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .collect(Collectors.toCollection(LinkedList::new));// 使用通用集合

        //转换成数组
        String[] strings = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
                .toArray(String[]::new);
        //转换成map
        Map<String, Integer> toMap = Stream.of(
                "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
        )
                .distinct()
                .collect(Collectors.toMap(
                        Function.identity(),   //Function<String, K> keyMapper
                        s -> (int) s.chars().distinct().count()// Function<String, V> valueMapper
                ));

        //使用 GroupingBy 来收集结果 groupingByList: {G=[Giraffe], L=[Lion, Lemur, Lion], M=[Monkey]}
        Map<Character, List<String>> groupingByList = Stream.of(
                "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
        )
                .collect(Collectors.groupingBy(
                        s -> s.charAt(0) // Function<String, K> classifier
                ));

        // 使用带有下游收集器的 GroupingBy 来收集结果  groupingByCounting: {G=1, L=3, M=1}
        Map<Character, Long> groupingByCounting = Stream.of(
                "Monkey", "Lion", "Giraffe", "Lemur", "Lion"
        )
                .collect(Collectors.groupingBy(
                        s -> s.charAt(0), // Function<String, K> classifier
                        counting()        // Downstream collectorFco
                ));

        //判断元素是否存在
        boolean b1 = IntStream.of(1, 2, 3).anyMatch(i -> i == 2);

        //计算操作
        long nrOfAnimals = Stream.of(
                "Monkey", "Lion", "Giraffe", "Lemur"
        ).count(); //能够得到动物的数量
        int sum = IntStream.of(1,2,3).sum();//6
        //计算平均值
        OptionalDouble average = IntStream.of(1, 2, 3).average();
        //找到最大值
        int i = IntStream.of(1, 2, 3).max().orElse(0);
        //将结果进行合并 statistics: IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}
        IntSummaryStatistics statistics = IntStream.of(1, 2, 3).summaryStatistics();


    }


}

Speedment一个数据库访问库它利用了Java8StreamAPI进行查询 Speedment:一个数据库访问库它利用了Java 8 Stream API进行查询 立即下载

相关推荐

Shopee App加密算法逆向实战:从抓包到libshpssdk.so的深度解析

本文详细解析了Shopee App加密算法的逆向过程,从网络抓包发现加密参数入手,通过Frida动态Hook定位到Java层关键方法,并深入追踪至Native层的libshpssdk.so核心函数。文章结合实战演示了如何利用Unidbg模拟执行环境进行算法分析与验证,并探讨了核心参数生成逻辑及风控规避要点,为移动安全与逆向工程研究提供了完整的技术路径。

weixin_28347369的博客 527

Speedment 简介

2019独角兽企业重金招聘Python工程师标准&gt;&gt;&gt; ...

weixin_34168700的博客 559

Ollama+FAISS+Embedding+Rerank+LLM 详细部署指南

下载安装Ollama 双击运行安装程序 安装完成后会自动启动服务 修改默认安装路径(避免C盘爆满) 或使用PowerShell: 验证安装 Linux系统部署: (二)准备模型 1. 拉取Embedding模型 2. 拉取LLM模型 注意:原文中提到的可能需要从自定义仓库拉取: 3. 下载Rerank模型 (三)安装依赖库 创建项目目录和虚拟环境: 二、主要功能实现 项目结构 (一)配置文件(config/config.yaml) (二)文本分块策略实现 创建 : (三)文档读取

AI技术改变世界 1576

使用Maven构建多模块项目

在平时的Javaweb项目开发中为了便于后期的维护,我们一般会进行分层开发,最常见的就是分为entity(模型层)、dao(数据库访问层)、service(业务逻辑层)、web(表现层),这样分层之后,各个层之间的职责会比较明确,后期维护起来也相对比较容易,今天我们就是使用Maven来构建以上的各个层。   项目结构如下:   website-parent         |----pom.x...

implements的博客 321

AutoSAR系列讲解(实践篇)12.1-Diagnostics简介

主要概括性的讲解了诊断协议UDS和AutoSAR的诊断架构

雪云飞星的博客 1万+

“华为杯”研究生数学建模竞赛2020年-【华为杯】B题:汽油辛烷值优化建模(附优秀论文)

摘 要:本文通过机器学习算法和数据挖掘技术,建立了汽油辛烷值(RON)损失的预测模型,在保证汽油产品脱硫效果的前提下,以降低汽油辛烷值损失在30%以上为目标,给出每个样本的优化操作条件。针对第一问,对285号和313号原始数据用给定的数据处理规范进行预处理,对每一列变量求期望,得到最终的数据补充到相应的样本编号。针对第二问,首先对数据样本按照最大最小限幅以及拉依达法则进行列变量清洗,通过两轮特征筛选确定变量:结合处理后的数据,针对产品中硫含量、RON损失值用。

getusushu的博客 1251

Advent of Code Kotlin Template高级技巧:如何优雅处理多部分谜题与性能优化

Advent of Code Kotlin Template是一款专为Kotlin开发者设计的高效解题框架,能帮助开发者快速应对Advent of Code中的各类编程谜题。本文将分享处理多部分谜题的优雅方案和实用性能优化技巧,让你的解题过程更流畅、代码更高效。 [![Advent of Code Kotlin Template封面图](https://raw.gitcode.com/gh_mi

gitblog_00024的博客 411

揭秘大厂 Unity 红点系统设计:别小看右上角那颗小红点,它能把你项目搞崩

本文详细探讨了游戏大厂如何设计高性能红点系统。红点作为提升用户活跃和转化率的重要工具,需要支持多级联动、动态判断和全局一致性。系统采用观察者模式和树形结构,分为数据管理、逻辑判定、订阅通知和UI展示四大模块,通过事件驱动方式实现高效刷新。文章特别强调了解耦设计、配置化管理和性能优化,并提出了热更新、数据分析等高级应用场景。最终指出,优秀的红点系统需要架构标准化、自动化配置和团队紧密协作,是大型游戏项目的关键技术基础。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 377

游戏性能优化终极指南:如何用OptiScaler实现帧率翻倍与画质提升

你是否曾经在玩最新3A大作时,看着帧率数字不断跳动却无能为力?或者在使用AMD显卡时羡慕Nvidia用户的DLSS技术?现在,一个名为OptiScaler的开源工具正在改变游戏性能优化的格局。这款跨平台、多显卡兼容的上采样解决方案,让所有玩家都能享受到最新的图形技术红利。 OptiScaler的核心价值在于它的"桥梁"作用——它能够拦截游戏中的上采样调用,并将其重定向到你选择的任何上采样后端。这

gitblog_00278的博客 1017

OpenCV-Python实战(26)——复杂场景下的实时物体检测与跟踪

本节介绍了一种鲁棒的特征跟踪方法,该方法速度足够快,能够实时应用于网络摄像头的视频流。首先,算法展示了如何在一幅图像中提取和检测重要特征——无论是我们感兴趣的模板图像(训练图像),还是预期包含该物体的更复杂场景(查询图像),这些特征都具有视角和尺寸不变性。然后对关键点进行聚类,找到两幅图像中特征点之间的匹配。在此基础上,可以计算出一个将一组特征点映射到另一组的透视变换矩阵。利用这些信息,我们可以在查询图像中勾勒出训练图像的轮廓,并对查询图像进行透视变换,使感兴趣的物体以直立状态显示在屏幕中央。

盼小辉丶的博客 1890

基于Costas环的载波同步实现及Matlab仿真

其中,第一个相位环接收带噪声的待同步载波,并产生一个相位误差信号,这个误差信号再通过第二个相位环进行反馈控制,使得两个相位环的相位差保持为90度,最终输出同步载波信号。接下来,我们可以编写Costas环的仿真程序,并将上面生成的信号输入到Costas环中进行同步,最终输出同步后的载波信号。综上所述,本文介绍了如何使用Matlab实现基于Costas环的载波同步,并给出相应的源代码。上述代码中,我们初始化了Costas环的各个参数,然后通过循环迭代实现了相位估计和相位误差控制,最终输出了同步后的载波信号。

2301_78484069的博客 1048

Java 8流中的数据库CRUD操作

在开始使用新工具时要克服的最大障碍是让您着手处理小事情。 到目前为止,您可能对新的Java 8 Stream API的工作方式充满信心,但是您可能尚未将其用于数据库查询。 为了帮助您开始使用Stream API创建,修改和读取SQL数据库,我整理了此快速入门。 希望它可以帮助您将信息流提升到一个新的水平! 背景 Speedment是一个开源工具包 ,可用于生成Java实体和管理器以与数据...

最佳 Java 编程 285

2025资源从哪里来!

研究网络通信中的安全问题,如VLAN划分、路由协议(静态、动态如OSPF、BGP)、NAT转换、AAA认证、访问控制列表(ACL)等。‌Wi-Fi安全设置‌、‌密码破解原理‌(仅用于合法测试或教育目的)、‌常见Web安全问题‌如SQL注入、XSS跨站脚本攻击等的防范措施。在这个网站你可以找到你对应的专业-对应的路线-对应的资源-对应的百度网盘里面全是免费的资源------------------惊喜不?‌防火墙配置与策略‌、‌入侵检测与防御系统(IDS/IPS)‌、‌端口扫描与漏洞扫描技术‌等。

qq_56248592的博客 1902

java lambda sql_Speedment -- 利用lambda编写SQL

众所周知Java8中加入了lambda语法,这一特性也帮助Java开发者极大的简化了开发。Speedment是一个利用lambda表达式操作数据库的框架,相比Java世界中现在非常流行的mybatis,它可以极大减少SQL语句的编写。从图形界面开始使用SpeedmentSpeedment使用Groovy配置来找到数据库并生成对应的一些Java代码,groovy配置文件一般不需要自己来编写,而是通过...

weixin_32816563的博客 1030

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

迈瑞BS系列全自动生化分析仪LIS协议接口手册(内含HL7和ASTM)

迈瑞BS系列全自动生化分析仪LIS协议接口手册,内含HL7和ASTM)

Speedment 3.0的新功能

如果您关注我的博客,那么您会知道我已经参与开源项目Speedment已有一段时间了。 在夏季和秋季,我已经完成了很多工作,完成了该工具包的下一个3.0.0大型发行版。 在这篇文章中,我将展示我们已经内置在平台中的一些很酷的新功能,并说明如何入门! 新模块系统 与上一个版本的Speedment相比,最大的变化是新的模块系统,它使我们花费了最多的时间才能完成工作。 如果您一直在关注新的进度JDK ...

cunhu4317的博客 178

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

使用Speedment实现事务处理

使用Speedment实现事务处理 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载。 一、Speedment介绍Speedment是一个开源的、基于Java的、流式ORM工具包和运行时工具,它把对现有数据库和表的各种操作封装成Java 8的Stream操作。Speedment的新版本还提供了支持数据库事务处理的便捷操作方式。Speedment在GitHub的地址:https://gi

chszs的专栏 1775

java摩尔常量_认识反摩尔定律的Java 8神器Speedment,实现数据库代码减半

认识反摩尔定律的Java 8神器Speedment实现数据库代码减半 回想九十年代末期我刚开始写Java数据库应用的时候什么事都得自己做。代码很多,而且异常处理对象转换都得自己写,所以很快代码就变得难以维护了。 就算是今天把Java和数据库这两个不同世界的东西联系起来还是相当的麻烦。你得自己写映射层或者使用Hibernate这样的ORM工具,使用ORM当然会方便点但配置的时候比较容易出错。很多时候...

weixin_39944944的博客 219

pdfreader与pdf2json深度整合:打造企业级PDF数据处理解决方案

在当今数字化办公环境中,PDF文件作为信息交换的重要载体,其数据提取与处理需求日益增长。**pdfreader**作为一款基于Node.js的PDF解析工具,通过与**pdf2json**的深度整合,为企业级应用提供了高效、灵活的PDF文本与表格数据处理能力。本文将详细介绍这一解决方案的技术架构、核心功能及实战应用,帮助开发者快速构建专业的PDF数据处理系统。 ## 技术架构:pdfreader

gitblog_00097的博客 430

使用Speedment和Spring创建REST API

随着Spring Boot的第4版发布,为Web开发企业应用程序变得非常容易。 代表开发人员仍然需要大量时间的事情是,例如在Hibernate中对现有数据库进行建模,以获取数据的面向对象的视图。 在本教程中,我们将探索如何结合使用Spring的开源工具Speedment和Spring,只需按一下按钮即可生成实体,管理器和控制器,从而使您可以更快地开始开发。 关于速度 Speedment是...

最佳 Java 编程 281

将速度加快到自己的个人代码生成器中

Speedment是一个开放源代码工具箱 ,可用于生成Java实体和管理器以与数据库进行通信。 如果您需要域模型的对象关系映射,那么这很好,但是在某些情况下,您可能希望使用数据库作为模板来生成完全不同的东西。 在本文中,我将向您展示一个hack,您可以使用该hack接管Speedment中的该代码生成器并将其用于个人目的。 在文章的结尾,我们将有一个完全空白的代码生成器,我们可以编程该代码生...

最佳 Java 编程 186

如何使用插件生成自定义Java 8代码

大多数程序员讨厌的一件事就是编写样板代码。 无休止的时间花费在设置实体类和配置数据库连接上。 为了避免这种情况,您可以让Speedment Open Source之类的程序为您生成所有这些代码。 这样可以很容易地以最少的人工就可以启动和运行数据库项目,但是当将大部分代码移交给机器时,如何保持对编写代码的控制? 假设您有一个数据库,该数据库的表“ user”具有“ gender”列,并且您...

最佳 Java 编程 226

Java:如何在不到5分钟的时间内通过Hazelcast提高生产力

如果要使用Hazelcast内存数据网格(IMDG)来加快数据库应用程序的速度,但是要处理数百个表怎么办? 手动编码所有Java POJO和序列化支持将需要数周的工作,完成后,手动维护该域模型将很快成为一场噩梦。 阅读本文,了解如何节省时间并在5分钟内完成。 现在,有一种优雅的方法可以管理这些需求。 Hazelcast Auto DB集成工具允许连接到现有数据库,该数据库可以自动生成所有...

最佳 Java 编程 303

java 8 数据库_Java 8 Streams 中的数据库 CRUD 操作

接触一个新工具的时候,刚开始要克服的最大障碍就是如何让你自己先尝试做出一个小东西来。现在你也许对 Java 8 中新的StreamAPI 的运作方式在理解上比较自信,但你也许并没用它来进行过数据库查询操作。为了帮助你开始使用 Stream API 来对 SQL数据库进行创建、修改和读取操作, 我已经在这个快速开始的教程中把它们整合到了一起。希望它能帮助你提升对流式API的使用水平!背景Speed...

weixin_29441555的博客 212

轻松地与Java完全集成

这里介绍了如何编写完整的堆栈数据库Web应用程序,而不使用SQL,HQL,PHP,ASP,HTML,CSS或Javascript,而是使用Vaadin的UI层和Speedment Stream ORM完全依赖Java。 是否曾经想过快速创建连接到您现有数据库的Web应用程序,或构建具有较短上市时间要求的专业应用程序? Java Stream API释放了用纯Java编写数据库查询的可能性...

最佳 Java 编程 330
上一篇: NIO实现服务器和客户端简单通讯与群聊系统
下一篇: redisTemplate工具类分享
金心靖晨
博客等级 码龄9年 460粉丝 211原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值