实体日志
实体日志是一种跟踪 JPA 实体变更的机制。它记录实体属性的改动并提供用户界面用于查找和展示变更的信息:
-
变更了什么实体实例。
-
属性变更前后的旧值和新值。
-
修改实体的时间。
-
修改实体的用户。
变更注册
如果 JPA 实体是通过 DataManager 或 EntityManager 保存,则实体日志会自动跟踪实体的变更。但如果使用原生 SQL,则实体日志不会起作用。
你也可以使用 EntityLog
bean 在应用程序代码中注册变更的实体:手动调用 registerCreate()
、registerModify()
和 registerDelete()
方法并设置 auto
参数为 false。当框架自动调用实体日志时,该参数设置为 true。
配置实体日志
你可以在应用程序的 Administration → Entity Log 界面中配置实体日志。
另外,如果你想要将实体日志的配置包含在数据库初始化脚本中,则能用插入数据库记录的方式进行配置。
日志是用 LoggedEntity
和 LoggedAttribute
实体配置,对应数据库的 AUDIT_LOGGED_ENTITY
和 AUDIT_LOGGED_ATTR
表。
LoggedEntity
定义需要记录日志的实体类型,具有如下属性:
-
name
- 实体元类的名称,例如,sample_Order
。 -
auto
- 定义当EntityLog
带有auto
=true
参数调用时(即,由实体监听器调用时),系统是否需要记录变更。 -
manual
- 定义当EntityLog
带有auto
=false
参数调用时,系统是否需要记录变更。
LoggedAttribute
定义需要记录的实体属性,带有属性名,并包含至 LoggedEntity
的连接。
如需为特定实体配置日志,需要在 AUDIT_LOGGED_ENTITY
和 AUDIT_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 界面即可查看实体日志。可以在过滤器中设置必要的条件查找日志记录。
另外,你也可以从任何应用程序界面访问特定实体的日志。
日志记录保存在 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 处理器内,为依赖的数据加载器设置参数,并加载数据。 |