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,进而操作实体类。
步骤如下:
- 创建自定义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; } }
- 在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代码更高效:
- 准备三张表的CSV文件(比如
authors.csv、books.csv、author_book.csv) - 在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
相关产品推荐
相关产品推荐

