首页 > Java > java教程 > 正文

Spring Boot中动态读取和持久化外部JSON文件教程

花韻仙語
发布: 2025-11-12 16:08:31
原创
764人浏览过

Spring Boot中动态读取和持久化外部JSON文件教程

本教程旨在解决spring boot应用在定时任务中读取持续更新的外部json文件时遇到的数据无法实时同步问题。文章将深入分析`class.getresourceasstream()`的局限性,并提供一套基于文件系统路径读取的解决方案,结合最佳实践(如构造器注入)和spring `@scheduled`注解,确保应用能高效、准确地将外部动态数据持久化到数据库。

Spring Boot中动态文件读取的挑战

在Spring Boot应用开发中,我们经常需要处理外部数据源,例如JSON文件。当这些文件位于项目的src/main/resources目录下,并且需要被应用周期性地读取和更新数据库时,可能会遇到一个常见问题:即使外部JSON文件内容已经更新,应用通过@Scheduled任务读取到的数据仍然是旧的。

这背后的主要原因是src/main/resources目录下的文件在应用打包(如JAR或WAR)后,会被视为类路径资源。当使用Class.getResourceAsStream()方法获取这些文件的输入流时,JVM或类加载器通常会从打包好的文件中读取,并且可能对这些资源进行缓存。这意味着,即使你在文件系统外部修改了src/main/resources目录下的源文件,运行中的Spring Boot应用通过getResourceAsStream获取的仍然是打包时或首次加载时的旧版本内容,无法反映实时的外部修改。

因此,对于需要持续更新且实时读取的文件,将其作为类路径资源处理是不合适的。我们应该将其视为外部文件,通过文件系统路径直接访问。

解决方案:访问外部文件而非类路径资源

要解决上述问题,核心在于改变文件读取策略:不再将动态更新的JSON文件视为类路径资源,而是将其作为普通的文件系统文件进行访问。

  1. 将JSON文件放置在应用外部可访问的路径:例如,与JAR包同级的某个目录下,或者一个明确的配置路径。
  2. 通过文件系统路径直接读取:使用java.nio.file.Path和java.nio.file.Files或者java.io.File和java.io.FileInputStream来获取文件的输入流。

示例代码重构

我们将对原有的Spring Boot应用进行改造,以实现动态JSON文件的实时读取和数据库持久化。

1. 配置外部文件路径

首先,在application.properties或application.yml中定义外部JSON文件的路径。

Find JSON Path Online
Find JSON Path Online

Easily find JSON paths within JSON objects using our intuitive Json Path Finder

Find JSON Path Online 30
查看详情 Find JSON Path Online

application.properties

json.file.path=/path/to/your/external/json/file.json
# 例如:json.file.path=./data/file.json (相对于应用启动目录)
# 或:json.file.path=/opt/app/data/file.json (绝对路径)
登录后复制

请根据实际情况替换/path/to/your/external/json/file.json。

2. Master模型类(不变)

// package com.example.demo.model;
// import lombok.Data;
// @Data
// public class Master {
//    // ... 你的Master实体字段
//    private Long id;
//    private String name;
//    // ...
// }
登录后复制

3. MasterRepository接口(不变)

package com.example.demo.Repository;

import com.example.demo.model.Master;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface MasterRepository extends CrudRepository<Master, Long> {
}
登录后复制

4. MasterService服务类(优化依赖注入)

为了遵循Spring的最佳实践,我们将@Autowired字段注入改为构造器注入。这使得依赖关系更清晰,便于测试,并确保所有必需的依赖在对象构造时就已提供。

package com.example.demo.Services;

import com.example.demo.Repository.MasterRepository;
import com.example.demo.model.Master;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; // 导入事务注解

import java.util.List;

@Service
public class MasterService {
    private final MasterRepository masterRepository; // 使用final修饰,强调不可变性

    // 构造器注入
    public MasterService(MasterRepository masterRepository) {
        this.masterRepository = masterRepository;
    }

    public Iterable<Master> list() {
        return masterRepository.findAll();
    }

    @Transactional // 确保保存操作在事务中执行
    public Master save(Master master) {
        return masterRepository.save(master);
    }

    @Transactional // 确保批量保存操作在事务中执行
    public Iterable<Master> save(List<Master> masters) {
        return masterRepository.saveAll(masters);
    }
}
登录后复制

