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 (String
s).
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 repositoryID
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