实体日志

实体日志是一种跟踪 JPA 实体变更的机制。它记录实体属性的改动并提供用户界面用于查找和展示变更的信息:

  • 变更了什么实体实例。

  • 属性变更前后的旧值和新值。

  • 修改实体的时间。

  • 修改实体的用户。

变更注册

如果 JPA 实体是通过 DataManagerEntityManager 保存,则实体日志会自动跟踪实体的变更。但如果使用原生 SQL,则实体日志不会起作用。

你也可以使用 EntityLog bean 在应用程序代码中注册变更的实体:手动调用 registerCreate()registerModify()registerDelete() 方法并设置 auto 参数为 false。当框架自动调用实体日志时,该参数设置为 true。

配置实体日志

你可以在应用程序的 Administration → Entity Log 界面中配置实体日志。

entity log set

另外,如果你想要将实体日志的配置包含在数据库初始化脚本中,则能用插入数据库记录的方式进行配置。

日志是用 LoggedEntityLoggedAttribute 实体配置,对应数据库的 AUDIT_LOGGED_ENTITYAUDIT_LOGGED_ATTR 表。

LoggedEntity 定义需要记录日志的实体类型,具有如下属性:

  • name - 实体元类的名称,例如,sample_Order

  • auto - 定义当 EntityLog 带有 auto = true 参数调用时(即,由实体监听器调用时),系统是否需要记录变更。

  • manual - 定义当 EntityLog 带有 auto = false 参数调用时,系统是否需要记录变更。

LoggedAttribute 定义需要记录的实体属性,带有属性名,并包含至 LoggedEntity 的连接。

如需为特定实体配置日志,需要在 AUDIT_LOGGED_ENTITYAUDIT_LOGGED_ATTR 表添加相应的记录。

示例,在数据库初始化时,记录 Customer 实体 phone 属性的变更:

<changeSet id="1" author="ex1">
    <insert tableName="AUDIT_LOGGED_ENTITY">
        <column name="ID" value="6c9e420a-2b7a-4c42-8654-a9027ee14083"/>
        <column name="CREATED_BY" value="admin"/>
        <column name="CREATE_TS" valueDate="2021-01-01T00:00:00"/>
        <column name="NAME" value="ex1_Customer"/>
        <column name="AUTO" value="true"/>
        <column name="MANUAL" value="true"/>
    </insert>
</changeSet>
<changeSet id="2" author="ex1">
    <insert tableName="AUDIT_LOGGED_ATTR">
        <column name="ID" value="52a0126a-65bb-11eb-ae93-0242ac130002"/>
        <column name="CREATE_TS" valueDate="2021-01-01T00:00:00"/>
        <column name="CREATED_BY" value="admin"/>
        <column name="ENTITY_ID" value="6c9e420a-2b7a-4c42-8654-a9027ee14083"/>
        <column name="NAME" value="phone"/>
    </insert>
</changeSet>

查看实体日志

打开 Administration → Entity Log 界面即可查看实体日志。可以在过滤器中设置必要的条件查找日志记录。

entity log view

另外,你也可以从任何应用程序界面访问特定实体的日志。

日志记录保存在 AUDIT_ENTITY_LOG 表,对应 EntityLogItem 实体。修改的属性值保存在 CHANGES 列,Java 中转换成 EntityLogAttr 实体实例。

在下面的示例中,Order 实体的编辑界面展示实体日志内容的表格。

这是界面 XML 的部分内容:

<data>
    <instance id="orderDc"
              class="audit.ex1.entity.Order">
        <fetchPlan extends="_local"/>
        <loader id="orderDl"/>
    </instance>
    <collection id="entityLogItemsDc"
                class="io.jmix.audit.entity.EntityLogItem"> (1)
        <fetchPlan extends="_local"/>
        <loader id="entityLogItemsDl">
            <query>
                <![CDATA[select e from audit_EntityLog e
                        where e.entityRef.entityId = :order
                        order by e.eventTs]]>
            </query>
        </loader>
        <collection id="entityLogAttrDc" property="attributes"/> (2)
    </collection>
</data>
<facets>
    <dataLoadCoordinator auto="true"/>
</facets>
<dialogMode height="600"
            width="800"/>
<layout spacing="true" expand="editActions">
    <vbox spacing="true">
        <form id="form" dataContainer="orderDc">
            <column width="350px">
                <dateField id="dateField" property="date"/>
                <textField id="productField" property="product"/>
                <textField id="amountField" property="amount"/>
            </column>
        </form>
        <hbox spacing="true">
            <table id="logTable"
                   width="100%"
                   height="100%"
                   dataContainer="entityLogItemsDc"> (3)
                <columns>
                    <column id="eventTs"/>
                    <column id="userLogin"/>
                    <column id="type"/>
                </columns>
            </table>
            <table id="attrTable"
                   height="100%"
                   width="100%"
                   dataContainer="entityLogAttrDc"> (4)
                <columns>
                    <column id="name"/>
                    <column id="oldValue"/>
                    <column id="value"/>
                </columns>
            </table>
        </hbox>
    </vbox>
    <hbox id="editActions" spacing="true">
        <button id="commitAndCloseBtn" action="windowCommitAndClose"/>
        <button id="closeBtn" action="windowClose"/>
    </hbox>
</layout>
1 加载 EntityLogItem 集合至 entityLogItemsDc 数据容器。
2 加载相关的 EntityLogAttr 示例至 entityLogAttrDc 数据容器。
3 连接至 entityLogItemsDc 容器的表格。
4 连接至 entityLogAttrDc 容器的表格。

Order 界面控制器代码如下:

@Autowired
private InstanceLoader<Order> orderDl;
@Autowired
private CollectionLoader<EntityLogItem> entityLogItemsDl;

@Subscribe
public void onBeforeShow(BeforeShowEvent event) { (1)
    orderDl.load();
}

@Subscribe(id = "orderDc", target = Target.DATA_CONTAINER)
public void onOrderDcItemChange(InstanceContainer.ItemChangeEvent<Order> event) { (2)
    entityLogItemsDl.setParameter("order", event.getItem().getId());
    entityLogItemsDl.load();
}
1 onBeforeShow 方法在界面展示之前加载数据。
2 orderDc 数据容器的 ItemChangeEvent 处理器内,为依赖的数据加载器设置参数,并加载数据。