Skip to main content

Java Persistence API

  • JPA: A standard API for object-relational mapping (ORM) in Java.
  • Hibernate: A popular ORM framework implementing JPA, offering extra features like caching, batch processing, and more.
  • Spring Data JPA: A Spring module providing an abstraction layer for JPA, simplifying repository creation and data access patterns.

Setting Up JPA

Adding Dependencies

Maven - pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
Gradle - build.gradle
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'

Basic Configuration

application.properties
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=update

Annotations

1. Entity Mapping Annotations (JPA/Hibernate)

AnnotationPurposeExample
@EntityMarks a class as a JPA entity mapped to a database table.@Entity public class User {}
@Table(name = "users")Specifies table name, schema, and constraints.@Table(name = "users", schema = "public")
@IdDefines the primary key.@Id private Long id;
@GeneratedValueKey generation strategy (IDENTITY, SEQUENCE, AUTO).@GeneratedValue(strategy = GenerationType.IDENTITY)
@ColumnCustomizes column name, nullability, uniqueness, length, type.@Column(name = "full_name", length = 100)
@TransientExcludes a field from DB mapping.@Transient private String temp;
@EnumeratedMaps enums (ORDINAL or STRING).@Enumerated(EnumType.STRING)
@LobMaps large data types (BLOB/CLOB).@Lob private String content;
@TemporalSpecifies date/time type (DATE, TIME, TIMESTAMP).@Temporal(TemporalType.TIMESTAMP)
@CreationTimestamp (Hibernate)Auto-sets creation time.@CreationTimestamp private LocalDateTime createdAt;
@UpdateTimestamp (Hibernate)Auto-sets update time.@UpdateTimestamp private LocalDateTime updatedAt;
@VersionUsed for optimistic locking.@Version private Long version;

2. Relationship Mapping Annotations

AnnotationPurposeExample
@OneToOneOne-to-one relationship.@OneToOne(mappedBy = "profile")
@OneToManyOne-to-many relationship.@OneToMany(mappedBy = "user")
@ManyToOneMany-to-one relationship.@ManyToOne @JoinColumn(name = "user_id")
@ManyToManyMany-to-many relationship.@ManyToMany @JoinTable(...)
@JoinColumnDefines foreign key column.@JoinColumn(name = "profile_id")
@JoinTableCustomizes join table for @ManyToMany.@JoinTable(name = "user_roles", joinColumns = @JoinColumn(name="user_id"), inverseJoinColumns = @JoinColumn(name="role_id"))
@MapsIdShares primary key with another entity.@OneToOne @MapsId

3. Repository & Query Annotations

AnnotationPurposeExample
@RepositoryMarks a class as a Spring Data repository; translates exceptions to DataAccessException.@Repository public interface UserRepository extends JpaRepository<User, Long>
@QueryDefines custom JPQL/Native queries.@Query("SELECT u FROM User u WHERE u.email = :email")
@ParamPasses parameters into a query.@Query("...") User findByEmail(@Param("email") String email);
@ModifyingMarks an update/delete query.@Modifying @Query("UPDATE User u SET u.active = false")
@TransactionalTransaction management.@Transactional(readOnly = true)
@EntityGraphDefines fetch graph to avoid N+1 queries.@EntityGraph(attributePaths = {"roles"})
@LockApplies locking (PESSIMISTIC_WRITE, OPTIMISTIC).@Lock(LockModeType.PESSIMISTIC_WRITE)

4. Other Useful Annotations

  • @NamedQuery / @NamedNativeQuery: Predefined JPQL/SQL queries.
  • @SqlResultSetMapping: Maps native query results.
  • @Inheritance, @DiscriminatorColumn: Entity inheritance mapping.

Creating Entities Examples

Define JPA Entity Classes

  • Annotate your class with @Entity.
  • Add an @Id annotation to the primary key field.
  • Use @GeneratedValue(strategy = GenerationType.IDENTITY) for auto-generated primary keys.
import javax.persistence.*;

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;

// getters and setters
}

Mapping Relationships

  • @OneToOne: A one-to-one association.
  • @OneToMany: One-to-many association, use a List or Set.
  • @ManyToOne: Many-to-one association.
  • @ManyToMany: Many-to-many association.

Example for @OneToMany:

@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;

@OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
private List<Employee> employees;

// getters and setters
}

Using Repositories

Define Repository Interfaces

Spring Data JPA provides CRUD and query methods out of the box.

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByName(String name);
}

Custom Queries with @Query

For complex queries, use the @Query annotation with JPQL or native SQL.

@Query("SELECT u FROM User u WHERE u.email = ?1")
User findByEmail(String email);

Best Practices

  • Use DTOs for Data Transfer: to avoid returning entire entities and exposing database details, use Data Transfer Objects (DTOs) or projections.

  • Use Lazy Loading Appropriately: for relationships, use @OneToMany(fetch = FetchType.LAZY) and load data only when needed.

  • Optimize with Batch Fetching: prevent the N+1 select problem by setting batch fetching in application.properties:

    spring.jpa.properties.hibernate.default_batch_fetch_size=10
  • Avoid Long Transactions: limit transaction scope by keeping database interactions brief. Perform complex calculations outside the transaction boundary when possible.

  • Implement Proper Exception Handling: use @Transactional and try-catch blocks to manage exceptions effectively.

Transaction Management

Transactional Methods

Annotate methods with @Transactional to manage database transactions.

@Transactional
public void updateUser(User user) {
userRepository.save(user);
}

Propagation and Isolation Levels

Customize transaction behavior:

  • Propagation: REQUIRED, REQUIRES_NEW, etc.
  • Isolation: READ_COMMITTED, REPEATABLE_READ, etc.
@Transactional(isolation = Isolation.SERIALIZABLE)
public void processTransaction() {
// transactional code here
}

Performance Tuning

Enable Caching

Use the second-level cache for entities that don’t change often.

spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory

Optimize Fetching Strategies

Use @EntityGraph to fetch only necessary data in complex queries.

Example:

@EntityGraph(attributePaths = {"department", "roles"})
List<User> findAllUsersWithDepartmentAndRoles();

Database Indexing

Ensure that your frequently queried fields are indexed to speed up data retrieval.

Error Handling and Debugging

Log SQL Statements

Enable SQL logging to debug queries:

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

Handle Exceptions Properly

Use @ControllerAdvice to centralize exception handling in Spring applications.

Example:

@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(EntityNotFoundException.class)
public ResponseEntity<String> handleNotFoundException(EntityNotFoundException ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
}
}

Testing JPA with Spring Boot

Unit Testing Repositories

@RunWith(SpringRunner.class)
@DataJpaTest // lightweight testing of JPA repositories.
public class UserRepositoryTest {
@Autowired
private UserRepository userRepository;

@Test
public void testFindByName() {
userRepository.save(new User("John", "john@example.com"));
List<User> users = userRepository.findByName("John");
assertEquals(1, users.size());
}
}