Spring Boot中能否让Liquibase的<insert>语句支持Hibernate Envers的@Audit?
我们的应用此前通过Liquibase的<insert>或<update>语句填充config表,该表是JPA实体并采用Hibernate Envers的@Audit审计功能,对应存在config_audit表。
为了让Liquibase的插入/更新操作也能触发审计,我们切换为<customChange>变更集方案,示例代码如下:
<changeSet id="my_id" author="author"> <customChange class="com.app.MyCustomTaskChange"> <param name="field1" value="val1" /> <param name="field2" value="val2" /> </customChange> </changeSet>
public class MyCustomTaskChange implements CustomTaskChange, CustomTaskRollback { @Setter private String field1; @Setter private String field2; @Setter private static ConfigJpaRepository configJpaRepository; @Override public void execute(Database database) throws CustomChangeException { configJpaRepository.save(new ConfigEntity(field1, field2)); } // 其他方法省略以简化展示 }
目前该方案运行正常,config和config_audit表均能正常添加条目,但我们想了解是否有更优雅且具前瞻性的方式,让Liquibase的<insert>或<update>(或类似)语句直接适配@Audit审计能力?
1. 通用JPA变更类型(推荐)
自定义一个通用的Liquibase变更类型,通过JPA EntityManager操作实体,自动触发Envers审计逻辑,避免为每个实体单独编写CustomTaskChange:
public class JpaEntityChange extends AbstractChange { private String entityClassName; private Map<String, Object> parameters = new HashMap<>(); // 从Spring上下文获取EntityManager(需确保Liquibase运行在Spring环境中) private EntityManager getEntityManager() { return ApplicationContextProvider.getApplicationContext().getBean(EntityManager.class); } @Override public void execute(Database database) throws CustomChangeException { EntityManager em = getEntityManager(); try { Class<?> entityClass = Class.forName(entityClassName); Object entity = entityClass.getDeclaredConstructor().newInstance(); // 通过反射设置实体字段值 for (Map.Entry<String, Object> entry : parameters.entrySet()) { BeanUtils.setProperty(entity, entry.getKey(), entry.getValue()); } em.persist(entity); em.flush(); } catch (Exception e) { throw new CustomChangeException("JPA实体操作失败", e); } } // 省略必要的getter/setter、getConfirmationMessage、getChangeMetaData等方法 }
在Liquibase配置中使用该通用变更:
<changeSet id="insert-config-jpa" author="author"> <customChange class="com.app.JpaEntityChange"> <param name="entityClassName" value="com.app.ConfigEntity"/> <param name="field1" value="val1"/> <param name="field2" value="val2"/> </customChange> </changeSet>
这种方式的优势是通用性强,所有带@Audit的实体都可以复用该变更类型,无需重复编写自定义任务,维护成本更低。
2. 结合Spring Bean调用(贴合Spring生态)
如果是Spring Boot项目,直接利用Liquibase的Spring集成能力,调用Spring管理的业务Bean方法,通过业务逻辑触发JPA保存和审计:
首先定义业务服务:
@Service public class ConfigService { private final ConfigJpaRepository configRepo; public ConfigService(ConfigJpaRepository configRepo) { this.configRepo = configRepo; } public void saveConfig(String field1, String field2) { configRepo.save(new ConfigEntity(field1, field2)); } }
然后在Liquibase变更集中调用该Bean:
<changeSet id="insert-config-spring" author="author"> <customChange class="org.springframework.boot.liquibase.SpringLiquibaseCustomChange"> <param name="beanName" value="configService"/> <param name="methodName" value="saveConfig"/> <param name="arguments" value="val1,val2"/> </customChange> </changeSet>
这种方式避免了静态依赖注入的问题,完全贴合Spring的依赖注入原则,适合已经深度使用Spring生态的项目。
3. 手动插入审计表(不推荐长期使用)
如果只是临时需求,可以在Liquibase的<insert>/<update>后手动插入审计表记录,但这种方式耦合度极高,实体字段变更后必须同步修改审计表的插入语句:
<changeSet id="insert-config-with-audit" author="author"> <insert tableName="config"> <column name="id" value="1"/> <column name="field1" value="val1"/> <column name="field2" value="val2"/> </insert> <insert tableName="config_audit"> <column name="id" value="1"/> <column name="field1" value="val1"/> <column name="field2" value="val2"/> <column name="rev" value="${next-rev-id}"/> <!-- 需要确保revision ID正确 --> <column name="revtype" value="0"/> <!-- 0=插入,1=更新,2=删除 --> </insert> </changeSet>
你当前的<customChange>方案是可行的,但通用JPA变更类型和Spring Bean调用的方式更优雅、更具前瞻性:
- 通用JPA变更类型适合多实体场景,减少重复代码;
- Spring Bean调用更贴合Spring生态,依赖管理更规范;
- 手动插入审计表的方式仅适合临时快速实现,长期维护成本高。
内容的提问来源于stack exchange,提问作者mjj1409

