Week-3 Spring Data JPA and Hibernate[1]
Week-3 Spring Data JPA and Hibernate[1]
1.
o Project Name:
EmployeeManagementSystem o Group:
com.example
o Artifact: EmployeeManagementSystem o
Packaging: jar o Java Version: 17 (or
any supported version) o Dependencies:
▪ Spring Data JPA: For managing database operations.
▪ H2 Database: An in-memory database for quick testing and development. ▪
Spring Web: For creating RESTful APIs.
▪ Lombok: To reduce boilerplate code like getters, setters, constructors, etc.
2. Generate the project and download it. Extract the project and open it in your IDE (IntelliJ IDEA or
Eclipse).
Step 2: Configuring Application Properties
Once your project is set up, you need to configure the application.properties file to connect to the H2
database.
1. Locate the application.properties file:
This file is located in src/main/resources/application.properties. If it's not present, create one.
# H2 Database Configuration spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
• spring.datasource.url=jdbc:h2:mem
: Configures the H2 database in memory mode. testdb is the name of the database.
• spring.datasource.driverClassName=org.h2.Driver: Specifies the driver class for H2
database.
• spring.datasource.username=sa: Username for the database.
• spring.datasource.password=password: Password for the database.
• spring.jpa.database-platform=org.hibernate.dialect.H2Dialect: Configures the
dialect for H2 database with Hibernate.
• spring.h2.console.enabled=true: Enables the H2 database console, which can be
accessed via a web browser.
• spring.h2.console.path=/h2-console: Specifies the path to access the H2 console
(e.g., http://localhost:8080/h2-console). Final Steps:
1. Run the Application:
o Navigate to the EmployeeManagementSystemApplication.java file and run it as a
Spring Boot application.
o This will start the embedded server (Tomcat by default) and configure the H2
database.
2. Access the H2 Console:
o Open a web browser and go to http://localhost:8080/h2-console.
o Use the configured settings to connect:
▪ JDBC URL: jdbc:h2:mem:testdb
▪ Username: sa
▪ Password: password o Click “Connect,” and you can
now interact with the H2 database.
Business Scenario:
Define JPA entities for Employee and Department with appropriate relationships.
Employee Entity:
package com.example.employeemanagementsystem.entity;
@Entity
@Table(name = "employees")
@Data
@NoArgsConstructor @AllArgsConstructor
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "department_id", nullable = false)
private Department department;
}
Department Entity:
package com.example.employeemanagementsystem.entity;
import java.util.List;
@Entity
@Table(name = "departments")
@Data
@NoArgsConstructor @AllArgsConstructor
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "department")
private List<Employee> employees;
}
import com.example.employeemanagementsystem.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository; import
java.util.List;
package com.example.employeemanagementsystem.repository;
• JpaRepository<T, ID>: This interface provides JPA-related methods for standard CRUD
operations. T is the entity type, and ID is the type of the entity's primary key.
• Derived Query Methods: Spring Data JPA allows you to define methods in the repository
interface that follow a certain naming convention, like findBy, countBy, deleteBy, etc. Spring
Data will automatically generate the necessary query based on the method name.
Final Steps
1. Inject Repositories into Services: You can now inject these repositories into your service
classes to perform business logic operations.
2. Testing: Test the repositories by creating, reading, updating, and deleting entities via your
service layer or directly in a test class.
3. Extend Functionality: Add more custom queries or business logic as needed.
With these repositories in place, you can now efficiently perform CRUD operations on Employee and
Department entities in your Employee Management System.
EmployeeService
package com.example.employeemanagementsystem.service;
import java.util.List;
import java.util.Optional;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
// Read Employee by ID
public Optional<Employee> getEmployeeById(Long id) {
return employeeRepository.findById(id);
}
// Read All Employees
public List<Employee> getAllEmployees() {
return employeeRepository.findAll();
}
DepartmentService
package com.example.employeemanagementsystem.service;
import java.util.List;
import java.util.Optional;
@Service
public class DepartmentService {
@Autowired
private DepartmentRepository departmentRepository;
// Read Department by ID
public Optional<Department> getDepartmentById(Long id) {
return departmentRepository.findById(id);
}
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/employees")
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
// Get an Employee by ID
@GetMapping("/{id}")
public ResponseEntity<Employee> getEmployeeById(@PathVariable Long id) {
Optional<Employee> employee = employeeService.getEmployeeById(id);
return employee.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}
// Update an Employee
@PutMapping("/{id}")
public ResponseEntity<Employee> updateEmployee(@PathVariable Long id, @RequestBody
Employee employeeDetails) {
Optional<Employee> employee = employeeService.getEmployeeById(id);
if (employee.isPresent()) {
Employee existingEmployee = employee.get();
existingEmployee.setName(employeeDetails.getName());
existingEmployee.setEmail(employeeDetails.getEmail());
existingEmployee.setDepartment(employeeDetails.getDepartment());
Employee updatedEmployee = employeeService.saveEmployee(existingEmployee);
return ResponseEntity.ok(updatedEmployee);
} else {
return ResponseEntity.notFound().build();
}
}
// Delete an Employee
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteEmployee(@PathVariable Long id) {
Optional<Employee> employee = employeeService.getEmployeeById(id);
if (employee.isPresent()) { employeeService.deleteEmployee(id);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
}
DepartmentController
package com.example.employeemanagementsystem.controller;
import java.util.List;
import java.util.Optional;
@RestController
@RequestMapping("/api/departments")
public class DepartmentController {
@Autowired
private DepartmentService departmentService;
// Get a Department by ID
@GetMapping("/{id}")
public ResponseEntity<Department> getDepartmentById(@PathVariable Long id) {
Optional<Department> department = departmentService.getDepartmentById(id); return
department.map(ResponseEntity::ok).orElseGet(() -> ResponseEntity.notFound().build());
}
// Update a Department
@PutMapping("/{id}")
public ResponseEntity<Department> updateDepartment(@PathVariable Long id, @RequestBody
Department departmentDetails) {
Optional<Department> department = departmentService.getDepartmentById(id);
if (department.isPresent()) {
Department existingDepartment = department.get();
existingDepartment.setName(departmentDetails.getName());
Department updatedDepartment = departmentService.saveDepartment(existingDepartment);
return ResponseEntity.ok(updatedDepartment);
} else {
return ResponseEntity.notFound().build();
}
}
// Delete a Department
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteDepartment(@PathVariable Long id) {
Optional<Department> department = departmentService.getDepartmentById(id);
if (department.isPresent()) {
departmentService.deleteDepartment(id);
return ResponseEntity.noContent().build();
} else {
return ResponseEntity.notFound().build();
}
}
}
• RESTful Endpoints:
o @PostMapping: To create a new entity (Employee or
Department). o @GetMapping: To retrieve entities by ID
or list all entities.
o @PutMapping: To update an existing entity. o
@DeleteMapping: To delete an entity by ID.
• ResponseEntity: Provides a standardized way to return HTTP responses, including status
codes (e.g., 200 OK, 404 Not Found, 204 No Content).
Final Steps
1. Test the API Endpoints:
o Use tools like Postman or curl to send HTTP requests to the API endpoints. o
Ensure all CRUD operations work as expected.
2. Expand Functionality:
o Implement additional business logic as needed.
o Add validation, exception handling, and more complex queries or services. Your
Employee Management System now supports full CRUD operations via RESTful
endpoints for both employees and departments.
package com.example.employeemanagementsystem.repository;
import java.util.List;
package com.example.employeemanagementsystem.repository;
EmployeeRepository
package com.example.employeemanagementsystem.repository;
import com.example.employeemanagementsystem.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import
org.springframework.data.repository.query.Param;
import java.util.List;
DepartmentRepository
package com.example.employeemanagementsystem.repository;
import com.example.employeemanagementsystem.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository; import
org.springframework.data.jpa.repository.Query; import
org.springframework.data.repository.query.Param;
import java.util.List;
package com.example.employeemanagementsystem.entity;
@Entity
@Table(name = "employees")
@Data
@NoArgsConstructor
@AllArgsConstructor
@NamedQueries({
@NamedQuery(name = "Employee.findByDepartmentName",
query = "SELECT e FROM Employee e WHERE e.department.name = :departmentName"),
@NamedQuery(name = "Employee.findByEmailFragment",
query = "SELECT e FROM Employee e WHERE e.email LIKE :emailFragment")
})
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Department department;
}
package com.example.employeemanagementsystem.repository;
import com.example.employeemanagementsystem.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository; import
org.springframework.data.repository.query.Param;
import java.util.List;
To implement pagination and sorting in an Employee Management System, you typically use Spring
Data JPA if you're working with a Java-based backend. Here's a step-by-step guide to help you
implement these features.
Ensure that you have the following dependencies in your pom.xml (if using Maven):
<dependencies>
<!-- Other dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
2. Entity Class
Assume you have an Employee entity:
import javax.persistence.Entity; import
javax.persistence.GeneratedValue; import
javax.persistence.GenerationType; import
javax.persistence.Id;
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private String name; private String
department; private double salary;
// Getters and Setters
}
3. Repository Interface
Your repository interface should extend JpaRepository, which provides methods for pagination and
sorting:
import org.springframework.data.jpa.repository.JpaRepository; import
org.springframework.stereotype.Repository;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
}
4. Service Layer
In the service layer, create a method to fetch paginated and sorted results:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import
org.springframework.data.domain.PageRequest; import
org.springframework.data.domain.Pageable; import
org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
public Page<Employee> getAllEmployees(int page, int size, String sortBy, String sortDir) {
Sort sort = sortDir.equalsIgnoreCase(Sort.Direction.ASC.name()) ? Sort.by(sortBy).ascending()
: Sort.by(sortBy).descending();
@RestController
public class EmployeeController {
@Autowired
private EmployeeService employeeService;
@GetMapping("/employees") public
Page<Employee> getEmployees(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id") String sortBy,
@RequestParam(defaultValue = "asc") String sortDir) {
http://localhost:8080/employees?page=0&size=5&sortBy=name&sortDir=desc
This will return the first page of employees, with 5 employees per page, sorted by name in
descending order.
7. Handling Edge Cases (Optional)
• Validation: Validate the page, size, sortBy, and sortDir parameters to ensure they are within
acceptable ranges/values.
• Error Handling: Implement error handling to manage scenarios like invalid sorting fields or
directions.
8. Front-End Considerations
If you're working with a front-end, you'll typically display pagination controls (like "Next", "Previous",
and page numbers) and allow the user to choose sorting options.
To enable entity auditing in your Employee Management System using Spring Data JPA, you can
follow these steps:
1. Add Dependencies
Ensure your pom.xml has the necessary dependencies:
<dependencies>
<!-- Other dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
</dependencies>
2. Enable Auditing in the Application
You need to enable auditing in your Spring Boot application. To do this, annotate your main
application class with @EnableJpaAuditing.
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import
org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication @EnableJpaAuditing
public class EmployeeManagementApplication {
public static void main(String[] args) {
SpringApplication.run(EmployeeManagementApplication.class, args);
}
}
3. Create Auditable Entity Base Class
Create a base class that other entities can inherit from. This class will include common auditing fields.
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class) public
abstract class Auditable {
@CreatedDate
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
import javax.persistence.Entity;
@Entity
public class Employee extends Auditable {
@CreatedBy
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
import javax.persistence.Entity;
@Entity
public class Department extends Auditable {
@CreatedBy
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
import java.util.Optional;
@Configuration
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public Optional<String> getCurrentAuditor() {
// Here you can fetch the current logged-in user, for now returning a fixed value
return Optional.of("admin"); // Replace with the actual user fetching logic
}
}
6. Register AuditorAware Bean
Register the AuditorAware implementation as a bean in your configuration.
import org.springframework.context.annotation.Bean; import
org.springframework.context.annotation.Configuration; import
org.springframework.data.domain.AuditorAware;
@Configuration
public class AuditConfig {
@Bean
public AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
}
7. Database Changes
Make sure your database schema supports these auditing fields. If you are using an ORM tool like
Hibernate, the schema will be updated automatically if hibernate.ddl-auto is set to update or create.
8. Testing the Auditing
• Create or Update Entities: When you create or modify an Employee or Department, the
fields createdDate, lastModifiedDate, createdBy, and lastModifiedBy should be automatically
populated.
• Verify Data: Check the database to ensure that these fields are being correctly updated.
In the Employee Management System, projections allow you to fetch specific subsets of data from
your entities rather than retrieving entire entities. This is especially useful when you want to optimize
performance by limiting the amount of data retrieved from the database.
1. Interface-Based Projections
Interface-based projections are a simple way to define a subset of fields you want to retrieve from an
entity.
1.1 Define Interface-Based Projection for Employee
Create an interface that defines the fields you want to project. java
Copy code
public interface EmployeeNameAndDepartment {
String getName();
String getDepartment();
}
1.2 Define Interface-Based Projection for Department
Similarly, define a projection for the Department entity: java
Copy code
public interface DepartmentNameAndId {
Long getId();
String getName();
}
1.3 Use Projections in Repository
In your repository interface, add methods that return these projections:
java
Copy code
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import
org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
List<DepartmentNameAndId> findBy();
}
2. Class-Based Projections
Class-based projections use a DTO (Data Transfer Object) class to represent the subset of data. You
can use constructor expressions to achieve this.
2.1 Define DTO for Employee
Create a DTO class that will hold the projected data. java
Copy code
public class EmployeeDTO {
import java.util.List;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
String getDepartment();
}
3.2 Use the Projection in Repository java
Copy code
import org.springframework.data.jpa.repository.JpaRepository; import
org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
import java.util.List;
@Service
public class EmployeeService {
@Autowired
private EmployeeRepository employeeRepository;
To customize the data source configuration in your Employee Management System and manage
multiple data sources, follow these steps:
1. Spring Boot Auto-Configuration
Spring Boot simplifies data source configuration with auto-configuration. By default, Spring Boot can
auto-configure a DataSource bean based on the properties defined in application.properties or
application.yml.
1.1 Default Data Source Configuration
If you only have one data source, you can configure it in the application.properties file:
properties Copy
code
spring.datasource.url=jdbc:mysql://localhost:3306/employee_db
spring.datasource.username=root spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
With this configuration, Spring Boot automatically creates a DataSource bean and uses it for all
database operations.
2. Externalizing Configuration
Externalizing configuration means moving your configurations, such as database credentials, out of
your codebase. This is typically done using application.properties or application.yml files.
2.1 Externalize Configuration with application.properties
You can externalize the configuration as shown above in application.properties. Additionally, you can
use environment variables or externalized configuration files to override these values at runtime. For
example, you can define placeholders:
properties Copy
code
spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/employee_db}
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD:yourpassword}
You can then provide the actual values via environment variables or an external properties file,
allowing different environments (dev, test, prod) to use different configurations.
3. Managing Multiple Data Sources
In some applications, you may need to manage multiple data sources, such as one for employee data
and another for department data.
3.1 Define Multiple Data Sources in application.properties
First, define properties for each data source in application.properties:
properties Copy
code
# Primary Data Source (Employee)
spring.datasource.employee.url=jdbc:mysql://localhost:3306/employee_db
spring.datasource.employee.username=root spring.datasource.employee.password=yourpassword
spring.datasource.employee.driver-class-name=com.mysql.cj.jdbc.Driver
@Primary
@Bean(name = "employeeDataSource")
@ConfigurationProperties(prefix = "spring.datasource.employee")
public DataSource employeeDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "employeeEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean employeeEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new
LocalContainerEntityManagerFactoryBean(); em.setDataSource(employeeDataSource());
em.setPackagesToScan(new String[]{"com.example.employee.entity"}); // Entity package
@Primary
@Bean(name = "employeeTransactionManager")
public PlatformTransactionManager employeeTransactionManager(EntityManagerFactory
employeeEntityManagerFactory) {
return new JpaTransactionManager(employeeEntityManagerFactory);
}}
Then, create a similar configuration for the Department data source:
java
Copy code
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder; import
org.springframework.context.annotation.Bean; import
org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager; import
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import
org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import
org.springframework.transaction.PlatformTransactionManager;
import javax.persistence.EntityManagerFactory; import
javax.sql.DataSource;
@Configuration @EnableJpaRepositories(
basePackages = "com.example.department.repository", // Specify the repository package for
department
entityManagerFactoryRef = "departmentEntityManagerFactory",
transactionManagerRef = "departmentTransactionManager"
)
public class DepartmentDataSourceConfig {
@Bean(name = "departmentDataSource")
@ConfigurationProperties(prefix = "spring.datasource.department")
public DataSource departmentDataSource() { return
DataSourceBuilder.create().build();
}
@Bean(name = "departmentEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean departmentEntityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new
LocalContainerEntityManagerFactoryBean(); em.setDataSource(departmentDataSource());
em.setPackagesToScan(new String[]{"com.example.department.entity"}); // Entity package
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
@Bean(name = "departmentTransactionManager")
public PlatformTransactionManager departmentTransactionManager(EntityManagerFactory
departmentEntityManagerFactory) {
return new JpaTransactionManager(departmentEntityManagerFactory);
}
}
4. Using Multiple Data Sources in Your Application
With the above configurations, Spring Boot knows which DataSource to use for each set of
repositories and entities. The @Primary annotation is used to indicate the primary DataSource when
only one is needed in a particular context.
To leverage Hibernate-specific features in your Employee Management System, you can use
Hibernate-specific annotations to customize entity mappings, configure the Hibernate dialect and
properties for optimal performance, and implement batch processing for bulk operations. Below are
detailed instructions on how to accomplish these tasks.
1. Hibernate-Specific Annotations
Hibernate offers specific annotations that can help you customize your entity mappings beyond the
standard JPA annotations.
1.1 @BatchSize Annotation
This annotation helps reduce the number of queries by fetching related entities in batches.
Example: java
Copy code
import org.hibernate.annotations.BatchSize;
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "department")
@BatchSize(size = 10) // Fetch 10 employees at a time
private List<Employee> employees;
@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "department")
@Fetch(FetchMode.JOIN) // Fetch employees using a join query
private List<Employee> employees;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import
javax.persistence.GenerationType; import
javax.persistence.Id;
import java.util.UUID;
@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Type(type = "uuid-char")
private UUID uniqueId;
import javax.persistence.EntityManager;
import java.util.List;
@Service
public class EmployeeService {
@Autowired
private EntityManager entityManager;
@Transactional
public void batchInsertEmployees(List<Employee> employees) {
int batchSize = 20; for (int i = 0; i < employees.size(); i++) {
entityManager.persist(employees.get(i));
if (i % batchSize == 0 && i > 0) {
entityManager.flush(); entityManager.clear();
}
}
entityManager.flush();
entityManager.clear();
}}
This example uses EntityManager to persist employees in batches, flushing and clearing the
persistence context periodically to prevent memory overflow.