跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Java大前端java

Spring Boot 数据可视化与图表集成实战

Spring Boot 结合 ECharts 实现数据可视化,通过 Thymeleaf 模板引擎将后端 Java 数据注入前端 JavaScript 脚本。文章详细演示了从依赖配置、实体类设计、服务层逻辑到前端图表渲染的全流程。重点解决了如何在 HTML 脚本块中安全传递列表数据的问题,实现了产品销量等数据的直观展示。该方案适用于后台管理系统中的销售报表、用户分析及运营监控等场景,帮助开发者快速构建交互式数据大屏。

游戏玩家发布于 2026/3/26更新于 2026/6/1325 浏览
Spring Boot 数据可视化与图表集成实战

Spring Boot 数据可视化与图表集成实战

数据可视化示意图

在 Java 开发中,单纯的数据列表往往难以直观反映业务趋势。将后端数据通过图表形式展示,不仅能提升可读性,还能辅助快速决策。本文将带你从零开始,在 Spring Boot 项目中集成 ECharts,实现产品销量等数据的可视化展示。

为什么需要数据可视化

数据可视化的核心在于将抽象数据转化为直观的图形。常见的工具包括 ECharts、Highcharts 和 D3.js 等。对于 Spring Boot 项目,ECharts 因其文档完善、功能丰富且开源免费,成为首选方案之一。

项目搭建与依赖配置

首先,我们需要创建一个标准的 Spring Boot Web 项目。在 pom.xml 中引入 Web 和 Thymeleaf 依赖,Thymeleaf 能方便地在服务端渲染 HTML 并嵌入动态数据。

<dependencies>
    <!-- Web 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thymeleaf 模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <>spring-boot-starter-test
        test
    

目录

  1. Spring Boot 数据可视化与图表集成实战
  2. 为什么需要数据可视化
  3. 项目搭建与依赖配置
  4. 后端数据层实现
  5. 前端页面与 ECharts 集成
  6. 实际应用场景
  • 免费图片AI生成工具免费生成了解详情
artifactId
</artifactId>
<scope>
</scope>
</dependency>
</dependencies>

接着配置 application.properties,关闭缓存以便调试时能即时看到页面变化:

server.port=8080
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.suffix=.html
spring.thymeleaf.prefix=classpath:/templates/

后端数据层实现

为了演示效果,我们构建一个简单的产品管理模块。实体类定义需规范,注意字段类型和 Getter/Setter 的完整性。

public class Product {
    private Long id;
    private String productId;
    private String productName;
    private double price;
    private int sales;

    public Product() {}

    public Product(Long id, String productId, String productName, double price, int sales) {
        this.id = id;
        this.productId = productId;
        this.productName = productName;
        this.price = price;
        this.sales = sales;
    }

    // Getter and Setter methods omitted for brevity
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getProductId() { return productId; }
    public void setProductId(String productId) { this.productId = productId; }
    public String getProductName() { return productName; }
    public void setProductName(String productName) { this.productName = productName; }
    public double getPrice() { return price; }
    public void setPrice(double price) { this.price = price; }
    public int getSales() { return sales; }
    public void setSales(int sales) { this.sales = sales; }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", productId='" + productId + '\'' +
                ", productName='" + productName + '\'' +
                ", price=" + price +
                ", sales=" + sales +
                '}';
    }
}

使用内存列表模拟数据库操作,便于快速验证逻辑。Repository 层负责数据的增删改查:

import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Repository
public class ProductRepository {
    private List<Product> products = new ArrayList<>();

    public ProductRepository() {
        products.add(new Product(1L, "P001", "手机", 1000.0, 100));
        products.add(new Product(2L, "P002", "电脑", 5000.0, 50));
        products.add(new Product(3L, "P003", "电视", 3000.0, 80));
        products.add(new Product(4L, "P004", "手表", 500.0, 200));
        products.add(new Product(5L, "P005", "耳机", 300.0, 150));
    }

    public List<Product> getAllProducts() {
        return products;
    }

    public Product getProductById(Long id) {
        return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
    }

    public void addProduct(Product product) {
        product.setId((long) (products.size() + 1));
        products.add(product);
    }

    public void updateProduct(Product product) {
        Product existing = getProductById(product.getId());
        if (existing != null) {
            existing.setProductId(product.getProductId());
            existing.setProductName(product.getProductName());
            existing.setPrice(product.getPrice());
            existing.setSales(product.getSales());
        }
    }

    public void deleteProduct(Long id) {
        products.removeIf(p -> p.getId().equals(id));
    }
}

Service 层简单透传 Repository 方法,Controller 层负责路由映射和模型数据传递:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Controller
@RequestMapping("/api/products")
public class ProductController {
    @Autowired
    private ProductService productService;

    @GetMapping("/")
    public String getAllProducts(Model model) {
        List<Product> products = productService.getAllProducts();
        model.addAttribute("products", products);
        return "product-list";
    }

    @GetMapping("/{id}")
    public String getProductById(@PathVariable Long id, Model model) {
        Product product = productService.getProductById(id);
        model.addAttribute("product", product);
        return "product-detail";
    }

    @GetMapping("/add")
    public String addProductForm(Model model) {
        model.addAttribute("product", new Product());
        return "product-form";
    }

    @PostMapping("/add")
    public String addProduct(@ModelAttribute Product product) {
        productService.addProduct(product);
        return "redirect:/api/products/";
    }

    @GetMapping("/edit/{id}")
    public String editProductForm(@PathVariable Long id, Model model) {
        Product product = productService.getProductById(id);
        model.addAttribute("product", product);
        return "product-form";
    }

