You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/04/16 11:00:53 UTC

[isis] branch master updated: ISIS-3010: CommandJpa fixes; now managed by EclipseLink

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new c1cc1ceec6 ISIS-3010: CommandJpa fixes; now managed by EclipseLink
c1cc1ceec6 is described below

commit c1cc1ceec62a875870689b2670a82f00bffc32f1
Author: andi-huber <ah...@apache.org>
AuthorDate: Sat Apr 16 13:00:49 2022 +0200

    ISIS-3010: CommandJpa fixes; now managed by EclipseLink
---
 .../demo/domain/src/main/resources/application.yml |  2 +-
 .../commandlog/applib/command/CommandLog.java      |  4 +-
 .../applib/command/ui/CommandLogServiceMenu.java}  | 45 +++++-----
 .../commandlog/jdo/IsisModuleExtCommandLogJdo.java |  4 +-
 .../jdo/entities/CommandJdoRepository.java         |  2 +-
 .../commandlog/jpa/IsisModuleExtCommandLogJpa.java | 19 +++--
 .../commandlog/jpa/entities/CommandJpa.java        | 97 +++++++++++-----------
 .../jpa/entities/CommandJpaRepository.java         |  7 +-
 .../resources/META-INF/orm-commandlog.template     | 32 +++++++
 9 files changed, 123 insertions(+), 89 deletions(-)

diff --git a/examples/demo/domain/src/main/resources/application.yml b/examples/demo/domain/src/main/resources/application.yml
index 84aaa68177..d9f183e594 100644
--- a/examples/demo/domain/src/main/resources/application.yml
+++ b/examples/demo/domain/src/main/resources/application.yml
@@ -107,7 +107,7 @@ isis:
 # schema auto creation etc. ...
   persistence:
     schema:
-       autoCreateSchemas: isisExtensionsSecman,demo
+       autoCreateSchemas: isisExtensionsSecman,isisExtensionsCommandLog,demo
 
   extensions:
     secman:
diff --git a/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/CommandLog.java b/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/CommandLog.java
index c48f42b061..30255bddf1 100644
--- a/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/CommandLog.java
+++ b/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/CommandLog.java
@@ -29,7 +29,6 @@ import java.util.UUID;
 import java.util.function.Consumer;
 
 import javax.persistence.Column;
-import javax.persistence.MappedSuperclass;
 import javax.persistence.Transient;
 
 import org.springframework.context.event.EventListener;
@@ -75,7 +74,6 @@ import lombok.val;
  * Note that this class doesn't subclass from {@link Command} ({@link Command}
  * is not an interface).
  */
