Spring Boot CSV - 在 Spring Boot 应用程序中提供 CSV 数据

位置:首页>文章>详情   分类: Java教程 > 编程技术   阅读(485)   2023-06-26 07:54:18

在本教程中,我们将在 Spring Boot RESTful 应用程序中提供 CSV 格式的内容。我们使用 Opencsv 库。

CSV(逗号分隔值) 是一种简单的数据格式,其中值(大部分)由逗号分隔,每行代表一条记录。数据存储在纯文本文件中。它作为电子表格和数据库中使用的导入和导出格式非常流行。 打开csv 是一个用于Java 的开源、简单的CSV 解析器库。

Spring Boot CSV 示例

我们的应用程序是一个 Spring Boot RESTful 应用程序,它以 CSV 格式从 H2 数据库返回数据。

build.gradle
...
src
├── main
│   ├── java
│   │   └── com
│   │       └── zetcode
│   │           ├── Application.java
│   │           ├── controller
│   │           │   └── CityController.java
│   │           ├── model
│   │           │   └── City.java
│   │           ├── repository
│   │           │   └── CityRepository.java
│   │           ├── service
│   │           │   ├── CityService.java
│   │           │   └── ICityService.java
│   │           └── util
│   │               └── WriteCsvToResponse.java
│   └── resources
│       ├── application.yml
│       └── import.sql
└── test
    ├── java
    └── resources

这是项目结构。

build.gradle
plugins {
    id 'org.springframework.boot' version '2.6.7'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.zetcode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.opencsv:opencsv:5.6'

    runtimeOnly 'com.h2database:h2'
}

这是 Gradle 构建文件。 h2 依赖项为 H2 数据库添加了一个驱动程序。 opencsv 依赖项为 Opencsv 库添加了一个驱动程序。

Spring Boot 启动器是一组方便的依赖描述符,我们可以将其包含在我们的应用程序中。它们极大地简化了应用程序配置。 spring-boot-starter-parent 为 Spring Boot 应用程序提供了一些通用配置。 spring-boot-starter-web 是使用 Spring MVC 构建 web(包括 RESTful 应用程序)的入门工具。它使用 Tomcat 作为默认的嵌入式容器。 spring-boot-starter-data-jpa 是将 Spring Data JPA 与 Hibernate 结合使用的入门工具。

resources/application.yml
spring:
    main:
        banner-mode: "off"
    jpa:
        database: h2
        hibernate:
            dialect: org.hibernate.dialect.H2Dialect
            ddl-auto: create-drop

logging:
    level:
        org:
            springframework: ERROR

application.yml 文件包含 Spring Boot 应用程序的各种配置设置。通过 banner-mode 属性,我们关闭了 Spring 横幅。

JPA database 值指定要操作的目标数据库。在我们的例子中,我们指定了 Hibernate 方言 org.hibernate.dialect.H2Dialectddl-auto是数据定义语言模式; create-drop 选项自动创建和删除数据库模式。

H2数据库在内存中运行。此外,我们将 spring 框架的日志记录级别设置为 ERROR。 application.yml 文件位于 src/main/resources 目录中。

com/zetcode/model/City.java
package com.zetcode.model;

import java.util.Objects;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "cities")
public class City {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int population;

    public City() {
    }

    public City(Long id, String name, int population) {

        this.id = id;
        this.name = name;
        this.population = population;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPopulation() {
        return population;
    }

    public void setPopulation(int population) {
        this.population = population;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 79 * hash + Objects.hashCode(this.id);
        hash = 79 * hash + Objects.hashCode(this.name);
        hash = 79 * hash + this.population;
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final City other = (City) obj;
        if (this.population != other.population) {
            return false;
        }
        if (!Objects.equals(this.name, other.name)) {
            return false;
        }
        return Objects.equals(this.id, other.id);
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("City{");
        sb.append("id=").append(id);
        sb.append(", name='").append(name).append('\'');
        sb.append(", population=").append(population);
        sb.append('}');
        return sb.toString();
    }
}

这是 City 实体。每个实体必须至少定义两件事:@Entity 注释和带有 @Id 注释的 Id 字段。我们已将 ddl-auto 选项设置为 create-drop,这意味着 Hibernate 将从该实体创建表模式。

@Entity
@Table(name = "cities")
public class City {

@Entity 注释指定该类是一个实体并映射到数据库表。 @Table 实体指定要用于映射的数据库表的名称。

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Id注解指定实体的主键,@GeneratedValue注解指定主键值的生成策略.

resources/import.sql
INSERT INTO cities(name, population) VALUES('Bratislava', 432000);
INSERT INTO cities(name, population) VALUES('Budapest', 1759000);
INSERT INTO cities(name, population) VALUES('Prague', 1280000);
INSERT INTO cities(name, population) VALUES('Warsaw', 1748000);
INSERT INTO cities(name, population) VALUES('Los Angeles', 3971000);
INSERT INTO cities(name, population) VALUES('New York', 8550000);
INSERT INTO cities(name, population) VALUES('Edinburgh', 464000);
INSERT INTO cities(name, population) VALUES('Berlin', 3671000);

该模式由 Hibernate 自动创建;稍后,执行 import.sql 文件以用数据填充表。

com/zetcode/repository/CityRepository.java
package com.zetcode.repository;

import com.zetcode.model.City;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface CityRepository extends CrudRepository<City, Long> {

}

通过从 Spring CrudRepository 扩展,我们将为我们的数据存储库实现一些方法,包括 findAll()findById()。这样我们就节省了很多样板代码。

com/zetcode/service/ICityService.java
package com.zetcode.service;

import com.zetcode.model.City;
import java.util.List;

public interface ICityService {

