blog: @LazyToOne, formatting, comments.

Change-Id: I44655fd7b43822f7c1a73af22402684acb49d333
diff --git a/blog/pom.xml b/blog/pom.xml
index 4d7be41..832ff43 100644
--- a/blog/pom.xml
+++ b/blog/pom.xml
@@ -73,6 +73,12 @@
       <artifactId>rest-assured</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.jetbrains</groupId>
+      <artifactId>annotations</artifactId>
+      <version>RELEASE</version>
+      <scope>compile</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -94,11 +100,6 @@
       </plugin>
 
       <plugin>
-        <artifactId>maven-compiler-plugin</artifactId>
-        <version>${compiler-plugin.version}</version>
-      </plugin>
-
-      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <version>${surefire-plugin.version}</version>
         <configuration>
@@ -108,9 +109,11 @@
           </systemPropertyVariables>
         </configuration>
       </plugin>
+
       <plugin>
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
+        <version>${compiler-plugin.version}</version>
         <configuration>
           <source>15</source>
           <target>15</target>
diff --git a/blog/src/main/java/eu/mulk/demos/blog/Author.java b/blog/src/main/java/eu/mulk/demos/blog/Author.java
index 53c21c4..d544d2e 100644
--- a/blog/src/main/java/eu/mulk/demos/blog/Author.java
+++ b/blog/src/main/java/eu/mulk/demos/blog/Author.java
@@ -2,12 +2,20 @@
 
 import io.quarkus.hibernate.orm.panache.PanacheEntity;
 import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.OneToOne;
