Monday, June 7, 2021

Changing The Way We Use JPA

I've been updating some training materials recently, and thinking about better ways of teaching and talking about JPA. One of the things I've been thinking about is how we have typically used JPA, and how that should change given the pains I've experienced (and observed).

JPA is often seen as a set of annotations (or XML files) that provide O/R (object-relational) mapping information. And most developers think that the more mapping annotations they know and use, the more benefits they get. But the past few years of wrestling with small to medium monoliths/systems (with about 200 tables/entities) have taught me something else.

TL;DR

  1. Reference entities by ID (only map entity relationships within an aggregate)
  2. Don't let JPA steal your Identity (avoid @GeneratedValue when you can)
  3. Use ad-hoc joins to join unrelated entities

Reference Entities by Identifier

Only map entity relationships within an aggregate.

Tutorials (and training) would typically go about teaching and covering all possible relationship mappings. After basic mappings, many would start from simple uni-directional @ManyToOne mapping. Then proceed to bi-directional @OneToMany and @ManyToOne. Unfortunately, most often than not, they fail to explicitly point out that it is perfectly fine to not map the relationship. So, beginners would often complete the training thinking that it would be a mistake to not map a related entity. They mistakenly think that a foreign key field must be mapped as a related entity.

In fact, it is not an error, if you change the @ManyToOne mapping below…

@Entity
public class SomeEntity {
    // ...
    @ManyToOne private Country country;
    // ...
}

@Entity
public class Country {
    @Id private String id; // e.g. US, JP, CN, CA, GB, PH
    // ...
}

…into a basic field that contains the primary key value of the related entity.

@Entity
public class SomeEntity {
    // ...
    @Column private String countryId;
    // ...
}

@Entity
public class Country {
    @Id private String id; // e.g. US, JP, CN, CA, GB, PH
    // ...
}

Why is this a problem?

Mapping all entity relationships increases the chances of unwanted traversals that usually lead to unnecessary memory consumption. This also leads to an unwanted cascade of EntityManager operations.

This may not be much if you're dealing with just a handful of entities/tables. But it becomes a maintenance nightmare when working with dozens (if not hundreds) of entities.

When do you map a related entity?

Map related entities only when they are within an Aggregate (in DDD).

Aggregate is a pattern in Domain-Driven Design. A DDD aggregate is a cluster of domain objects that can be treated as a single unit. An example may be an order and its line-items, these will be separate objects, but it's useful to treat the order (together with its line items) as a single aggregate.

@Entity
public class Order {
    // ...
    @OneToMany(mappedBy = "order", ...) private List<OrderItem> items;
    // ...
}

@Entity
public class OrderItem {
    // ...
    @ManyToOne(optional = false) private Order order;
    // ...
}

More modern approaches to aggregate design (see Vaughn Vernon's Implementing Domain-Driven Design) advocate a cleaner separation between aggregates. It is a good practice to refer to an aggregate root by storing its ID (unique identifier), not a full reference.

If we expand the simple order example above, the line-item (OrderItem class) should not have a @ManyToOne mapping to the product (since it is another aggregate in this example). Instead, it should just have the ID of the product.

@Entity
public class Order {
    // ...
    @OneToMany(mappedBy = "order", ...) private List<OrderItem> items;
    // ...
}

@Entity
public class OrderItem {
    // ...
    @ManyToOne(optional = false) private Order order;
    // @ManyToOne private Product product; // <-- Avoid this!
    @Column private ... productId;
    // ...
}

But… what if the Product (aggregate root entity) has its @Id field mapped as @GeneratedValue? Are we forced to persist/flush first and then use the generated ID value?

And, what about joins? Can we still join those entities in JPA?

Don't Let JPA Steal Your Identity

Using @GeneratedValue may initially make the mapping simple and easy to use. But when you start referencing other entities by ID (and not by mapping a relationship), it becomes a challenge.

If the Product (aggregate root entity) has its @Id field mapped as @GeneratedValue, then calling getId() may return null. When it returns null, the line-item (OrderItem class) will not be able to reference it!

In an environment where all entities always have a non-null Id field, referencing any entity by ID becomes easier. Furthermore, having non-null Id fields all the time, makes equals(Object) and hashCode() easier to implement.

And because all Id fields become explicitly initialized, all (aggregate root) entities have a public constructor that accepts the Id field value. And, as I've posted long ago, a protected no-args constructor can be added to keep JPA happy.

@Entity
public class Order {
    @Id private Long id;
    // ...
    public Order(Long id) {
        // ...
        this.id = id;
    }
    public Long getId() { return id; }
    // ...
    protected Order() { /* as required by ORM/JPA */ }
}

But beware! When using Spring Data JPA to save() an entity that does not use @GeneratedValue on its @Id field, an unnecessary SQL SELECT is issued before the expected INSERT. This is due to SimpleJpaRepository's save() method (shown below). It relies on the presence of the @Id field (non-null value) to determine whether to call persist(Object) or merge(Object).

public class SimpleJpaRepository // ...
	@Override
	public <S extends T> save(S entity) {
		// ...
		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}
}