    List<City> findAll();
    City findById(Long id);
}

ICityService 提供了获取所有城市的契约方法,并通过城市的Id从数据源中获取城市。

com/zetcode/service/CityService.java
package com.zetcode.service;

import com.zetcode.model.City;
import com.zetcode.repository.CityRepository;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class CityService implements ICityService {

    private final CityRepository repository;

    public CityService(CityRepository repository) {
        this.repository = repository;
    }

    @Override
    public List<City> findAll() {

        return  (List<City>) repository.findAll();
    }

    @Override
    public City findById(Long id) {

        return repository.findById(id).orElse(new City());
    }
}

CityService 包含 findAll()findById() 方法的实现。我们使用存储库从数据库中检索数据。

private final CityRepository repository;

public CityService(CityRepository repository) {
    this.repository = repository;
}

CityRepository 被注入。

return  (List<City>) repository.findAll();

存储库的 findAll() 方法返回城市列表。

return repository.findById(id).orElse(new City());

存储库的 findById() 方法返回一个特定的城市对象。如果找不到城市,我们将发送一个空的城市对象。

com/zetcode/controller/CityController.java
package com.zetcode.controller;

import com.zetcode.model.City;
import com.zetcode.service.ICityService;
import com.zetcode.util.WriteCsvToResponse;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@RestController
public class CityController {

    private final ICityService cityService;

    public CityController(ICityService cityService) {
        this.cityService = cityService;
    }

    @RequestMapping(value = "/cities", produces = "text/csv")
    public void findCities(HttpServletResponse response) throws IOException {

        List<City> cities = cityService.findAll();

        WriteCsvToResponse.writeCities(response.getWriter(), cities);
    }

    @RequestMapping(value = "/cities/{cityId}", produces = "text/csv")
    public void findCity(@PathVariable Long cityId, HttpServletResponse response) throws IOException {

        City city = cityService.findById(cityId);
        WriteCsvToResponse.writeCity(response.getWriter(), city);
    }
}

这是 Spring Boot RESTful 应用程序的控制器类。 @RestController 注释创建一个 RESTful 控制器。传统的 MVC 控制器使用 ModelAndView,而 RESTful 控制器只是简单地返回对象,对象数据以 JSON 或 XML 格式直接写入 HTTP 响应(通常)。在我们的例子中,我们选择了 CSV 格式。

private final ICityService cityService;

public CityController(ICityService cityService) {
    this.cityService = cityService;
}

我们将 ICityService 注入到 countryService 字段中。

@RequestMapping(value = "/cities", produces = "text/csv")
public void findCities(HttpServletResponse response) throws IOException {

...
}

@RequestMapping 注释用于将 Web 请求映射到 Spring 控制器方法。 produces 选项设置媒体类型,在我们的例子中是 text/csv。我们将带有 /cities 路径的请求映射到控制器的 findCities() 方法。默认请求是 GET 请求。

List<City> cities = cityService.findAll();

WriteCsvToResponse.writeCities(response.getWriter(), cities);

我们调用 cityService's findAll() 来获取所有城市。我们将 CSV 数据写入 HttpServletResponse 对象。 Java bean 到 CSV 数据的映射委托给 WriteCsvToResponse 类。

@RequestMapping(value = "/cities/{cityId}", produces = "text/csv")
public void findCity(@PathVariable Long cityId, HttpServletResponse response) throws IOException {

    City city = cityService.findById(cityId);
    WriteCsvToResponse.writeCity(response.getWriter(), city);
}

在第二种方法中,我们有一个 URL 路径,其中包含要检索的城市的 Id;我们使用 @PathVariable 注释将 URL 模板变量绑定到方法 cityId 参数。

com/zetcode/util/WriteCsvToResponse.java
package com.zetcode.util;

import com.opencsv.CSVWriter;
import com.opencsv.bean.ColumnPositionMappingStrategy;
import com.opencsv.bean.StatefulBeanToCsv;
import com.opencsv.bean.StatefulBeanToCsvBuilder;
import com.opencsv.exceptions.CsvException;
import com.zetcode.model.City;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.PrintWriter;
import java.util.List;

public class WriteCsvToResponse {

    private static final Logger LOGGER = LoggerFactory.getLogger(WriteCsvToResponse.class);

    public static void writeCities(PrintWriter writer, List<City> cities) {

        try {

            var builder = getStatefulBean(writer);
            builder.write(cities);

        } catch (CsvException ex) {

            LOGGER.error("Error mapping Bean to CSV", ex);
        }
    }

