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

Spring Boot中Liquibase处理多对多数据加载及自定义Change问题

回答你的Liquibase多对多数据加载问题

首先明确说:Liquibase完全适合你这个场景。不管是处理简单的单表数据导入,还是多对多关联的三张表数据写入,Liquibase都提供了灵活的解决方案——从内置的loadData这类便捷操作,到支持自定义逻辑的customChange,都能覆盖你的需求。

一、为什么Liquibase适合这个场景?

  • 内置的loadData/loadUpdateData可以直接从CSV/JSON等文件加载数据到主表,操作简单,无需写代码。
  • 对于关联表(存储多对多ID的中间表),可以在主表数据加载完成后,用loadData或者sql标签直接插入关联关系,或者通过customChange实现更复杂的关联逻辑。
  • 所有操作都纳入Liquibase的版本控制,确保数据库变更的可追溯性和一致性,这在Spring Boot应用的团队协作中非常有用。

二、解决你遇到的CustomChange + Hibernate EntityManager问题

你尝试用customChange结合Hibernate EntityManager的思路没问题,但核心问题是Liquibase的自定义类默认无法直接访问Spring的上下文(包括EntityManagerFactory和你的实体类)。下面给你两种可行的解决方式:

方案1:让CustomChange类获取Spring上下文(推荐)

在Spring Boot中,你可以让自定义的CustomTaskChange类实现ApplicationContextAware,从而获取Spring容器中的EntityManagerFactory,进而操作实体类。

步骤如下:

  1. 创建自定义Change类,同时实现CustomTaskChange和ApplicationContextAware,并交给Spring管理(加@Component):
import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.exception.CustomChangeException;
import liquibase.resource.ResourceAccessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.io.InputStream;
import java.util.List;
// 导入你的实体类,比如Author、Book

@Component
public class ManyToManyDataLoader implements CustomTaskChange, ApplicationContextAware {

    private static ApplicationContext springContext;
    private ResourceAccessor resourceAccessor;

    @Override
    public void execute(Database database) throws CustomChangeException {
        // 从Spring上下文获取EntityManagerFactory
        EntityManagerFactory emf = springContext.getBean(EntityManagerFactory.class);
        EntityManager em = emf.createEntityManager();

        try {
            em.getTransaction().begin();

            // 1. 读取文件数据(比如JSON/CSV)
            InputStream dataStream = resourceAccessor.getResourceAsStream("classpath:data/many-to-many-data.json");
            // 这里写你的文件解析逻辑,比如用Jackson转成实体列表
            List<Author> authors = ...; // 解析后的作者列表
            List<Book> books = ...; // 解析后的书籍列表

            // 2. 保存主表数据并处理关联
            for (Author author : authors) {
                em.persist(author);
                // 假设Author实体里有books集合,已经关联好了对应的Book对象
            }

            em.getTransaction().commit();
        } catch (Exception e) {
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
            throw new CustomChangeException("Failed to load many-to-many data", e);
        } finally {
            em.close();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext context) {
        springContext = context;
    }

    // 实现CustomTaskChange的其他必填方法
    @Override
    public String getConfirmationMessage() {
        return "Many-to-many data loaded successfully";
    }

    @Override
    public void setUp() {}

    @Override
    public liquibase.exception.ValidationErrors validate(Database database) {
        return new liquibase.exception.ValidationErrors();
    }

    @Override
    public void setFileOpener(ResourceAccessor resourceAccessor) {
        this.resourceAccessor = resourceAccessor;
    }
}
  1. 在Liquibase的changelog文件中引用这个自定义类:
<changeSet id="load-many-to-many-data" author="your-name">
    <customChange class="com.yourpackage.ManyToManyDataLoader"/>
</changeSet>

方案2:避开Hibernate,直接用JDBC操作(更轻量)

如果你的数据处理逻辑不依赖Hibernate的实体关联,可以直接在customChange中使用Liquibase提供的Database对象获取JDBC连接,手动执行插入语句,这样不需要依赖Spring上下文:

import liquibase.change.custom.CustomTaskChange;
import liquibase.database.Database;
import liquibase.exception.CustomChangeException;
import liquibase.resource.ResourceAccessor;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.io.InputStream;
// 导入解析文件的工具类

public class JdbcDataLoader implements CustomTaskChange {

    private ResourceAccessor resourceAccessor;

    @Override
    public void execute(Database database) throws CustomChangeException {
        try (Connection conn = database.getConnection()) {
            // 读取文件数据
            InputStream dataStream = resourceAccessor.getResourceAsStream("classpath:data/author-book-links.csv");
            // 解析CSV得到关联ID对
            List<Object[]> links = ...;

            // 插入关联表
            String sql = "INSERT INTO author_book (author_id, book_id) VALUES (?, ?)";
            try (PreparedStatement stmt = conn.prepareStatement(sql)) {
                for (Object[] link : links) {
                    stmt.setLong(1, (Long) link[0]);
                    stmt.setLong(2, (Long) link[1]);
                    stmt.addBatch();
                }
                stmt.executeBatch();
            }
        } catch (Exception e) {
            throw new CustomChangeException("Failed to load data via JDBC", e);
        }
    }

    // 实现其他CustomTaskChange方法...
}

三、更简单的替代方案:用Liquibase内置标签

如果你的数据不需要复杂的业务逻辑处理,优先用Liquibase的loadData标签,这比写Java代码更高效:

  1. 准备三张表的CSV文件(比如authors.csv、books.csv、author_book.csv)
  2. 在changelog中依次加载:
<!-- 加载作者表 -->
<changeSet id="load-authors" author="your-name">
    <loadData tableName="author" file="classpath:data/authors.csv">
        <column name="id" type="NUMERIC"/>
        <column name="name" type="STRING"/>
    </loadData>
</changeSet>

<!-- 加载书籍表 -->
<changeSet id="load-books" author="your-name">
    <loadData tableName="book" file="classpath:data/books.csv">
        <column name="id" type="NUMERIC"/>
        <column name="title" type="STRING"/>
    </loadData>
</changeSet>

<!-- 加载关联表 -->
<changeSet id="load-author-book-links" author="your-name">
    <loadData tableName="author_book" file="classpath:data/author_book.csv">
        <column name="author_id" type="NUMERIC"/>
        <column name="book_id" type="NUMERIC"/>
    </loadData>
</changeSet>

这种方式无需写任何Java代码,适合数据格式规整的场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.21 07:37:19