英文:
Spring Boot - Hibernate Envers - Liquibase - @Audit aware <insert> statements possible?
问题
Our app historically populated our config
table entries via liquibase <insert>
or <update>
statements.
This config
table is a jpa entity and leverages hibernate-envers @Audit
functionality.
There is a corresponding config_audit
table as well.
In order to audit entries inserted/updated via liquibase I'm contemplating switching over to a <customChange>
changeset. e.g.
<customChange class="com.app.MyCustomTaskChange">
<param name="field1" value="val1" />
<param name="field2" value="val2" />
</customChange>
</changeSet>
@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));
}
//other methods excluded for clarity
To be clear, the above approach works fine, entries are added both to config
and config_audit
tables as expected; however, I was wondering if there is a more elegant and future-proof way of allowing the liquibase <insert>
or <update>
(or similar) statements to latch into the @Audit capabilities?
英文:
Our app historically populated our config
table entries via liquibase <insert>
or <update>
statements.
This config
table is a jpa entity and leverages hibernate-envers @Audit
functionality.
There is a corresponding config_audit
table as well.
In order to audit entries inserted/updated via liquibase I'm contemplating switching over to a <customChange>
changeset. e.g.
<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));
}
//other methods excluded for clarity
To be clear, the above approach works fine, entries are added both to config
and config_audit
tables as expected; however, I was wondering if there is a more elegant and future proof way of allowing the liquibase <insert>
or <update>
(or similar) statements to latch into the @Audit capabilities?
答案1
得分: 1
首先,我建议避免在数据库迁移中使用 spring
或 hibernate
的想法,因为它会使很多事情变得复杂,而且随着您修改领域模型,这些迁移很快就会变得过时,因此,sooner or later hibernate
可能无法将数据写入数据库。在您的情况下,由于 spring
在运行 liquibase 迁移之前初始化了实体管理器和存储库,所以您以某种方式能够将存储库实例“注入”到 CustomTaskChange
类中,但是在最近的 spring
版本中,spring-data-jpa
团队引入了 liquibase
和 hibernate
之间的强依赖关系,这种方法不应该起作用,但是可以像这样做(抱歉,无法记起如何启用 envers
):
public class HibernateCustomTaskChange implements CustomTaskChange {
@Override
public void execute(Database database) throws CustomChangeException {
EntityManagerFactory entityManagerFactory = null;
EntityManager entityManager = null;
try {
entityManagerFactory = getEntityManagerFactory(database, MyEntity.class);
entityManager = entityManagerFactory.createEntityManager();
// some logic here
} finally {
if (entityManager != null) {
entityManager.close();
}
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}
}
// 以下是您代码中的其他方法和类,我将原样保留
// ...
}
希望这能帮助您。如果您有任何其他问题,请随时提出。
英文:
First of all, I would recommend to stay clear of the idea using spring
or hibernate
in DB migrations, it complicates a lot of things regardless it's attractiveness, the main problem there is such migrations become outdated as soon as you modify domain model, and so, sooner or later hibernate
may fail to write data into DB. In your case you somehow were able to "inject" repository instance into CustomTaskChange
class just because spring
initialised entity manager and repositories before running liquibase migrations, however in recent spring
versions spring-data-jpa
team introduced strong dependency between liquibase
and hibernate
and such approach should not work, however that is possible to do something like (sorry, can't recall how to enable envers
):
public class HibernateCustomTaskChange implements CustomTaskChange {
@Override
public void execute(Database database) throws CustomChangeException {
EntityManagerFactory entityManagerFactory = null;
EntityManager entityManager = null;
try {
entityManagerFactory = getEntityManagerFactory(database, MyEntity.class);
entityManager = entityManagerFactory.createEntityManager();
// some logic here
} finally {
if (entityManager != null) {
entityManager.close();
}
if (entityManagerFactory != null) {
entityManagerFactory.close();
}
}
}
protected EntityManagerFactory getEntityManagerFactory(Database database, Class<?>... entityClasses) {
DataSource dataSource = createDataSource(database);
ClassLoader classLoader = Scope.getCurrentScope().getClassLoader();
MutablePersistenceUnitInfo pui = new MutablePersistenceUnitInfo() {
@Override
public ClassLoader getNewTempClassLoader() {
return classLoader;
}
};
pui.setNonJtaDataSource(dataSource);
for (Class<?> entityClass : entityClasses) {
pui.addManagedClassName(entityClass.getName());
}
Map<String, Object> settings = new HashMap<>();
settings.put(AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, true);
pui.setPersistenceUnitName("liquibase");
return new EntityManagerFactoryBuilderImpl(
new PersistenceUnitInfoDescriptor(pui),
settings,
classLoader
).build();
}
protected DataSource createDataSource(Database database) {
Connection connection = ((JdbcConnection) database.getConnection()).getUnderlyingConnection();
connection = createConnectionProxy(connection);
return new SingleConnectionDataSource(connection, true);
}
protected Connection createConnectionProxy(Connection con) {
return (Connection) Proxy.newProxyInstance(
ConnectionProxy.class.getClassLoader(),
new Class<?>[]{ConnectionProxy.class},
new CommitSuppressInvocationHandler(con));
}
static class CommitSuppressInvocationHandler implements InvocationHandler {
private final Connection target;
public CommitSuppressInvocationHandler(Connection target) {
this.target = target;
}
@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (ReflectionUtils.isEqualsMethod(method)) {
return (proxy == args[0]);
} else if (ReflectionUtils.isHashCodeMethod(method)) {
return System.identityHashCode(proxy);
} else if (method.getName().equals("unwrap")) {
if (((Class<?>) args[0]).isInstance(proxy)) {
return proxy;
}
} else if (method.getName().equals("isWrapperFor")) {
if (((Class<?>) args[0]).isInstance(proxy)) {
return true;
}
} else if (method.getName().equals("close")) {
return null;
} else if (method.getName().equals("commit")) {
return null;
} else if (method.getName().equals("isClosed")) {
return false;
} else if (method.getName().equals("getTargetConnection")) {
return this.target;
}
try {
return method.invoke(this.target, args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
}
}
}
// some liquibase method omitted
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论