The astute reader will notice that, if the @Id field is never null, the save() method will always call merge(). This causes the unnecessary SQL SELECT (before the expected INSERT).

Fortunately, the work-around is simple — implement Persistable<ID>.

@MappedSuperclass
public abstract class BaseEntity<ID> implements Persistable<ID> {
	@Transient
	private boolean persisted = false;
	@Override
	public boolean isNew() {
		return !persisted;
	}
	@PostPersist
	@PostLoad
	protected void setPersisted() {
		this.persisted = true;
	}
}

The above also implies that all updates to entities must be done by loading the existing entity into the persistence context first, and applying changes to the managed entity.

Use Ad-hoc Joins to Join Unrelated Entities

And, what about joins? Now that we reference other entities by ID, how can we join unrelated entities in JPA?

In JPA version 2.2, unrelated entities cannot be joined. However, I cannot confirm if this has become a standard in version 3.0, where all javax.persistence references were renamed to jakarta.persistence.

Given the OrderItem entity, the absence of the @ManyToOne mapping causes it to fail to be joined with the Product entity.

@Entity
public class Order {
    // ...
}

@Entity
public class OrderItem {
    // ...
    @ManyToOne(optional = false) private Order order;
    @Column private ... productId;
    // ...
}

Thankfully 😊, Hibernate 5.1.0+ (released back in 2016) and EclipseLink 2.4.0+ (released back in 2012) have been supporting joins of unrelated entities. These joins are also referred to as ad-hoc joins.

SELECT o
  FROM Order o
  JOIN o.items oi
  JOIN Product p ON (p.id = oi.productId) -- supported in Hibernate and EclipseLink

Also, this has been raised as an API issue (Support JOIN/ON for two root entities). I really hope that it will become a standard soon.

In Closing

What do you think about the above changes? Are you already using similar approaches? Do you use native SQL to explicitly retrieve a generated value (e.g. sequence object) to create an entity with a non-null Id field? Do you use entity-specific ID types to differentiate ID values? Let me know in the comments below.

Thursday, September 17, 2020

Reduce Repetitive Code in Spring MVC Controllers

After spending some time doing sustained engineering (a.k.a. maintaining legacy code), I ventured to reduce repetitive code in Spring MVC @Controllers. I started with an abstract base controller class. But I soon found out that it was a dead-end because @RequestMapping is not inherited from (or combined with) parent classes and/or interfaces (see Spring MVC @RequestMapping Inheritance).

With some free time to further think about this, I took different approach.

@Controller
public class RepositoryCrudController {

	private final Repositories repositories;
	private final RepositoryInvokerFactory repositoryInvokerFactory;

	// ...

	@RequestMapping("/{repository}")
	String index(@PathVariable("repository") String repositoryKey,
			Pageable pageable, Model model) {
		// ... (only if repository has findAll(Pageable) method)
		return repositoryKey + "/index";
	}

	@GetMapping("/{repository}/{id}")
	String show(@PathVariable("repository") String repositoryKey,
			@PathVariable("id") Object id, Model model) {
		// ... (only if repository has findById method)
		return repositoryKey + "/show";
	}

	@GetMapping(path = "/{repository}", param = "create")
	String create(...) {
		// ... (only if repository has save method)
		return repositoryKey + "/create";
	}

	@PostMapping("/{repository}")
	String save(...) {
		// ... (only if repository has save method)
		return "redirect:/" + repositoryKey + "/{id}";
	}

	@GetMapping(path = "/{repository}/{id}", param = "edit")
	// ... edit (only if repository has findById and save methods)
	@PutMapping("/{repository}/{id}")
	// ... update (only if repository has save method)
	// @DeleteMapping("/{repository}/{id}")
	// ... delete (only if repository has deleteById method)
}

This approach is largely inspired by RepositoryEntityController of Spring Data REST.

