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
<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>
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
Basic Configuration
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)
Annotation | Purpose | Example |
---|---|---|
@Entity | Marks 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") |
@Id | Defines the primary key. | @Id private Long id; |
@GeneratedValue | Key generation strategy (IDENTITY , SEQUENCE , AUTO ). | @GeneratedValue(strategy = GenerationType.IDENTITY) |
@Column | Customizes column name, nullability, uniqueness, length, type. | @Column(name = "full_name", length = 100) |
@Transient | Excludes a field from DB mapping. | @Transient private String temp; |
@Enumerated | Maps enums (ORDINAL or STRING ). | @Enumerated(EnumType.STRING) |
@Lob | Maps large data types (BLOB/CLOB). | @Lob private String content; |
@Temporal | Specifies 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; |
@Version | Used for optimistic locking. | @Version private Long version; |
2. Relationship Mapping Annotations
Annotation | Purpose | Example |
---|---|---|
@OneToOne | One-to-one relationship. | @OneToOne(mappedBy = "profile") |
@OneToMany | One-to-many relationship. | @OneToMany(mappedBy = "user") |
@ManyToOne | Many-to-one relationship. | @ManyToOne @JoinColumn(name = "user_id") |
@ManyToMany | Many-to-many relationship. | @ManyToMany @JoinTable(...) |
@JoinColumn | Defines foreign key column. | @JoinColumn(name = "profile_id") |
@JoinTable | Customizes join table for @ManyToMany . | @JoinTable(name = "user_roles", joinColumns = @JoinColumn(name="user_id"), inverseJoinColumns = @JoinColumn(name="role_id")) |
@MapsId | Shares primary key with another entity. | @OneToOne @MapsId |
3. Repository & Query Annotations
Annotation | Purpose | Example |
---|---|---|
@Repository | Marks a class as a Spring Data repository; translates exceptions to DataAccessException . | @Repository public interface UserRepository extends JpaRepository<User, Long> |
@Query | Defines custom JPQL/Native queries. | @Query("SELECT u FROM User u WHERE u.email = :email") |
@Param | Passes parameters into a query. | @Query("...") User findByEmail(@Param("email") String email); |
@Modifying | Marks an update/delete query. | @Modifying @Query("UPDATE User u SET u.active = false") |
@Transactional | Transaction management. | @Transactional(readOnly = true) |
@EntityGraph | Defines fetch graph to avoid N+1 queries. | @EntityGraph(attributePaths = {"roles"}) |
@Lock | Applies 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
orSet
. - @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
andtry-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());
}
}