You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring+MySQL从文本加载数据时建立艺术家与艺术品的实体关系

解决方案:修正实体关联+分阶段加载关联数据

看起来你已经完成了基础的解析和持久化工作,核心问题出在实体关联映射的定义和数据加载的顺序/关联逻辑上,我给你梳理一套可行的实现方案:

一、先修正JPA实体的一对多关联映射

你的Artwork类当前用String存储artist,这只是存储了艺术家的标识,没法建立JPA的对象级关联。需要调整实体类,定义正确的双向一对多关系:

1. Artist实体修改

添加@OneToMany关联到Artwork,并维护双向关联的方法:

@Entity
@Table(name = "artists")
public class Artist {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, unique = true) // 确保艺术家名字唯一,避免重复插入
    private String name;

    private LocalDate dob;
    private String sex;
    private String country;

    // 双向关联:一个艺术家对应多个艺术品,mappedBy指定由Artwork的artist字段维护外键
    @OneToMany(mappedBy = "artist", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Artwork> artworks = new ArrayList<>();

    // 构造器(无参+全参)、getter/setter省略

    // 维护双向关联的辅助方法
    public void addArtwork(Artwork artwork) {
        this.artworks.add(artwork);
        if (artwork.getArtist() != this) {
            artwork.setArtist(this);
        }
    }
}

2. Artwork实体修改

将String类型的artist替换为@ManyToOne关联到Artist实体:

@Entity
@Table(name = "artworks")
public class Artwork {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 多对一关联:一个艺术品属于一个艺术家
    @ManyToOne(fetch = FetchType.LAZY) // 懒加载优化性能
    @JoinColumn(name = "artist_id", nullable = false) // 指定外键列名
    private Artist artist;

    private String title;
    private Integer year;
    private String country;

    // 构造器、getter/setter省略

    // 维护双向关联的setter方法
    public void setArtist(Artist artist) {
        this.artist = artist;
        if (!artist.getArtworks().contains(this)) {
            artist.addArtwork(this);
        }
    }
}

二、调整DataLoader的加载逻辑(分两个阶段)

核心思路是先加载所有艺术家并缓存到内存,再加载艺术品时直接关联缓存中的艺术家对象,避免重复查询数据库,同时保证关联正确:

代码示例(假设使用Spring Data JPA)

@Component
public class DataLoader {
    private final ArtistRepository artistRepository;
    private final ArtworkRepository artworkRepository;
    private static final String FILE_PATH = "path/to/your/data.txt";

    // 构造注入Repository
    public DataLoader(ArtistRepository artistRepository, ArtworkRepository artworkRepository) {
        this.artistRepository = artistRepository;
        this.artworkRepository = artworkRepository;
    }

    @PostConstruct // Spring启动时自动执行数据加载
    public void loadData() {
        // 阶段1:加载所有艺术家,存入内存Map(键为艺术家名字,值为持久化后的Artist对象)
        Map<String, Artist> artistMap = new HashMap<>();

        try (Stream<String> lines = Files.lines(Paths.get(FILE_PATH))) {
            // 先处理所有艺术家条目
            lines.filter(line -> line.startsWith("A|"))
                 .forEach(line -> {
                     String[] parts = line.split("\\|"); // 注意|是正则特殊字符,需要转义
                     // 处理分割后的空格(比如" A| Van Gogh| ...")
                     String name = parts[1].trim();
                     LocalDate dob = LocalDate.parse(parts[2].trim());
                     String sex = parts[3].trim();
                     String country = parts[4].trim();

                     // 先查询是否已存在该艺术家(避免重复插入)
                     Artist artist = artistRepository.findByName(name)
                                                     .orElseGet(() -> {
                                                         Artist newArtist = new Artist();
                                                         newArtist.setName(name);
                                                         newArtist.setDob(dob);
                                                         newArtist.setSex(sex);
                                                         newArtist.setCountry(country);
                                                         return artistRepository.save(newArtist);
                                                     });
                     artistMap.put(name, artist);
                 });

            // 阶段2:重新读取文件处理艺术品条目
            Files.lines(Paths.get(FILE_PATH))
                 .filter(line -> line.startsWith("W|"))
                 .forEach(line -> {
                     String[] parts = line.split("\\|");
                     String artistName = parts[1].trim();
                     String title = parts[2].trim();
                     Integer year = Integer.parseInt(parts[3].trim());
                     String country = parts[4].trim();

                     // 从缓存Map获取艺术家
                     Artist artist = artistMap.get(artistName);
                     if (artist != null) {
                         Artwork artwork = new Artwork();
                         artwork.setArtist(artist);
                         artwork.setTitle(title);
                         artwork.setYear(year);
                         artwork.setCountry(country);
                         artworkRepository.save(artwork);
                     } else {
                         // 处理异常情况:记录日志或跳过
                         System.err.printf("Warning: Artist '%s' not found, skipping artwork '%s'%n", artistName, title);
                     }
                 });
        } catch (IOException | DateTimeParseException | NumberFormatException e) {
            e.printStackTrace();
            throw new RuntimeException("Failed to load data from file", e);
        }
    }
}

三、关键注意事项

  • 数据唯一性:通过@Column(unique = true)确保艺术家名字唯一,加载时先查询再创建,处理文件中重复的艺术家条目。
  • 双向关联维护:通过addArtwork和setArtist方法保证双向关联的一致性,避免出现“艺术家有艺术品列表,但艺术品的artist为null”的情况。
  • 性能优化:使用内存Map缓存艺术家,避免每次加载艺术品都查询数据库;对于大数据量,建议使用批量保存(比如artworkRepository.saveAll(List<Artwork>)),并配置JPA的批量插入属性(比如spring.jpa.properties.hibernate.jdbc.batch_size=50)。
  • 异常处理:添加了对文件IO、日期解析、数字转换的异常捕获,避免程序崩溃,同时记录错误信息便于排查。

内容的提问来源于stack exchange,提问作者roboticcrow

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 07:26:24