+import org.hibernate.annotations.LazyToOne;
+import org.hibernate.annotations.LazyToOneOption;
 
 @Entity
 public class Author extends PanacheEntity {
 
   public String name;
 
+  @OneToOne(fetch = FetchType.LAZY, mappedBy = "author")
+  @LazyToOne(LazyToOneOption.NO_PROXY)
+  public BasicCredentials basicCredentials;
+
   public static Author create(String name) {
     var a = new Author();
     a.name = name;
diff --git a/blog/src/main/java/eu/mulk/demos/blog/BasicCredentials.java b/blog/src/main/java/eu/mulk/demos/blog/BasicCredentials.java
new file mode 100644
index 0000000..01471c6
--- /dev/null
+++ b/blog/src/main/java/eu/mulk/demos/blog/BasicCredentials.java
@@ -0,0 +1,28 @@
+package eu.mulk.demos.blog;
+
+import io.quarkus.hibernate.orm.panache.PanacheEntity;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.MapsId;
+import javax.persistence.OneToOne;
+
+@Entity
+public class BasicCredentials extends PanacheEntity {
+
+  @OneToOne(fetch = FetchType.LAZY)
+  @MapsId
+  public Author author;
+
+  public String username;
+
+  public String password;
+
+  public static BasicCredentials create(Author author, String username, String password) {
+    var bc = new BasicCredentials();
+    bc.author = author;
+    bc.id = author.id;
+    bc.username = username;
+    bc.password = password;
+    return bc;
+  }
+}
diff --git a/blog/src/main/java/eu/mulk/demos/blog/DemoDataLoader.java b/blog/src/main/java/eu/mulk/demos/blog/DemoDataLoader.java
index 3ee745a..cbaed9a 100644
--- a/blog/src/main/java/eu/mulk/demos/blog/DemoDataLoader.java
+++ b/blog/src/main/java/eu/mulk/demos/blog/DemoDataLoader.java
@@ -13,6 +13,7 @@
 @ApplicationScoped
 public class DemoDataLoader {
 
+  static final int AUTHOR_COUNT = 3;
   static final int POST_COUNT = 10;
   static final int COMMENT_COUNT = 3;
   static final int CATEGORY_COUNT = 2;
@@ -28,25 +29,32 @@
     }
 
     // Authors
-    var mb = Author.create("Matthias Benkard");
-    em.persist(mb);
+    var authors =
+        nat(AUTHOR_COUNT)
+            .map(x -> Author.create("Author #%d".formatted(x)))
+            .collect(toList());
+    authors.forEach(em::persist);
 
     // Posts
     var posts =
-        nat(POST_COUNT).map(x -> Post.create(mb, "Post #%d".formatted(x))).collect(toList());
+        nat(POST_COUNT)
+            .map(x -> Post.create(authors.get(x % AUTHOR_COUNT), "Post #%d".formatted(x)))
+            .collect(toList());
     posts.forEach(em::persist);
 
     // Comments
     for (var post : posts) {
       post.comments =
           nat(COMMENT_COUNT)
-              .map(x -> Comment.create(post, "Anonymous Coward", "First post")).collect(toList());
+              .map(x -> Comment.create(post, "Anonymous Coward", "First post"))
+              .collect(toList());
       post.comments.forEach(em::persist);
     }
 
     // Categories
     var categories =
-        nat(CATEGORY_COUNT).map(x -> Category.create("Category #%d".formatted(x)))
+        nat(CATEGORY_COUNT)
+            .map(x -> Category.create("Category #%d".formatted(x)))
             .collect(toList());
     categories.forEach(em::persist);
     for (var post : posts) {
diff --git a/blog/src/main/java/eu/mulk/demos/blog/PostResource.java b/blog/src/main/java/eu/mulk/demos/blog/PostResource.java
index 44410cf..532e873 100644
--- a/blog/src/main/java/eu/mulk/demos/blog/PostResource.java
+++ b/blog/src/main/java/eu/mulk/demos/blog/PostResource.java
@@ -1,43 +1,138 @@
 package eu.mulk.demos.blog;
 
 import java.util.List;
+import java.util.Set;
 import javax.transaction.Transactional;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
 import javax.ws.rs.Produces;
 import javax.ws.rs.core.MediaType;
+import org.hibernate.annotations.LazyToOne;
+import org.hibernate.annotations.LazyToOneOption;
+import org.jboss.logging.Logger;
 
 @Path("/posts")
 public class PostResource {
 
+  static final Logger log = Logger.getLogger(PostResource.class);
+
+  /**
+   * Fetches all posts with no extra information.
+   *
+   * Simple.  No surprises.
+   */
   @GET
   @Produces(MediaType.TEXT_PLAIN)
   @Transactional
   public List<Post> getAll() {
+    clearLog();
+
     return Post.findAll().list();
   }
 
+  /**
+   * Fetches all posts with comments included.
+   *
+   * Lazy fetching.  Simple.  No surprises.
+   */
   @GET
   @Produces(MediaType.TEXT_PLAIN)
   @Transactional
   @Path("/q1")
   public List<Post> getAllWithComments() {
-    return Post.find("""
-        SELECT p FROM Post p
-          LEFT JOIN FETCH p.comments 
-        """).list();
+    clearLog();
+
+    return Post.find(
+        """
+            SELECT p FROM Post p
+              LEFT JOIN FETCH p.comments 
+            """)
+        .list();
   }
 
+  /**
+   * Fetches all posts with author info included.
+   *
+   * <strong>Oops!</strong>
+   *
+   * {@link LazyToOne} with {@link LazyToOneOption#NO_PROXY} is needed to make this efficient.
+   */
   @GET
   @Produces(MediaType.TEXT_PLAIN)
   @Transactional
   @Path("/q2")
-  public List<Post> getAllWithCommentsAndCategories() {
-    return Post.find("""
-        SELECT p FROM Post p
-          LEFT JOIN FETCH p.comments
-          LEFT JOIN FETCH p.categories
-        """).list();
+  public List<Post> getAllWithAuthors() {
+    clearLog();
+
+    return Post.find(
+        """
+            SELECT p FROM Post p
+              LEFT JOIN FETCH p.author 
+            """)
+        .list();
   }
 
+  /**
+   * Fetches all posts with comments and category info included.
+   *
+   * <strong>Oops!</strong>  Crashes.
+   *
+   * Either use {@link Set} and get bad performance or do it as in {@link
+   * #getAllWithCommentsAndCategories2()}.
+   */
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  @Transactional
+  @Path("/q3")
+  public List<Post> getAllWithCommentsAndCategories() {
+    clearLog();
+
+    return Post.find(
+        """
+            SELECT p FROM Post p
+              LEFT JOIN FETCH p.comments
+              LEFT JOIN FETCH p.categories
+            """)
+        .list();
+  }
+
+  /**
+   * Fetches all posts with comments and category info included.
+   *
+   * 2 queries, but hey, no cartesian explosion!  Works really well.
+   */
+  @GET
+  @Produces(MediaType.TEXT_PLAIN)
+  @Transactional
+  @Path("/q4")
+  public List<Post> getAllWithCommentsAndCategories2() {
+    clearLog();
+
+    List<Post> posts = Post.find(
+        """
+            SELECT p FROM Post p
+              LEFT JOIN FETCH p.comments
+            """)
+        .list();
+
+    posts = Post.find(
+        """
+            SELECT DISTINCT p FROM Post p
+              LEFT JOIN FETCH p.categories
+             WHERE p IN (?1)
+            """,
+        posts)
+        .list();
+
+    return posts;
+  }
+
+  private static void clearLog() {
+    log.infof("""
+                
+        -----------------------------------------------------
+        -------------------- NEW REQUEST --------------------
+        -----------------------------------------------------
+        """);
+  }
 }