Instead of defining an abstract base controller class, I created a concrete controller class with the default (or repetitive) behavior. The default behavior relies on invoking methods on Spring Data repositories.

For custom controllers, instead of creating subclasses (of the abstract base class), the controller can simply define handler methods that behave differently. For example:

@Entity
public class Article {...}

// Spring Data JPA
public interface ArticleRepository extends CrudRepository<Article, ...> {...}

@Controller
@RequestMapping("/articles")
public class ArticlesController {

	// no need for index() handler method
	// just set-up "articles/index" view

	// no need for show() handler method
	// just set-up "articles/show" view

	@GetMapping(param = "create")
	String create(Model model) {
		// Do something that is _not_ *default* behavior
		// e.g. provide options for dropdowns, or use form-backing object/JavaBean
		// ...
		return "articles/create";
	}

	// no need for save() handler method
	// just set-up "articles/show" view

	@GetMapping(path = "/{id}", param = "edit")
	String edit(@PathVariable("id") ... id, Model model) {
		// Do something that is _not_ *default* behavior
		// e.g. provide options for dropdowns, or use form-backing object/JavaBean
		// ...
		return "articles/edit";
	}

	// no need for update() handler method
	// just set-up "articles/show" view

}

The above will work because Spring MVC chooses the more specific mapping first. When a GET /articles?create request is received, the ArticlesController will be chosen to handle it (and not RepositoryCrudController). But if ArticlesController handler methods were not defined, the GET /articles?create request would have been handled by the RepositoryCrudController.

With this simple fall-back controller that has the default behavior, developers (team mates) can then focus on the domain, creating views, or creating controllers with custom behavior (e.g. Ajax, Server-generated JavaScript Responses).

That's all for now.

Tuesday, June 25, 2019

Spring with Rails' jQuery UJS

I’ve always wanted to try to see if I could use Rails’ jQuery UJS in a Spring Boot project. The UJS in jquery-ujs stands for unobtrusive JavaScript. I really like how UJS wires event handlers to eligible DOM elements marked with HTML5 data-* attributes. I find myself wanting to see more of this approach being used in Spring Boot web apps. I wonder why there’s very little mentioned on the web about this. Or, may be I’ve been looking at the wrong places.

Anyway, here are some things jQuery UJS can do, and the related source code is on GitHub (albeit using a different example).

Non-GET Links (e.g. DELETE)

When I need to render a link that deletes an item, I would use a <button> wrapped in a <form> with a hidden _method field with a value of delete. The <form> is not visible to the user. But the visible button is used to submit the <form>. Some CSS is used to make the button look like a link.

<form action="/articles/42" method="post">
  <input type="hidden" name="_method" value="delete" />
  <button class="btn btn-link">Delete</button>
</form>

Thanks to Spring’s HiddenHttpMethodFilter (also automatically configured in Spring Boot), when this <form> is submitted, it will be received as a request with a method of DELETE. It maps to @DeleteMapping(path="/articles/{id}") in the related @Controller.

While the above works, there is an easier way with jQuery UJS. All that is needed to render a link to delete an item is this:

<a href="/articles/42" data-method="delete">Delete</a>

jQuery UJS will enhance links that have data-method attribute. When the above example link is clicked, the JavaScript will create a <form>. The action attribute of this <form> is set to the value of link’s href. The method is set to post. A hidden _method field is added to the <form> and set to the value of the link’s data-method. Finally, the <form> is submitted (and the link is not followed).

Confirmation Dialogs

Most often than not, when it comes to deleting anything, it would be good to confirm with the user. This could be implemented as a simple dialog via window.confirm(). If we build from the previous example, the <form> would look like this:

<form action="/articles/42" method="post"
    onsubmit="return confirm('Are you sure?')">
  <input type="hidden" name="_method" value="delete" />
  <button>Delete</button>
</form>

While the above works, jQuery UJS shows us a better way. All that is needed to confirm before deleting is this:

<a href="/articles/42?delete" data-method="delete"
    data-confirm="Are you sure?">Delete</a>

jQuery UJS will enhance links (and <form>s too) that have data-confirm attribute. When the above example link is clicked, the JavaScript will call confirm() to show a dialog containing the text that is the value of the attribute. If the user chooses to cancel, the creation/submission of the <form> (due to data-method) does not take place.

Ajax Links

After deleting an item, the page usually reloads to show that the deleted element has indeed been removed.