-@MappedSuperclass
 @DomainObject(
         logicalTypeName = CommandLog.LOGICAL_TYPE_NAME,
         editing = Editing.DISABLED
@@ -256,7 +254,7 @@ implements
     public static class ParentDomainEvent extends PropertyDomainEvent<Command> { }
     @Property(domainEvent = ParentDomainEvent.class)
     @PropertyLayout(hidden = Where.ALL_TABLES)
-    public abstract CommandLog getParent();
+    public abstract <C extends CommandLog> C getParent();
     public abstract void setParent(CommandLog parent);
 
     public static class TargetDomainEvent extends PropertyDomainEvent<String> { }
diff --git a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/ui/CommandServiceMenu.java b/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/ui/CommandLogServiceMenu.java
similarity index 79%
rename from extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/ui/CommandServiceMenu.java
rename to extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/ui/CommandLogServiceMenu.java
index e17a0ee6ec..32a8183fcc 100644
--- a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/ui/CommandServiceMenu.java
+++ b/extensions/core/command-log/applib/src/main/java/org/apache/isis/extensions/commandlog/applib/command/ui/CommandLogServiceMenu.java
@@ -16,7 +16,7 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-package org.apache.isis.extensions.commandlog.jdo.ui;
+package org.apache.isis.extensions.commandlog.applib.command.ui;
 
 import java.time.LocalDate;
 import java.time.ZoneId;
@@ -43,16 +43,15 @@ import org.apache.isis.applib.annotation.RestrictTo;
 import org.apache.isis.applib.annotation.SemanticsOf;
 import org.apache.isis.applib.services.clock.ClockService;
 import org.apache.isis.extensions.commandlog.applib.IsisModuleExtCommandLogApplib;
-import org.apache.isis.extensions.commandlog.jdo.IsisModuleExtCommandLogJdo;
-import org.apache.isis.extensions.commandlog.jdo.entities.CommandJdo;
-import org.apache.isis.extensions.commandlog.jdo.entities.CommandJdoRepository;
+import org.apache.isis.extensions.commandlog.applib.command.CommandLog;
+import org.apache.isis.extensions.commandlog.applib.command.ICommandLogRepository;
 
 import lombok.RequiredArgsConstructor;
 
 /**
  * @since 2.0 {@index}
  */
-@Named(IsisModuleExtCommandLogJdo.NAMESPACE + ".CommandServiceMenu")
+@Named(IsisModuleExtCommandLogApplib.NAMESPACE + ".CommandLogServiceMenu")
 @DomainService(
     nature = NatureOfService.VIEW
 )
@@ -63,44 +62,46 @@ import lombok.RequiredArgsConstructor;
 @javax.annotation.Priority(PriorityPrecedence.EARLY)
 @Qualifier("Jdo")
 @RequiredArgsConstructor(onConstructor_ = { @Inject })
-public class CommandServiceMenu {
+public class CommandLogServiceMenu {
 
     public static abstract class PropertyDomainEvent<T>
-            extends IsisModuleExtCommandLogApplib.PropertyDomainEvent<CommandServiceMenu, T> { }
+            extends IsisModuleExtCommandLogApplib.PropertyDomainEvent<CommandLogServiceMenu, T> { }
     public static abstract class CollectionDomainEvent<T>
-            extends IsisModuleExtCommandLogApplib.CollectionDomainEvent<CommandServiceMenu, T> { }
+            extends IsisModuleExtCommandLogApplib.CollectionDomainEvent<CommandLogServiceMenu, T> { }
     public static abstract class ActionDomainEvent
-            extends IsisModuleExtCommandLogApplib.ActionDomainEvent<CommandServiceMenu> {
+            extends IsisModuleExtCommandLogApplib.ActionDomainEvent<CommandLogServiceMenu> {
     }
 
-    final CommandJdoRepository commandServiceRepository;
+    final ICommandLogRepository<? extends CommandLog> commandLogRepository;
     final ClockService clockService;
 
     public static class ActiveCommandsDomainEvent extends ActionDomainEvent { }
-    @Action(domainEvent = ActiveCommandsDomainEvent.class, semantics = SemanticsOf.SAFE)
+    @Action(domainEvent = ActiveCommandsDomainEvent.class, semantics = SemanticsOf.SAFE,
+            typeOf = CommandLog.class)
     @ActionLayout(bookmarking = BookmarkPolicy.AS_ROOT, cssClassFa = "fa-bolt", sequence="10")
-    public List<CommandJdo> activeCommands() {
-        return commandServiceRepository.findCurrent();
+    public List<? extends CommandLog> activeCommands() {
+        return commandLogRepository.findCurrent();
     }
     @MemberSupport public boolean hideActiveCommands() {
-        return commandServiceRepository == null;
+        return commandLogRepository == null;
     }
 
 
     public static class FindCommandsDomainEvent extends ActionDomainEvent { }
-    @Action(domainEvent = FindCommandsDomainEvent.class, semantics = SemanticsOf.SAFE)
+    @Action(domainEvent = FindCommandsDomainEvent.class, semantics = SemanticsOf.SAFE,
+            typeOf = CommandLog.class)
     @ActionLayout(cssClassFa = "fa-search", sequence="20")
-    public List<CommandJdo> findCommands(
+    public List<? extends CommandLog> findCommands(
             @Parameter(optionality= Optionality.OPTIONAL)
             @ParameterLayout(named="From")
             final LocalDate from,
             @Parameter(optionality= Optionality.OPTIONAL)
             @ParameterLayout(named="To")
             final LocalDate to) {
-        return commandServiceRepository.findByFromAndTo(from, to);
+        return commandLogRepository.findByFromAndTo(from, to);
     }
     @MemberSupport public boolean hideFindCommands() {
-        return commandServiceRepository == null;
+        return commandLogRepository == null;
     }
     @MemberSupport public LocalDate default0FindCommands() {
         return now().minusDays(7);
@@ -113,13 +114,13 @@ public class CommandServiceMenu {
     public static class FindCommandByIdDomainEvent extends ActionDomainEvent { }
     @Action(domainEvent = FindCommandByIdDomainEvent.class, semantics = SemanticsOf.SAFE)
     @ActionLayout(cssClassFa = "fa-crosshairs", sequence="30")
-    public CommandJdo findCommandById(
+    public CommandLog findCommandById(
             @ParameterLayout(named="Transaction Id")
             final UUID transactionId) {
-        return commandServiceRepository.findByInteractionId(transactionId).orElse(null);
+        return commandLogRepository.findByInteractionId(transactionId).orElse(null);
     }
     @MemberSupport public boolean hideFindCommandById() {
-        return commandServiceRepository == null;
+        return commandLogRepository == null;
     }
 
 
@@ -127,7 +128,7 @@ public class CommandServiceMenu {
     @Action(domainEvent = TruncateLogDomainEvent.class, semantics = SemanticsOf.IDEMPOTENT_ARE_YOU_SURE, restrictTo = RestrictTo.PROTOTYPING)
     @ActionLayout(cssClassFa = "fa-trash", sequence="40")
     public void truncateLog() {
-        commandServiceRepository.truncateLog();
+        commandLogRepository.truncateLog();
     }
 
 
diff --git a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogJdo.java b/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogJdo.java
index e96c2845b8..ff0cf14343 100644
--- a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogJdo.java
+++ b/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/IsisModuleExtCommandLogJdo.java
@@ -25,9 +25,9 @@ import org.springframework.context.annotation.Import;
 import org.apache.isis.extensions.commandlog.applib.command.CommandLog;
 import org.apache.isis.extensions.commandlog.applib.command.ICommandLog;
 import org.apache.isis.extensions.commandlog.applib.command.subscriber.CommandSubscriberForCommandLog;
+import org.apache.isis.extensions.commandlog.applib.command.ui.CommandLogServiceMenu;
 import org.apache.isis.extensions.commandlog.jdo.entities.CommandJdo;
 import org.apache.isis.extensions.commandlog.jdo.entities.CommandJdoRepository;
-import org.apache.isis.extensions.commandlog.jdo.ui.CommandServiceMenu;
 import org.apache.isis.testing.fixtures.applib.fixturescripts.FixtureScript;
 import org.apache.isis.testing.fixtures.applib.teardown.jdo.TeardownFixtureJdoAbstract;
 
@@ -37,7 +37,7 @@ import org.apache.isis.testing.fixtures.applib.teardown.jdo.TeardownFixtureJdoAb
 @Configuration
 @Import({
         // @DomainService's
-        CommandServiceMenu.class,
+        CommandLogServiceMenu.class,
 
         // @Service's
         CommandJdoRepository.class,
diff --git a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/entities/CommandJdoRepository.java b/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/entities/CommandJdoRepository.java
index c335a2c8b1..5096f51b67 100644
--- a/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/entities/CommandJdoRepository.java
+++ b/extensions/core/command-log/jdo/src/main/java/org/apache/isis/extensions/commandlog/jdo/entities/CommandJdoRepository.java
@@ -259,7 +259,7 @@ implements ICommandLogRepository<CommandJdo> {
     @Override
     public List<CommandJdo> findNotYetReplayed() {
         return repositoryService().allMatches(
-                Query.named(CommandJdo.class, "findNotYetReplayed"));
+                Query.named(CommandJdo.class, "findNotYetReplayed").withLimit(10));
     }
 
     @Override
diff --git a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogJpa.java b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogJpa.java
index 20195d0fe7..0abb185c9d 100644
--- a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogJpa.java
+++ b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/IsisModuleExtCommandLogJpa.java
@@ -18,12 +18,14 @@
  */
 package org.apache.isis.extensions.commandlog.jpa;
 
-import org.springframework.context.annotation.ComponentScan;
+import org.springframework.boot.autoconfigure.domain.EntityScan;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
 
 import org.apache.isis.extensions.commandlog.applib.command.CommandLog;
 import org.apache.isis.extensions.commandlog.applib.command.subscriber.CommandSubscriberForCommandLog;
+import org.apache.isis.extensions.commandlog.applib.command.ui.CommandLogServiceMenu;
+import org.apache.isis.extensions.commandlog.jpa.entities.CommandJpa;
 import org.apache.isis.extensions.commandlog.jpa.entities.CommandJpaRepository;
 
 /**
@@ -32,19 +34,20 @@ import org.apache.isis.extensions.commandlog.jpa.entities.CommandJpaRepository;
 @Configuration
 @Import({
         // @DomainService's
-        CommandJpaRepository.class,
-
-//TODO        , CommandServiceMenu.class
+        CommandLogServiceMenu.class,
 
         // @Service's
+        CommandJpaRepository.class,
         CommandLog.TableColumnOrderDefault.class,
         CommandSubscriberForCommandLog.class,
 
+        // entities
+        CommandJpa.class
+
+})
+@EntityScan(basePackageClasses = {
+        CommandJpa.class,
 })
-@ComponentScan(
-        basePackageClasses= {
-                IsisModuleExtCommandLogJpa.class
-        })
 public class IsisModuleExtCommandLogJpa {
 
     public static final String NAMESPACE = "isis.ext.commandLog";
diff --git a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpa.java b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpa.java
index 1384f59aba..fdad490d96 100644
--- a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpa.java
+++ b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpa.java
@@ -23,10 +23,13 @@ import java.sql.Timestamp;
 import javax.persistence.Basic;
 import javax.persistence.Column;
 import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
 import javax.persistence.FetchType;
 import javax.persistence.Id;
 import javax.persistence.Index;
+import javax.persistence.JoinColumn;
 import javax.persistence.Lob;
+import javax.persistence.ManyToOne;
 import javax.persistence.NamedQueries;
 import javax.persistence.NamedQuery;
 import javax.persistence.Table;
@@ -39,6 +42,7 @@ import org.apache.isis.applib.types.MemberIdentifierType;
 import org.apache.isis.extensions.commandlog.applib.command.CommandLog;
 import org.apache.isis.extensions.commandlog.applib.command.ReplayState;
 import org.apache.isis.extensions.commandlog.jpa.IsisModuleExtCommandLogJpa;
+import org.apache.isis.persistence.jpa.applib.integration.IsisEntityListener;
 import org.apache.isis.schema.cmd.v2.CommandDto;
 
 import lombok.Getter;
@@ -64,62 +68,61 @@ import lombok.Setter;
     @NamedQuery(
             name="findByParent",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE parent = :parent "),
+                    + "WHERE cl.parent = :parent "),
     @NamedQuery(
             name="findCurrent",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE completedAt is null "
+                    + "WHERE cl.completedAt is null "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findCompleted",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE completedAt is not null "
+                    + "WHERE cl.completedAt is not null "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findRecentByTarget",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE target = :target "
-                    + "ORDER BY cl.timestamp DESC "
-                    + "RANGE 0,30"),
+                    + "WHERE cl.target = :target "
+                    + "ORDER BY cl.timestamp DESC"), // programmatic LIMIT 30
     @NamedQuery(
             name="findByTargetAndTimestampBetween",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE target = :target "
-                    + " AND timestamp >= :from "
-                    + " AND timestamp <= :to "
+                    + "WHERE cl.target = :target "
+                    + " AND cl.timestamp >= :from "
+                    + " AND cl.timestamp <= :to "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTargetAndTimestampAfter",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE target = :target "
-                    + " AND timestamp >= :from "
+                    + "WHERE cl.target = :target "
+                    + " AND cl.timestamp >= :from "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTargetAndTimestampBefore",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE target = :target "
-                    + " AND timestamp <= :to "
+                    + "WHERE cl.target = :target "
+                    + " AND cl.timestamp <= :to "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTarget",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE target = :target "
+                    + "WHERE cl.target = :target "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTimestampBetween",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE timestamp >= :from "
-                    + " AND  timestamp <= :to "
+                    + "WHERE cl.timestamp >= :from "
+                    + " AND  cl.timestamp <= :to "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTimestampAfter",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE timestamp >= :from "
+                    + "WHERE cl.timestamp >= :from "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="findByTimestampBefore",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE timestamp <= :to "
+                    + "WHERE cl.timestamp <= :to "
                     + "ORDER BY cl.timestamp DESC"),
     @NamedQuery(
             name="find",
@@ -128,24 +131,20 @@ import lombok.Setter;
     @NamedQuery(
             name="findRecentByUsername",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE username = :username "
-                    + "ORDER BY cl.timestamp DESC "
-                    + "RANGE 0,30"),
+                    + "WHERE cl.username = :username "
+                    + "ORDER BY cl.timestamp DESC"), // programmatic LIMIT 30
     @NamedQuery(
             name="findFirst",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE startedAt   is not null "
-                    + "   AND completedAt is not null "
-                    + "ORDER BY cl.timestamp ASC "
-                    + "RANGE 0,2"),
-        // this should be RANGE 0,1 but results in DataNucleus submitting "FETCH NEXT ROW ONLY"
-        // which SQL Server doesn't understand.  However, as workaround, SQL Server *does* understand FETCH NEXT 2 ROWS ONLY
+                    + "WHERE cl.startedAt   is not null "
+                    + "   AND cl.completedAt is not null "
+                    + "ORDER BY cl.timestamp ASC"), // programmatic LIMIT 1
     @NamedQuery(
             name="findSince",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE timestamp > :timestamp "
-                    + "   AND startedAt is not null "
-                    + "   AND completedAt is not null "
+                    + "WHERE cl.timestamp > :timestamp "
+                    + "   AND cl.startedAt is not null "
+                    + "   AND cl.completedAt is not null "
                     + "ORDER BY cl.timestamp ASC"),
     // most recent (replayed) command previously replicated from primary to
     // secondary.  This should always exist except for the very first times
@@ -153,28 +152,19 @@ import lombok.Setter;
     @NamedQuery(
             name="findMostRecentReplayed",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE (replayState == 'OK' || replayState == 'FAILED') "
-                    + "ORDER BY cl.timestamp DESC "
-                    + "RANGE 0,2"), // this should be RANGE 0,1 but results in DataNucleus submitting "FETCH NEXT ROW ONLY"
-                                    // which SQL Server doesn't understand.  However, as workaround, SQL Server *does* understand FETCH NEXT 2 ROWS ONLY
-    // the most recent completed command, as queried on the
-    // secondary, corresponding to the last command run on primary before the
-    // production database was restored to the secondary
+                    + "WHERE (cl.replayState = 'OK' OR cl.replayState = 'FAILED') "
+                    + "ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
     @NamedQuery(
             name="findMostRecentCompleted",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE startedAt   is not null "
-                    + "   AND completedAt is not null "
-                    + "ORDER BY cl.timestamp DESC "
-                    + "RANGE 0,2"),
-        // this should be RANGE 0,1 but results in DataNucleus submitting "FETCH NEXT ROW ONLY"
-        // which SQL Server doesn't understand.  However, as workaround, SQL Server *does* understand FETCH NEXT 2 ROWS ONLY
+                    + "WHERE cl.startedAt   is not null "
+                    + "   AND cl.completedAt is not null "
+                    + "ORDER BY cl.timestamp DESC"), // programmatic LIMIT 1
     @NamedQuery(
             name="findNotYetReplayed",
             query=CommandJpa.SELECT_FROM
-                    + "WHERE replayState = 'PENDING' "
-                    + "ORDER BY cl.timestamp ASC "
-                    + "RANGE 0,10"),    // same as batch size
+                    + "WHERE cl.replayState = 'PENDING' "
+                    + "ORDER BY cl.timestamp ASC"), // programmatic LIMIT 10
 })
 
 //    @javax.jdo.annotations.Query(
@@ -195,6 +185,7 @@ import lombok.Setter;
 @DomainObject(
         logicalTypeName = CommandJpa.LOGICAL_TYPE_NAME,
         editing = Editing.DISABLED)
+@EntityListeners(IsisEntityListener.class)
 @NoArgsConstructor
 public class CommandJpa extends CommandLog {
 
@@ -246,9 +237,17 @@ public class CommandJpa extends CommandLog {
     @Getter @Setter
     private String replayStateFailureReason;
 
-    @Column(name="parentId", nullable=true)
-    @Getter @Setter
-    private CommandLog parent;
+    @ManyToOne
+    @JoinColumn(name="parentId", nullable=true)
+    private CommandJpa parent;
+    @Override
+    public CommandJpa getParent() {
+        return parent;
+    }
+    @Override
+    public void setParent(final CommandLog parent) {
+        this.parent = (CommandJpa)parent;
+    }
 
     @Column(nullable=true, length = 2000, name="target")
     @Getter @Setter
diff --git a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpaRepository.java b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpaRepository.java
index bab666bcf2..54cfc9a053 100644
--- a/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpaRepository.java
+++ b/extensions/core/command-log/jpa/src/main/java/org/apache/isis/extensions/commandlog/jpa/entities/CommandJpaRepository.java
@@ -183,14 +183,16 @@ implements ICommandLogRepository<CommandJpa> {
     public List<CommandJpa> findRecentByUsername(final String username) {
         return repositoryService().allMatches(
                 Query.named(CommandJpa.class, "findRecentByUsername")
-                    .withParameter("username", username));
+                    .withParameter("username", username)
+                    .withLimit(30));
     }
 
     @Override
     public List<CommandJpa> findRecentByTarget(final Bookmark target) {
         return repositoryService().allMatches(
                 Query.named(CommandJpa.class, "findRecentByTarget")
-                    .withParameter("target", target));
+                    .withParameter("target", target)
+                    .withLimit(30));
     }
 
     @Override
@@ -249,7 +251,6 @@ implements ICommandLogRepository<CommandJpa> {
 
     @Override
     public Optional<CommandJpa> findMostRecentReplayed() {
-
         return repositoryService().firstMatch(
                 Query.named(CommandJpa.class, "findMostRecentReplayed"));
     }
diff --git a/extensions/core/command-log/jpa/src/main/resources/META-INF/orm-commandlog.template b/extensions/core/command-log/jpa/src/main/resources/META-INF/orm-commandlog.template
new file mode 100644
index 0000000000..d2c2c83c31
--- /dev/null
+++ b/extensions/core/command-log/jpa/src/main/resources/META-INF/orm-commandlog.template
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm
+      http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
+    version="2.1">
+
+	<!-- rename file to .xml then customize-->
+
+	<entity class="org.apache.isis.extensions.commandlog.jpa.entities.CommandJpa">
+		<table schema="isisExtensionsCommandLog" name="Command"/>
+	</entity>
+
+</entity-mappings>
\ No newline at end of file