What You'll Learn

The tag for this tutorial has the word ‘hexagonal' in it. It's there because we're heading towards implementation of a hexagonal architecture and towards putting in an adaptor between our service layer and our new repository layer. By the end of this tutorial, you should be able to see how the service layer can access all the data and services that it requires to implement its layer, and you should also be able to see how that is quite different from what was done with JDBC.

We have a Charity entity. Let's add a Trustee.

In entities/Trustee.java (new class)

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class Trustee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Convert(converter =  TrusteeRoleToStringConverter.class)
    private Role role;

    public enum Role {CHAIR, SECRETARY, TREASURER, UNSPECIFIED};
}

Everything should feel familiar. There is another converter class that will convert from the Enumerated type to a string and back. The Role enum is copied into the ‘entities' package just to keep it separate and allow entity and domain layers to diverge if necessary.

We now need to map the relationship between the charity and its trustees.

In entities/Charity.java

    @OneToMany
    @JoinColumn(name="charity_id")
    private Set<Trustee> trustees;

This is telling JPA that there is a one to many relationship between charities and trustees and that the column on the trustee table that defines the join is "charity_id". JPA assumes that the join is from the primary key (the id) of the entity to the named column.

Notice that there isn't a reverse relationship on the trustee side. If you get a trustee object, you can't ask it for its charity. JPA does support 2-way relationships (using the mappedBy) annotation, but you have to include it. Remember, in OO programming, we navigate the graph of objects via the references that one object has to another and thinking about that graph and how is compares to the key-based relationships in the relational model is a key part of object-relational modelling. This mapping has to cover a ‘paradign mismatch' - even though the models feel similar, there are some fundamental differences. For example, OO supports inheritance and relational doesn't. JPA provides different ways of mapping inherited classes to tables.

Let's add some test. As you read these tests, think about how the calls made in the tests are exemplar of how the service layer will call the repository.

Amend the schema.sql and data.sql files

Quick change to the schema file. Reverse the order of the drop table statements. This is to avoid referential integrity issues.

Quick change to the data sql file. Spell ‘Kathryn' correctly.

Write trustee tests

Let's write those tests.

In CharityDataJPATests.java

    @Test
    public void shouldHave4TrusteesAfterAddingOne() throws Exception {
        Charity nspcc = charityRepository.findByAcronym("nspcc").get();
        assertEquals(3, nspcc.getTrustees().size());
        Set<String> trusteeNames = nspcc.getTrustees()
                .stream()
                .map (t -> t.getName())
                .collect(Collectors.toSet());
        assertTrue(trusteeNames.contains("Lisa"));
        assertTrue(trusteeNames.contains("Phil"));
        assertTrue(trusteeNames.contains("Kathryn"));
        Trustee newTrustee = new Trustee(null, "Dave", Trustee.Role.UNSPECIFIED);
        nspcc.addTrustee(newTrustee);
        charityRepository.save(nspcc);
        Charity nspcc2 = charityRepository.findByAcronym("nspcc").get();
        assertEquals(4, nspcc2.getTrustees().size());
    }

In this test, we first get the charity. We can then get the trustees from the charity (how does that work?) We then assert that we have the right trustees. Then, we create a new trustee and add it to the charity object. We then save the charity.

We then retrieve that charity from the repository again and check that there are 4 trustees. Again - how does this work?

JPA uses proxies - objects that stand in place of other objects. From the point of view of the developer, they are getting the objects that they think they are getting, but the proxies can intercept method calls and do extra work. For example, the proxy around the charity can intercept the getTrustees() method call and make the required SQL call to get the trustees. Once the objects are retrieved from the databases, the objects act as a cache.

The proxies are considered "connected" during the database connection. By default, Spring starts this session with the request and ends it once the response has gone back.

If an error occurs because an object is considered "detached", then the session has expired during the processing. This often happens when entities are used throughout the architecture and are placed on the model by a controller. Once the entity goes onto the model as part of the response leg, it is doomed to become detached.

For more detail on how this works, have a look at this book on Safari.

In this tutorial, we've introduced relationships between entities. JPA will support one-to-one, one-to-many, many-to-one, many-to-many and inheritance relationships.

For many-to-many, it is important to note that an entity is NOT required for the link table. JPA can write the queries to join through the link.

There are a number of additional features that are left to the student to look up. In particular, the student should look up;