    public static void writeCity(PrintWriter writer, City city) {

        try {

            var builder = getStatefulBean(writer);
            builder.write(city);

        } catch (CsvException ex) {

            LOGGER.error("Error mapping Bean to CSV", ex);
        }
    }

    private static StatefulBeanToCsv<City> getStatefulBean(PrintWriter writer) {

        ColumnPositionMappingStrategy<City> mapStrategy
                = new ColumnPositionMappingStrategy<>();

        mapStrategy.setType(City.class);

        String[] columns = new String[]{"id", "name", "population"};
        mapStrategy.setColumnMapping(columns);

        StatefulBeanToCsv<City> builder = new StatefulBeanToCsvBuilder<City>(writer)
                .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
                .withMappingStrategy(mapStrategy)
                .withSeparator(',')
                .build();

        return builder;
    }
}

WriteCsvToResponse 中,我们使用 Opencsv 库将 Java bean 转换为 CSV,并将最终输出写入 HttpServletResponse

ColumnPositionMappingStrategy<City> mapStrategy
    = new ColumnPositionMappingStrategy<>();

mapStrategy.setType(City.class);

MappingStrategy 定义了 Java 属性如何映射到 CSV 列名。 ColumnPositionMappingStrategy 使用列位置进行映射。

String[] columns = new String[]{"id", "name", "population"};
mapStrategy.setColumnMapping(columns);

我们设置列名。

StatefulBeanToCsv<City> builder = new StatefulBeanToCsvBuilder<City>(writer)
    .withQuotechar(CSVWriter.NO_QUOTE_CHARACTER)
    .withMappingStrategy(mapStrategy)
    .withSeparator(',')
    .build();

StatefulBeanToCsv 类以 CSV 格式将 bean 写出,以保存状态信息并智能猜测要应用的映射策略。

var builder = getStatefulBean(writer);
builder.write(cities);

bean 被写入响应对象。

com/zetcode/Application.java
package com.zetcode;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

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

Application 设置 Spring Boot 应用程序。 @SpringBootApplication 启用自动配置和组件扫描。

$ ./gradlew bootRun

我们运行应用程序。

$ curl localhost:8080/cities
1,Bratislava,432000
2,Budapest,1759000
3,Prague,1280000
4,Warsaw,1748000
5,Los Angeles,3971000
6,New York,8550000
7,Edinburgh,464000
8,Berlin,3671000

使用 curl 命令,我们可以获取所有城市。

$ curl localhost:8080/cities/1
1,Bratislava,432000

在这里,我们得到一个由其 Id 标识的城市。

在本教程中,我们已将数据从 Spring Boot RESTful 应用程序以 CSV 格式返回给客户端。我们使用了 Opencsv 库。

List 所有 Spring Boot 教程.

地址:https://www.cundage.com/article/springboot-csv.html

相关阅读

Spring Boot Vue.js 教程展示了如何使用 Vue.js 框架创建一个简单的 Spring Boot。 Vue.js Vue.js 是一个用于构建用户界面的 JavaScript ...
JavaScript 是否已经取代 Java 成为新的“一次编写,随处运行”的编程语言?这完全取决于您的观点。随着 WebAssembly 等技术的出现,Java 可以在新奇的“一次编写,随处编...
Usage of TypeScript,微软基于 JavaScript 的强类型语言, has soared compared to six years ago, according to th...
云莓将基于 Spring 构建的 Java 后端与使用 Lit 构建的 TypeScript 前端相结合,一个快速、响应式的 JavaScript 框架。基于 Vaadin Fusion 的 H...
本博客严重偏向于 GWT(和基于 GWT 的框架),但我们牢记 GWT 将来可能会被其他技术接管,因此我们始终对探索其他平台/框架持开放态度。正如他们所说,多元化可以降低风险。每种编程语言,即使...
Java JSON 教程展示了如何使用 JSON-Java 在 Java 中进行 JSON 序列化和反序列化。 JSON(JavaScript 对象显示法) 是一种轻量级数据交换格式。人类易于读...
JHipster 是一个长期存在且雄心勃勃的混合 Java 和 JavaScript 项目,致力于使用现代反应式前端简化全栈 Java 应用程序的开发。 JHipster 开发团队不断发布新版本...
解析器是强大的工具,使用 ANTLR 可以编写可用于多种不同语言的各种解析器。 在这个完整的教程中,我们将: 解释基础:什么是解析器,它可以用来做什么 查看如何设置 ANTLR 以便在 Java...
Spring Boot JSON 教程展示了如何在 Spring Boot 注释中提供 JSON 数据。 春天 是一个流行的 Java 应用程序框架,弹簧贴 是 Spring 的演变,有助于创建...
根据最近一项全球开发人员调查,在开发人员偏好方面,JavaScript 和 Python 保持了持久力,而 锈 的使用率正在上升. 5 月 4 日的一份题为“开发者国家情况,第 22nd 版”的...