- Published on
5 - Spring Data Access
- Authors

- Name
- Samreach YAN
Table of Contents
- Docker Compose for PostgreSQL
- Spring Data JPA
- Hibernate Basics
- Repository Pattern
- Query Methods
- Transaction Management
- DTO Pattern
- Best Practices
- Learning Resources
Docker Compose for PostgreSQL
Let's start by setting up a PostgreSQL database using Docker Compose.
# docker-compose.yml
version: '3.8'
services:
postgres:
image: postgres:13
container_name: spring_postgres
environment:
POSTGRES_USER: springuser
POSTGRES_PASSWORD: springpass
POSTGRES_DB: springdb
ports:
- '5432:5432'
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
To start the container:
docker-compose up -d
Spring Data JPA
Spring Data JPA simplifies data access by reducing boilerplate code. Add these dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
Configure your application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/springdb
spring.datasource.username=springuser
spring.datasource.password=springpass
spring.datasource.driver-class-name=org.postgresql.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
Hibernate Basics
Hibernate is the JPA implementation used by Spring Data JPA. Here's a basic entity example:
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String email;
// Standard getters and setters
// Constructors
// equals() and hashCode()
}
Key annotations:
@Entity: Marks class as a JPA entity@Table: Specifies the table name@Id: Marks the primary key@GeneratedValue: Configures ID generation strategy@Column: Configures column properties
Repository Pattern
Spring Data JPA provides repository interfaces to reduce boilerplate code:
public interface UserRepository extends JpaRepository<User, Long> {
// Basic CRUD operations are provided automatically
}
Usage example in a service:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public List<User> getAllUsers() {
return userRepository.findAll();
}
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}
Query Methods
Spring Data JPA supports several ways to define queries:
1. Method Name Queries
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByUsername(String username);
List<User> findByEmailContaining(String emailPart);
List<User> findByUsernameAndEmail(String username, String email);
}
2. @Query Annotation
@Query("SELECT u FROM User u WHERE u.email LIKE %?1%")
List<User> findByEmailLike(String emailPart);
@Query(value = "SELECT * FROM users WHERE username = ?1", nativeQuery = true)
List<User> findByUsernameNative(String username);
3. JPA Criteria API
For complex dynamic queries, use Specifications:
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
}
// Usage
List<User> users = userRepository.findAll(
(root, query, cb) -> cb.like(root.get("email"), "%gmail%")
);
Transaction Management
Spring provides declarative transaction management:
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
private final UserRepository userRepository;
private final AuditLogRepository auditLogRepository;
@Transactional(rollbackFor = Exception.class)
public User createUserWithAudit(User user) {
User savedUser = userRepository.save(user);
auditLogRepository.save(new AuditLog("USER_CREATED", savedUser.getId()));
return savedUser;
}
@Transactional(readOnly = true)
public List<User> getAllActiveUsers() {
return userRepository.findByActiveTrue();
}
}
Key points:
@Transactionalon class applies to all methods- Can override at method level
readOnly=trueoptimizes read operations- By default, rolls back on runtime exceptions
DTO Pattern
DTOs (Data Transfer Objects) help separate persistence models from API contracts:
Request/Response DTOs
public class UserRequest {
@NotBlank
private String username;
@Email
@NotBlank
private String email;
// Getters and setters
}
public class UserResponse {
private Long id;
private String username;
private String email;
private Instant createdAt;
// Getters and setters
}
Using ModelMapper
Add dependency:
<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.4.4</version>
</dependency>
Configuration:
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setMatchingStrategy(MatchingStrategies.STRICT);
return modelMapper;
}
}
Service example with DTOs:
@Service
@RequiredArgsConstructor
public class UserService {
private final UserRepository userRepository;
private final ModelMapper modelMapper;
public UserResponse createUser(UserRequest userRequest) {
User user = modelMapper.map(userRequest, User.class);
User savedUser = userRepository.save(user);
return modelMapper.map(savedUser, UserResponse.class);
}
public UserResponse getUserById(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
return modelMapper.map(user, UserResponse.class);
}
}
Best Practices
Entity Design
- Keep entities focused on persistence concerns
- Avoid business logic in entities
- Use Lombok judiciously (
@Datacan cause issues with JPA)
Repository Layer
- Keep repositories focused on data access
- Use custom repository interfaces for complex queries
- Consider query DSLs for complex dynamic queries
Transaction Management
- Keep transactions short
- Be explicit about rollback behavior
- Use
readOnly=truefor read operations
DTO Pattern
- Always use DTOs for API boundaries
- Consider using different DTOs for request vs response
- Use mapping libraries carefully (avoid leaking sensitive data)
Performance
- Use pagination (
Pageable) for large datasets - Consider lazy loading vs eager loading
- Use entity graphs or
@EntityGraphfor query optimization
- Use pagination (
Testing
- Use
@DataJpaTestfor repository tests - Test transactions behavior
- Consider Testcontainers for integration testing
- Use
Learning Resources
Official Documentation
Books
- "Spring Data: Modern Data Access for Enterprise Java" by Mark Pollack
- "Java Persistence with Hibernate" by Christian Bauer and Gavin King
Online Courses
- Spring Data JPA on Udemy (by Chad Darby)
- Hibernate and JPA Fundamentals on Pluralsight
Sample Projects
Community
- Stack Overflow (spring-data-jpa tag)
- Spring Community Forum
Remember that mastering Spring Data Access technologies requires practice. Start with simple CRUD operations, then gradually move to more complex scenarios like transactions, query optimization, and advanced mapping strategies.