JPA Buddy
announcement - icon

JPA is huge! It covers nearly every aspect of communication between relational databases and the Java application and is deeply integrated into all major frameworks.

If you're using IntelliJ, JPA Buddy is super helpful. The plugin gently guides you through the subtleties of the most popular JPA implementations, visually reminds you of JPA features, generates code that follows best practices, and integrates intelligent inspections to improve your existing persistence code.

More concretely, it provides powerful tooling to generate Spring Data JPA repositories and methods, Flyway Versioned Migrations, Liquibase Differential Changelogs, DDL and SQL statements, DTO objects, and MapStruct interfaces.

Oh, and it actually generates JPA entities from an existing database and gradually update the data model as the database evolves! Yeah.

>> Become a lot more productive with JPA Buddy

Persistence top

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE
Lightrun – Third Party Code
announcement - icon

Flakiness in REST requests is a common issue. A request can get a 200 OK in one scenario and a 409 next time. Sometimes a request can even succeed and fail intermittently on the same exact request. In short, working over HTTP can be a bit of a mess without solid tooling.

Also, while it’s easy enough to debug these issues locally when developing the application, we’re talking about production here - we can’t afford the downtime while you’re stepping in and out of code. Uptime is kind of the whole point.

With Lightrun, you can get the same level of access you get with a local debugger or profiler - no downtime required. You can add logs, metrics, and snapshots (think breakpoints, but without stopping the running service), in a safe and read-only manner - without redeploying, restarting, or even stopping the running service. Performance and security are maintained throughout the process.

Learn how to debug a live REST API (built with Spring, of course), using Lightrun, in this 5-minute tutorial:

>> Debugging REST Requests in Spring-Based applications using the Lightrun Platform

1. Overview

JPA makes dealing with relational database models from our Java applications less painful. Things are simple when we map every table to a single entity class.

But we sometimes have reasons to model our entities and tables differently:

In this short tutorial, we'll see how to tackle this last scenario.

2. Data Model

Let's say we run a restaurant, and we want to store data about every meal we serve:

  • Name
  • Description
  • Price
  • What kind of allergens it contains

Since there are many possible allergens, we're going to group this data set together.

Furthermore, we'll also model this using the following table definitions:

meals

Now let's see how we can map these tables to entities using standard JPA annotations.

3. Creating Multiple Entities

The most obvious solution is to create an entity for both classes.

Let's start by defining the Meal entity:

@Entity
@Table(name = "meal")
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @OneToOne(mappedBy = "meal")
    Allergens allergens;

    // standard getters and setters
}

Next, we'll add the Allergens entity:

@Entity
@Table(name = "allergens")
class Allergens {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "meal_id")
    Long mealId;

    @OneToOne
    @PrimaryKeyJoinColumn(name = "meal_id")
    Meal meal;

    @Column(name = "peanuts")
    boolean peanuts;

    @Column(name = "celery")
    boolean celery;

    @Column(name = "sesame_seeds")
    boolean sesameSeeds;

    // standard getters and setters
}

We can see that meal_id is both the primary key and also the foreign key. That means we need to define the one-to-one relationship column using @PrimaryKeyJoinColumn.

However, this solution has two problems:

  • We always want to store allergens for a meal, and this solution doesn't enforce this rule.
  • The meal and allergen data belong together logically. Therefore, we might want to store this information in the same Java class even though we created multiple tables for them.

One possible resolution to the first problem is to add the @NotNull annotation to the allergens field on our Meal entity. JPA won't let us persist the Meal if we have a null Allergens.

However, this is not an ideal solution. We want a more restrictive one, where we don't even have the opportunity to try to persist a Meal without Allergens.

4. Creating a Single Entity With @SecondaryTable

We can create a single entity specifying that we have columns in different tables using the @SecondaryTable annotation:

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

Behind the scenes, JPA joins the primary table with the secondary table and populates the fields. This solution is similar to the @OneToOne relationship, but this way, we can have all of the properties in the same class.

It's important to note that if we have a column that is in a secondary table, we have to specify it with the table argument of the @Column annotation. If a column is in the primary table, we can omit the table argument since JPA looks for columns in the primary table by default.

Also note that we can have multiple secondary tables if we embed them in @SecondaryTables. Alternatively, from Java 8, we can mark the entity with multiple @SecondaryTable annotations since it's a repeatable annotation.

5. Combining @SecondaryTable With @Embedded

As we've seen, @SecondaryTable maps multiple tables to the same entity. We also know that @Embedded and @Embeddable do the opposite and map a single table to multiple classes.

Let's see what we get when we combine @SecondaryTable with @Embedded and @Embeddable:

@Entity
@Table(name = "meal")
@SecondaryTable(name = "allergens", pkJoinColumns = @PrimaryKeyJoinColumn(name = "meal_id"))
class Meal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    Long id;

    @Column(name = "name")
    String name;

    @Column(name = "description")
    String description;

    @Column(name = "price")
    BigDecimal price;

    @Embedded
    Allergens allergens;

    // standard getters and setters

}

@Embeddable
class Allergens {

    @Column(name = "peanuts", table = "allergens")
    boolean peanuts;

    @Column(name = "celery", table = "allergens")
    boolean celery;

    @Column(name = "sesame_seeds", table = "allergens")
    boolean sesameSeeds;

    // standard getters and setters

}

It's a similar approach to what we saw using @OneToOne. However, it has a couple of advantages:

  • JPA manages the two tables together for us, so we can be sure that there will be a row for each meal in both tables.
  • Also, the code is a bit simpler since we need less configuration.

Nevertheless, this one-to-one-like solution works only when the two tables have matching ids.

It's worth mentioning that if we want to reuse the Allergens class, it would be better if we defined the columns of the secondary table in the Meal class with @AttributeOverride.

6. Conclusion

In this short tutorial, we've seen how we can map multiple tables to the same entity using the @SecondaryTable JPA annotation.

We also saw the advantages of combining @SecondaryTable with @Embedded and @Embeddable to get a relationship similar to one-to-one.

As usual, the examples are available over on GitHub.

Spring bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> THE COURSE
Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!