    @PostMapping("/edit/{id}")
    public String editProduct(@PathVariable Long id, @ModelAttribute Product product) {
        product.setId(id);
        productService.updateProduct(product);
        return "redirect:/api/products/";
    }

    @GetMapping("/delete/{id}")
    public String deleteProduct(@PathVariable Long id) {
        productService.deleteProduct(id);
        return "redirect:/api/products/";
    }
}

前端页面与 ECharts 集成

这是最关键的一步。我们需要在 Thymeleaf 模板中引入 ECharts 库,并将后端传来的数据序列化为 JavaScript 数组。

<!DOCTYPE html>
<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>产品列表</title>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js"></script>
</head>
<body>
    <h1>产品列表</h1>
    <a href="/api/products/add">添加产品</a>
    
    <table border="1">
        <thead>
            <tr>
                <th>ID</th>
                <th>产品 ID</th>
                <th>产品名称</th>
                <th>价格</th>
                <th>销量</th>
                <th>操作</th>
            </tr>
        </thead>
        <tbody>
            <tr th:each="product : ${products}">
                <td th:text="${product.id}"></td>
                <td th:text="${product.productId}"></td>
                <td th:text="${product.productName}"></td>
                <td th:text="${product.price}"></td>
                <td th:text="${product.sales}"></td>
                <td>
                    <a th:href="@{/api/products/edit/{id}(id=${product.id})}">编辑</a>
                    <a th:href="@{/api/products/delete/{id}(id=${product.id})}">删除</a>
                </td>
            </tr>
        </tbody>
    </table>

    <h2>产品销量图表</h2>
    <div id="salesChart" style="width: 800px;height: 400px;"></div>

    <script>
        var chartDom = document.getElementById('salesChart');
        var myChart = echarts.init(chartDom);
        var option;
        
        // 准备数据
        var productNames = [];
        var productSales = [];
        
        // 利用 Thymeleaf 将后端数据注入到 JS 变量中
        <th:block th:each="product : ${products}">
            productNames.push('<span th:text="${product.productName}"></span>');
            productSales.push(<span th:text="${product.sales}"></span>);
        </th:block>
        
        option = {
            title: {
                text: '产品销量图表',
                left: 'center'
            },
            tooltip: {
                trigger: 'item'
            },
            legend: {
                orient: 'vertical',
                right: 10,
                top: 'center'
            },
            series: [{
                name: '销量',
                type: 'pie',
                radius: [, ],
                : ,
                : {
                    : ,
                    : ,
                    : 
                },
                : {
                    : ,
                    : 
                },
                : {
                    : {
                        : ,
                        : ,
                        : 
                    }
                },
                : {
                    : 
                },
                : [
                    
                ]
            }]
        };
        
        option && myChart.(option);
    </script>
</body>
</html>

启动应用后访问 http://localhost:8080/api/products/,即可看到包含产品列表和饼图的销售仪表盘。点击相关按钮可完成产品的增删改查操作。

实际应用场景

这种模式不仅限于产品展示,在实际业务中非常通用:

  • 用户分析:统计不同地区或年龄段的用户分布。
  • 订单监控:实时展示每日订单量趋势。
  • 销售报表:多维度对比各品类销售额占比。

关键在于根据业务需求选择合适的图表类型(折线图、柱状图、饼图等),并通过 RESTful API 将数据传递给前端渲染。

  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 原生js事件绑定和事件移除
  • 基于 GLM-4.6V-Flash-WEB 搭建物理实验报告自动评分系统
  • 自主机器人核心技术学习指南
  • Spring Boot + jQuery 前后端分离图书管理系统:从接口设计到问题排查
  • Java 核心面试题与实战解析
'40%'
'70%'
avoidLabelOverlap
false
itemStyle
borderRadius
10
borderColor
'#fff'
borderWidth
2
label
show
false
position
'center'
emphasis
label
show
true
fontSize
20
fontWeight
'bold'
labelLine
show
false
data
<th:block th:each="product : ${products}"> {value: <span th:text="${product.sales}"></span>, name: '<span th:text="${product.productName}"></span>'}, </th:block>
setOption
Visual C++ 运行库缺失问题修复指南
  • 沃研 Turbo 科研大模型发布:支持论文推荐、降重与润色
  • 新版 llama.cpp 使用及本地部署 LLaMA
  • 堆排序与 TopK 问题实战解析
  • Ubuntu 部署 OpenClaw 实战指南
  • 树莓派 4B 连接大疆 M300 无人机开发教程
  • SpringBoot+Vue 社团管理系统设计与实现
  • 网络安全工程师七年实战经验与技能成长指南
  • SLAM+AI Agent 机器人自主导航最小原型实现
  • Apache IoTDB 在工业物联网时序数据管理中的核心优势
  • 文心一言 4.5 评测与本地部署指南:开源大模型的中文能力实测
  • OpenClaw 对接飞书实现多机器人群聊配置
  • Stable Diffusion 3.5 移动端方案:手机连接云端 GPU 创作
  • 前端调用 AI 接口全流程及具体案例
  • 生产环境 Nginx 双机热备部署 - Keepalived 多模式配置
  • 相关免费在线工具

    • Keycode 信息

      查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online

    • Escape 与 Native 编解码

      JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online

    • JavaScript / HTML 格式化

      使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online

    • JavaScript 压缩与混淆

      Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online

    • Base64 字符串编码/解码

      将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

    • Base64 文件转换器

      将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online