Spring Boot – Hibernate Envers – Liquibase – @Audit aware statements possible?

huangapple go评论55阅读模式
英文:

Spring Boot - Hibernate Envers - Liquibase - @Audit aware <insert> statements possible?

问题

Our app historically populated our config table entries via liquibase &lt;insert&gt; or &lt;update&gt; 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 &lt;customChange&gt; changeset. e.g.

    &lt;customChange class=&quot;com.app.MyCustomTaskChange&quot;&gt;
        &lt;param name=&quot;field1&quot; value=&quot;val1&quot; /&gt;
        &lt;param name=&quot;field2&quot; value=&quot;val2&quot; /&gt;
    &lt;/customChange&gt;
&lt;/changeSet&gt;
    @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 &lt;insert&gt; or &lt;update&gt; (or similar) statements to latch into the @Audit capabilities?

英文:

Our app historically populated our config table entries via liquibase &lt;insert&gt; or &lt;update&gt; 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 &lt;customChange&gt; changeset. e.g.

    &lt;changeSet id=&quot;my_id&quot; author=&quot;author&quot;&gt;
        &lt;customChange class=&quot;com.app.MyCustomTaskChange&quot;&gt;
            &lt;param name=&quot;field1&quot; value=&quot;val1&quot; /&gt;
            &lt;param name=&quot;field2&quot; value=&quot;val2&quot; /&gt;
        &lt;/customChange&gt;
    &lt;/changeSet&gt;
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 &lt;insert&gt; or &lt;update&gt; (or similar) statements to latch into the @Audit capabilities?

答案1

得分: 1

首先,我建议避免在数据库迁移中使用 springhibernate 的想法,因为它会使很多事情变得复杂,而且随着您修改领域模型,这些迁移很快就会变得过时,因此,sooner or later hibernate 可能无法将数据写入数据库。在您的情况下,由于 spring 在运行 liquibase 迁移之前初始化了实体管理器和存储库,所以您以某种方式能够将存储库实例“注入”到 CustomTaskChange 类中,但是在最近的 spring 版本中,spring-data-jpa 团队引入了 liquibasehibernate 之间的强依赖关系,这种方法不应该起作用,但是可以像这样做(抱歉,无法记起如何启用 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&lt;?&gt;... 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&lt;?&gt; entityClass : entityClasses) {
            pui.addManagedClassName(entityClass.getName());
        }
        Map&lt;String, Object&gt; settings = new HashMap&lt;&gt;();
        settings.put(AvailableSettings.CONNECTION_PROVIDER_DISABLES_AUTOCOMMIT, true);
        pui.setPersistenceUnitName(&quot;liquibase&quot;);
        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&lt;?&gt;[]{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(&quot;unwrap&quot;)) {
                if (((Class&lt;?&gt;) args[0]).isInstance(proxy)) {
                    return proxy;
                }
            } else if (method.getName().equals(&quot;isWrapperFor&quot;)) {
                if (((Class&lt;?&gt;) args[0]).isInstance(proxy)) {
                    return true;
                }
            } else if (method.getName().equals(&quot;close&quot;)) {
                return null;
            } else if (method.getName().equals(&quot;commit&quot;)) {
                return null;
            } else if (method.getName().equals(&quot;isClosed&quot;)) {
                return false;
            } else if (method.getName().equals(&quot;getTargetConnection&quot;)) {
                return this.target;
            }

            try {
                return method.invoke(this.target, args);
            } catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }

    }

    // some liquibase method omitted

}

huangapple
  • 本文由 发表于 2023年5月11日 03:38:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76222046.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定