Let's say the items are displayed in a table. Each row has a unique id.

<table>
  <tr id="article:18">
    <!-- data cells for /articles/18 -->
    <td><a href="/articles/18?delete"
        data-method="delete" data-confirm="Are you sure?">Delete</a></td>
  </tr>
  <tr id="article:42">
    <!-- data cells for /articles/42 -->
    <td><a href="/articles/42?delete"
        data-method="delete" data-confirm="Are you sure?">Delete</a></td>
  </tr>
  <!-- other rows -->
</table>

Assuming that we can simply remove the HTML element that represented the deleted item, then we can probably send the DELETE request asynchronously and get a response that would remove the related HTML element. jQuery UJS makes this as easy as adding data-remote="true" and some minor changes to the server-side controller.

For the HTML, all we need is data-remote="true".

<a href="/articles/42?delete" data-remote="true"
    data-method="delete"
    data-confirm="Are you sure?">Delete</a>

When the link is clicked, jQuery UJS will again send the DELETE request. But this time, it will be sent asynchronously using Ajax. Doing so enables the browser not to reload the entire page. And depending on the server’s response, can update just a portion of the page. Thus, providing a slightly better user experience.

For the server-side controller, we need to send a different response when the request is expecting text/javascript. We add a handler method that will respond with text/javascript by using the produces element of @RequestMapping. The response will remove the related HTML element(s).

// inside a @Controller
@DeleteMapping(path="/articles/{id}")
String delete(... id) {
    // ... delete article with given identifier
    return "redirect:/articles";
}

@DeleteMapping(path="/articles/{id}",
    produces="text/javascript")
String delete(... id) {
    // ... delete article with given identifier
    return "articles/delete";
}

The view is a JSP that contains text/javascript. This will be executed by jQuery UJS.

<%-- articles/delete.js.jsp --%>
<%@ page contentType="text/javascript" %>
$('#article:${id}').remove();

Partials and Server-generated JavaScript Responses

Now what happens if we wanted to have an edit link to get some HTML content and show it up in a modal (without a page refresh)?

Here's what we can do. We send a GET request asynchronously. The response is expected to contain JavaScript that would append the HTML in targeted places in the document, and then trigger the modal to appear.

  <a href="/articles/42?edit" data-remote="true">Edit</a>

When the response is expected to be text/javascript, articles/edit.js.jsp is rendered. Otherwise, the usual articles/edit.jsp is rendered.

// inside a @Controller
@GetMapping(path="/articles/{id}", params={"edit"})
String edit(... id, ...) {
    // ...
    return "articles/edit";
}

@GetMapping(path="/articles/{id}", params={"edit"},
    produces="text/javascript")
String editViaAjax(... id, ...) {
    // ...
    return "articles/edit";
}

The edit.jsp renders the <form> (a partial, not a complete HTML document) which has been refactored to its own JSP to avoid repetition.

<%-- articles/edit.jsp --%>
<!-- -->
  <jsp:include page="_form.jsp" />
<!-- -->

The edit.js.jsp renders the same <form> (a partial, not a complete HTML document) as a string in JS. Then includes it in the modal. It was tricky to render _form.jsp as a string. I had to use <c:import>.

<%-- articles/edit.js.jsp --%>
<%@ page contentType="text/javascript" %>
<c:import var="html" url="…_form.jsp" />
<!-- escape double quotes and remove new lines -->
(function() {
  const $modal = $('#...'); // ID of modal element
  $modal.find('.modal-body').html('${html}');
  if (!$modal.is(':visible')) {
    $modal.modal('show');
  }
})()

For this to work, another InternalResourceViewResolver (IRVR) bean with text/javascript as the contentType is configured. This bean uses the same prefix and a slightly different suffix: .js.jsp. That way, when the request is expecting text/javascript, the CNVR will favor using the IRVR bean with text/javascript and it ends up rendering articles/edit.js.jsp.

Ajax Forms

The data-remote="true" attribute can also be applied to <form>s. With it, jQuery UJS will handle the form submission as an Ajax request. And when the form is being submitted, the buttons can be disabled by adding data-disabled-with. For example,

<form ...>
  <!-- ... -->
  <button data-disable-with="Saving...">Save</button>
</form ...>

When the above form is submitted, jQuery UJS will disable the button and change its content to "Saving...".

Closing Thoughts

I’ve barely touched the surface of Rails’ jQuery UJS. There is so much more that it can offer. I look forward to using it (and similar techniques) in my web apps.