In this part, we will cover the fundamentals of JPA and compare it to previous attempts at persistence frameworks used to store data in databases.
- How does it compare to JDBC?
- How does it compare to Spring JDBC?
We will understand the basic concepts of JPA
- Entities
- Mappings
- Relationships
We will create a Spring Boot project with Spring Initializr and create an Entity and Repository to get a big picture of JPA, Spring Data and Spring Data JPA.
JPA and Hibernate in 10 Steps with H2
Steps
- Step 1 : Object Relational Impedence Mismatch - Understanding the problem that JPA solves
- Step 2 : World before JPA - JDBC, Spring JDBC and myBatis
- Step 3 : Introduction to JPA
- Step 4 : Creating a JPA Project using Spring Initializr
- Step 5 : Defining a JPA Entity - User
- Step 6 : Defining a Service to manage the Entity - UserService and EntityManager
- Step 7 : Using a Command Line Runner to save the User to Database
- Step 8 : Magic of Spring Boot and In Memory Database H2
- Step 9 : Introduction to Spring Data JPA
- Step 10 : More JPA Repository : findById and findAll
Step 1 : Object Relational Impedence Mismatch - Understanding the problem that JPA solves
What problem does JPA solve?
Java is an object oriented programming language & Relational databases are used to store data.The way we design objects is different from the way the relational databases are designed.
- Mismatch in Design - Object Oriented vs Normal Forms
- Mismatches in naming - Java Fields vs Table Columns
- Relationships between objects are expressed in a different way compared with relationship between tables.
Must Read - http://www.springboottutorial.com/introduction-to-jpa-with-spring-boot-data-jpa
Step 2 : World before JPA - JDBC, Spring JDBC and myBatis
Before the emergence of JPA and Hibernate, we relied on JDBC, Spring JDBC and myBatis to interact with Relational Databases.
- JDBC, Spring JDBC and myBatis involve writing queries.
- In big application, queries can become complex. Especially when we retrieve data from multiple tables.
- This creates a problem whenever there are changes in the structure of the database.
JDBC
Lot of code
Connection connection = datasource.getConnection();
PreparedStatement st = connection.prepareStatement(
"Update todo set user=?, desc=?, target_date=?, is_done=? where id=?");
st.setString(1, todo.getUser());
st.setString(2, todo.getDesc());
st.setTimestamp(3, new Timestamp(
todo.getTargetDate().getTime()));
st.setBoolean(4, todo.isDone());
st.setInt(5, todo.getId());
st.execute();
st.close();
connection.close();
Spring JDBC
Simpler than JDBC but still queries
jdbcTemplate
.update("Update todo set user=?, desc=?, target_date=?, is_done=? where id=?",
todo.getUser(),
todo.getDesc(),
new Timestamp(todo.getTargetDate().getTime()),
todo.isDone(),
todo.getId());
myBatis
MyBatis removes the need for manually writing code to set parameters and retrieve results. It provides simple XML or Annotation based configuration to map Java POJOs to database.
@Override
@Update("Update todo set user=#{user}, desc=#{desc}, target_date=#{targetDate}, is_done=#{isDone} where id=#{id}")
public void updateTodo(Todo todo) throws SQLException;
Step 03 : Introduction to JPA
Hibernate (and ORM - Object Relational Mapping) emerged as a result of this big idea:
Instead of writing queries by hand, what if we can map tables(relations) to java objects and generate queries
JPA came in later when Java EE attempted to standize ORM frameworks.
Hibernate went on to become the preferred implementation for JPA (in addition to offering excellent additional features)
Step 4 : Creating a JPA Project using Spring Initializr
Creating a Spring Project with Spring Initializr is a cake walk.
Spring Initializr http://start.spring.io/ is great tool to bootstrap your Spring Boot projects.
As shown in the image above, following steps have to be done
- Launch Spring Initializr and choose the following
- Choose
com.in28minutes.learning.jpa
as Group - Choose
jpa-in-10-steps
as Artifact - Choose Following Dependencies
- Web
- JPA
- H2
- Choose
- Click Generate Project.
- Import the project into Eclipse.
- If you want to understand all the files that are part of this project, you can go here.
JPA vs Hibernate
Hibernate is one of the most popular ORM frameworks.
JPA defines the specification. It is an API.
- How do you define entities?
- How do you map attributes?
- How do you map relationships between entities?
- Who manages the entities?
- Hibernate is one of the popular implementations of JPA.
Hibernate recognises the mappings we create between objects and tables. It guarantees that data is stored and retrieved from the database in accordance with the mappings.
Hibernate extends JPA with extra functionality. But, relying on them would bind you to Hibernate. You cannot switch to another JPA implementation, such as Toplink.
Step 5 : Defining a JPA Entity - User
Let’s define a User Entity with a primary key.
package com.in28minutes.learning.jpa.jpain10steps.entity;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
//Table - User
@Entity
public class User {
@Id
@GeneratedValue
private long id;
private String name;
private String role;
protected User() {
}
public User(String name, String role) {
super();
this.name = name;
this.role = role;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getRole() {
return role;
}
@Override
public String toString() {
return String.format("User [id=%s, name=%s, role=%s]", id, name, role);
}
}
Step 6 : Defining a Service to manage the Entity - UserService and EntityManager
Lets define a repository to manage the User entity.
package com.in28minutes.learning.jpa.jpain10steps.service;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import org.springframework.stereotype.Repository;
import com.in28minutes.learning.jpa.jpain10steps.entity.User;
@Repository
@Transactional
public class UserDAOService {
@PersistenceContext
private EntityManager entityManager;
public long insert(User user){
entityManager.persist(user);
return user.getId();
}
}
Important things to note
- @Repository: Spring Annotation to indicate that this component handles storing data to a data store.
- @Transactional: Spring annotation used to simplify transaction management
- @PersistenceContext: A persistence context handles a set of entities which hold data to be persisted in some persistence store (e.g. a database). In particular, the context is aware of the different states an entity can have (e.g. managed, detached) in relation to both the context and the underlying persistence store.
- EntityManager : Interface used to interact with the persistence context.
- entityManager.persist(user): Make user entity instance managed and persistent i.e. saved to database.
Notes from http://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#createNamedQuery(java.lang.String)
An EntityManager instance is associated with a persistence context. A persistence context is a set of entity instances in which for any persistent entity identity there is a unique entity instance. Within the persistence context, the entity instances and their lifecycle are managed. The EntityManager API is used to create and remove persistent entity instances, to find entities by their primary key, and to query over entities.
The set of entities that can be managed by a given EntityManager instance is defined by a persistence unit. A persistence unit defines the set of all classes that are related or grouped by the application, and which must be colocated in their mapping to a single database.
Step 7 : Using a Command Line Runner to save the User to Database
CommandLineRunner interface is used to indicate that this bean has to be run as soon as the Spring application context is initialized.
package com.in28minutes.learning.jpa.jpain10steps;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.in28minutes.learning.jpa.jpain10steps.entity.User;
import com.in28minutes.learning.jpa.jpain10steps.service.UserDAOService;
@Component
public class UserDaoServiceCommandLineRunner implements CommandLineRunner{
private static final Logger log =
LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class);
@Autowired
private UserDAOService userDaoService;
@Override
public void run(String... arg0) throws Exception {
var user = new User("Jack", "Admin");
//New User is created : User [id=1, name=Jack, role=Admin]
long insert = userDaoService.insert(user);
log.info("New User is created : " + user);
}
}
Step 8 : Magic of Spring Boot and In Memory Database H2
The simple answer is all the magic is happening because of Spring Boot and Starter Projects.
Recommended Reading
- Spring Boot Auto Configuration - http://www.springboottutorial.com/spring-boot-auto-configuration
- Spring Boot Starter Project - http://www.springboottutorial.com/spring-boot-starter-projects
- Spring Boot Starter Parent - http://www.springboottutorial.com/spring-boot-starter-parent
H2 Console - We will enable h2 console in /src/main/resources/application.properties
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.data.jpa.repositories.bootstrap-mode=default
FAQ
- Where is the database created?
- In Memory - Using H2
- What schema is used to create the tables?
- Created based on the entities defined
- Where are the tables created?
- Created based on the entities defined
- In Memory - Using H2
- Can I see the data in the database?
- http://localhost:8080/h2-console
- Use db url jdbc:h2:mem:testdb
- Where is Hibernate coming in from?
- Through Spring Data JPA Starter
- How is a datasource created?
- Through Spring Boot Auto Configuration
Magic of Spring Boot and in Memory Database
- Zero project setup or infrastructure
- Zero Configuration
- Zero Maintainance
- Easy to use for Learning and Unit Tests
- Simple Configuration to switch to a real database
Step 9 : Introduction to Spring Data JPA
Spring Data aims to provide a consistent model for accessing data from different kinds of data stores.
UserService (which we created earlier) contains a lot of redundant code which can be easily generalized. Spring Data aims to simplify the code below.
package com.in28minutes.learning.jpa.jpain10steps.service;
import org.springframework.data.jpa.repository.JpaRepository;
import com.in28minutes.learning.jpa.jpain10steps.entity.User;
public interface UserRepository extends JpaRepository<User, Long>{
}
package com.in28minutes.learning.jpa.jpain10steps;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.in28minutes.learning.jpa.jpain10steps.entity.User;
import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository;
@Component
public class UserRepositoryCommandLineRunner implements CommandLineRunner{
private static final Logger log =
LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class);
@Autowired
private UserRepository userRepository;
@Override
public void run(String... arg0) throws Exception {
var user = new User("Jill", "Admin");
userRepository.save(user);
log.info("New User is created : " + user);
}
}
Step 10 : More JPA Repository : findById and findAll
package com.in28minutes.learning.jpa.jpain10steps;
import java.util.List;
import java.util.Optional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import com.in28minutes.learning.jpa.jpain10steps.entity.User;
import com.in28minutes.learning.jpa.jpain10steps.service.UserRepository;
@Component
public class UserRepositoryCommandLineRunner implements CommandLineRunner{
private static final Logger log =
LoggerFactory.getLogger(UserRepositoryCommandLineRunner.class);
@Autowired
private UserRepository userRepository;
@Override
public void run(String... arg0) throws Exception {
var user = new User("Jill", "Admin");
userRepository.save(user);
log.info("New User is created : " + user);
Optional<User> userWithIdOne = userRepository.findById(1L);
log.info("User is retrived : " + userWithIdOne);
List<User> users = userRepository.findAll();
log.info("All Users : " + users);
}
}
log
HibernateJpaAutoConfiguration matched:
- @ConditionalOnClass found required classes 'org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean', 'jakarta.persistence.EntityManager'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
- HibernateEntityManager found class 'org.hibernate.ejb.HibernateEntityManager' (HibernateJpaAutoConfiguration.HibernateEntityManagerCondition)
DataSourceAutoConfiguration matched:
- @ConditionalOnClass found required classes 'jakarta.sql.DataSource', 'org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType'; @ConditionalOnMissingClass did not find unwanted class (OnClassCondition)
JpaBaseConfiguration#entityManagerFactory matched:
- @ConditionalOnMissingBean (types: org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean,jakarta.persistence.EntityManagerFactory; SearchStrategy: all) did not find any beans (OnBeanCondition)
JpaBaseConfiguration#transactionManager matched:
- @ConditionalOnMissingBean (types: org.springframework.transaction.PlatformTransactionManager; SearchStrategy: all) did not find any beans (OnBeanCondition)