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 Audit → Entity Log view.

entity log set

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 when EntityLog is called with auto = true parameter (called by entity listeners).

  • manual – defines if the system should log the changes when EntityLog 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 Audit → Entity Log view. Set necessary filters to find log entries.

entity log view

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.