Entity Log
Entity Log is a mechanism for tracking changes in your JPA entities. It records changes in entity attributes and provides a user interface for searching and displaying information about the changes:
-
What entity instance was changed.
-
Old and new values of changed attributes.
-
When the entity was changed.
-
Which user changed the entity.
Registering Changes
Entity Log automatically tracks changes of JPA entities when they are saved using DataManager or EntityManager. It doesn’t work if you use native SQL.
You can also use the EntityLog
bean to register changed entities from your application code. In this case, invoke registerCreate()
, registerModify()
and registerDelete()
methods with auto
parameter set to false. When the entity log is called automatically by the framework, this parameter is set to true.
Setting Up Entity Log
You can set up entity log in the Administration → Entity Log application screen.
Also, you can set up entity log by entering some records in the database if you want to include the configuration to the database initialization.
Logging is configured using the LoggedEntity
and LoggedAttribute
entities corresponding to AUDIT_LOGGED_ENTITY
and AUDIT_LOGGED_ATTR
tables.
LoggedEntity
defines the types of entities that should be logged. LoggedEntity
has the following attributes:
-
name
– the name of the entity meta-class, for example,sample_Order
. -
auto
– defines if the system should log the changes whenEntityLog
is called withauto
=true
parameter (called by entity listeners). -
manual
– defines if the system should log the changes whenEntityLog
is called with auto =false
parameter.
LoggedAttribute
defines the entity attribute to be logged and contains a link to the LoggedEntity
and the attribute name.
To set up logging for a certain entity, add the corresponding entries into the AUDIT_LOGGED_ENTITY
and AUDIT_LOGGED_ATTR
tables.
In the example, logging changes of the phone
attribute of the Customer
entity is set up while database initialization:
<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>
Viewing Entity Log
To view the entity log content, go to the Administration → Entity Log screen. Set necessary filters to find log entries.
Also, you can access the log for a certain entity from any application screen.
Log entries are stored in the AUDIT_ENTITY_LOG
table corresponding to the EntityLogItem
entity. Changed attribute values are stored in the CHANGES
column and are converted to instances of EntityLogAttr
entity.
In the example below, an edit screen for the Order
entity shows tables with the entity log content.
Here is a fragment of the screen XML descriptor:
<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="username"/>
<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 | Loading a collection of EntityLogItem into the entityLogItemsDc data container. |
2 | Loading associated EntityLogAttr instances into the entityLogAttrDc data container. |
3 | A table connected to the entityLogItemsDc container. |
4 | A table connected to the entityLogAttrDc container. |
Order
screen controller looks like this:
@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 | The onBeforeShow method loads data before showing the screen. |
2 | In the ItemChangeEvent handler of the orderDc container, a parameter is set to the dependent loader and it is triggered. |