5. 主应用类(重构文件读取逻辑)

在主应用类中,我们将注入配置的JSON文件路径,并在@Scheduled方法中通过Path和Files来读取文件。

package com.example.demo;

import com.example.demo.Services.MasterService;
import com.example.demo.model.Master;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value; // 导入Value注解
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files; // 导入Files工具类
import java.nio.file.Path; // 导入Path类
import java.nio.file.Paths; // 导入Paths工具类
import java.util.List;

// 假设MasterList不再需要,直接读取List<Master>
// @Data
// class MasterList {
//    List<Master> masterList;
// }

@SpringBootApplication
@EnableScheduling // 启用定时任务
@EnableTransactionManagement // 启用事务管理
public class ReadAndWriteJsonApplication {

    private final MasterService masterService; // 使用final修饰

    @Value("${json.file.path}") // 注入配置文件中的JSON文件路径
    private String jsonFilePath;

    // 构造器注入MasterService
    public ReadAndWriteJsonApplication(MasterService masterService) {
        this.masterService = masterService;
    }

    public static void main(String[] args) {
        SpringApplication.run(ReadAndWriteJsonApplication.class, args);
        // 移除TimerTaskUtil的调用,因为我们使用Spring的@Scheduled
    }

    @Scheduled(fixedRate = 90000) // 每90秒执行一次
    public void readFileAndSaveToDatabase() {
        ObjectMapper mapper = new ObjectMapper();
        TypeReference<List<Master>> typeReference = new TypeReference<List<Master>>() {};

        Path filePath = Paths.get(jsonFilePath); // 根据配置路径创建Path对象

        try (InputStream inputStream = Files.newInputStream(filePath)) { // 使用Files.newInputStream获取实时文件流
            List<Master> masters = mapper.readValue(inputStream, typeReference);
            System.out.println("成功读取到数据: " + masters);

            // 批量保存到数据库,Service层已添加@Transactional
            masterService.save(masters);
            System.out.println("数据已成功保存到数据库。");

        } catch (IOException e) {
            System.err.println("读取或保存JSON文件时发生错误: " + e.getMessage());
            // 生产环境中应使用日志框架记录错误,如SLF4J + Logback
        } catch (Exception e) {
            System.err.println("处理数据时发生未知错误: " + e.getMessage());
        }
    }
}
登录后复制

注意事项

  1. 文件路径的正确性:确保json.file.path配置的路径是正确的,并且Spring Boot应用拥有读取该路径下文件的权限。对于相对路径,它通常是相对于应用启动的目录。
  2. 文件存在性检查:在生产环境中,最好在读取文件前添加文件存在性检查,以避免不必要的文件未找到异常。
    Path filePath = Paths.get(jsonFilePath);
    if (!Files.exists(filePath) || !Files.isReadable(filePath)) {
        System.err.println("JSON文件不存在或不可读: " + jsonFilePath);
        return; // 提前退出
    }
    登录后复制
  3. 事务管理:在MasterService的save和saveAll方法上添加@Transactional注解,确保数据库操作的原子性。如果在保存过程中发生错误,所有更改将回滚。
  4. 错误处理和日志记录:在实际应用中,应使用专业的日志框架(如SLF4J配合Logback)来记录错误和信息,而不是简单的System.out.println或System.err.println。
  5. 资源管理:使用Java 7的try-with-resources语句可以确保InputStream在操作完成后被正确关闭,避免资源泄露。
  6. 文件写入与读取的同步:如果JSON文件是由同一个应用或另一个进程持续写入的,需要考虑写入和读取的同步问题。例如,写入方可以先写入一个临时文件,待写入完成后再原子性地替换原文件,以避免读取到不完整或损坏的文件。

总结

通过将动态更新的JSON文件视为外部文件,并使用java.nio.file包提供的API进行读取,我们成功解决了Spring Boot @Scheduled任务无法实时获取更新数据的问题。同时,结合构造器注入等Spring最佳实践,使得代码更加健壮、可维护。在处理这类场景时,理解类路径资源与文件系统文件的区别至关重要,这有助于避免常见的缓存问题,并确保应用能够高效、准确地处理实时变化的外部数据。

以上就是Spring Boot中动态读取和持久化外部JSON文件教程的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号