Spring Data JPA

Why write your own SQL?

What is JPA?

  • Java Persistence API
  • Allows for Object-Relational Mapping (ORM)

    • ORM allows us to map objects (entities) to databases

Entities

  • An entity is an object that is mapped to the database
  • In a relational database context, think of:

    • the class as the table
    • instance variables as the columns
    • objects (class instances) as the rows

Entity Mapping

Here is an example class with JPA annotations:

@Entity
public class Topic {

  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;

}

Let's break that down.


@Entity

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

[focus="1"]

The @Entity annotation indicates to JPA that instances of this class are entities to be stored in a database.

By default, JPA generates names based on our class and variable names. In this example, JPA will expect a table named topic, corresponding to entities of type Topic. This default can be overridden as necessary, but using the default keeps our code cleaner and easier to understand.


@Id

@Entity
public class Topic {

  @Id
  @GeneratedValue
  private long id;
  // ...
}

[focus="4"]

The @Id annotation indicates to JPA that the annotated instance variable should be used to store the primary key for the record representing this entity.

In this example, JPA would create a column named (you guessed it!) id in the topic table and assign this column as its primary key. The column's type will vary based on the specific database in use.


@GeneratedValue

@Entity
public class Topic {

  @Id
  @GeneratedValue
  private long id;
  // ...
}

[focus="5"]

@GeneratedValue indicates to JPA that this value should be generated. We usually rely on our API and/or the database to generate primary keys, since doing this manually is fiddly and painful.


The Professor and Mary Ann (the rest)

@Entity
public class Topic {

  @Id
  @GeneratedValue
  private long id;
  private String name;
  private String description;

}

[focus="7-8"]

JPA generates/expects columns for any additional instance, selecting appropriate types based on the declared variable type and the database in use.

In this example, it would expect name and description columns with types allowing them to contain variable length character data (Strings).


Camels to Snakes

If JPA encounters a class or instance variable name containing more than one word, it converts these from camelCase to snake_case.

Consider this:

public class VirtualPet {
  @Id
  @GeneratedValue
  private long id;
  private int numberOfLegs;
}

In this case, JPA would expect/generate a table named virtual_pet with columns named id and number_of_legs.


CRUD

  • Create: Create and persist (save) new objects.
  • Read: Read objects from the database.
  • Update: Persist changes made to objects' state (instance variables).
  • Delete: Delete objects from the database.

CRUD in Spring Data

Spring Data offers us the following interface:

public interface CrudRepository<T, ID extends Serializable>
    extends Repository<T, ID> {

where:

  • T is the type of entity managed by this repository
  • ID is the type of its id

We need only extend this interface, specifying type parameters, to have Spring Data automatically generate methods for basic CRUD operations!

What's this Serializable business?

Serializable is an interface indicating that instances of a class can be serialized, meaning their state can be represented by a series of bytes (marshaled), or restored from a series of bytes (demarshaled) for transmission, writing to a file, etc. All of the primitive wrapper types as well as String are Serializable.


So?

CrudRepository defines several methods that Spring Data will implement for us at runtime.

Some of the highlights:

C,R,U,D? method does what?
Create (new)
or
Update (existing)
<S extends T> S save(S entity); Saves the entity. (Why <S extends T>?)
Read long count(); Returns the number of entities.
Read Iterable<T> findAll(); Returns an Iterable allowing access to all entities, retrieving them from the database as necessary.
Read T findOne(ID id); Finds an instance of the entity based on its id
Delete void delete(T entity); Deletes the entity.

{: .second-column-nowrap}


Back on Topic

For our Topic example, the repository interface would look like this:

import org.springframework.data.repository.CrudRepository;

public interface TopicRepository extends CrudRepository<Topic, Long> {
}

Topic is our @Entity type and its @Id is of type long. We used the Long wrapper type above because we can't specify primitives as type parameters.


Repositories are Topical

For TopicRepository, after assigning type parameters, the methods inherited from CrudRepository become:

method does what?
<S extends Topic> S save(S entity); Saves the topic.
long count(); Returns the number of topics in the db.
Iterable<Topic> findAll(); Returns an Iterable allowing access to all topics, retrieving them from the database as necessary.
Topic findOne(Long id); Finds the topic with id.
void delete(Topic entity); Deletes the topic.

{: .first-column-nowrap}

We can use all of these without implementing them!

Welcome to the magical world of Spring Data.


Visualizing SQL from JPA

You can see the work that JPA is doing for you inside of your console by adding the following two lines to src/main/resources/application.properties

spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

Relationships are Important

Our entities can relate to each other, which is extremely important. For example, an Article can have many Topics. This can be represented in Java as:

  @Entity
  public Article {
    @Id
    @GeneratedValue
    private Long id;
    private String title;
    private String content;

    @OneToMany(mappedBy = "article")
    private Collection<Topic> topics;
  }

One to Many

  @Entity
  public Article {
    @OneToMany(mappedBy = "article")
    private Collection<Topic> topics;
  }

A One to Many relationship relates two entities in a certain way. In this case Article has a One to Many relationship to Topic, meaning an Article can have any number of topics related to it. Many in this context means each Article can have between 0 and an infinite number of Topics related to it.


Many to One

  @Entity
  public Topic {
    @ManyToOne
    private Article article;
  }

Many to One is the inverse relationship of One to Many. We need to have these on the opposing side of the relationship so both our POJOs are aware of the relationship. Note the mappedBy is not present on this side. Placing the mappedBy on the wrong side is a very commmon mistake.


Many to Many

So far relationships have been one sided, as in one side holds multiple relations. But what if both sides need to hold multiple relations?

An example of this would be a book and its authors. A book can have many authors, however if the relationship was one to many, each book can still have many authors, but each author would be limited to writing one book. Obviously that wouldn't work, which is why we have Many to Many relationships. This way, a book can have many authors, and conversely an author can write many books.


Many to Many example

  @Entity
  public Book {
    @ManyToMany(mappedBy = "books")
    private Collection<Author> authors;
  }
  @Entity
  public Author {
    @ManyToMany
    private Collection<Book> books;
  }

Notice you only have the mappedBy on one side. You can choose either side to put the mappedBy on, but only should have it on one side.


Conclusion

JPA and Hibernate are really powerful abstractions that allow us to easily integrate relational databases into our Spring applications

Further reading