Employee Management in Spring Boot: Pagination, Status Toggle, Editing, and Automated Common Field Population
@GetMapping("/page")
@ApiOperation("Employee Pagination Query")
public Result<PageResult> listByPage(StaffQueryDTO queryParams) {
log.info("Executing employee pagination query with params: {}", queryParams);
PageResult paginationResult = staffService.getPagedList(queryParams);
return Result.success(paginationResult);
}
PageHelper Dependency Configuration
Integrate the PageHelper starter for pagination support:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${pagehelper.version}</version>
</dependency>
Service Layer Pagination Implementation
@Override
public PageResult getPagedList(StaffPageQueryDTO pageQueryParams) {
// Initialize pagination with requested page number and size
PageHelper.startPage(pageQueryParams.getPageNum(), pageQueryParams.getPageSize());
Page<Staff> page = staffMapper.queryPagedList(pageQueryParams);
long totalRecords = page.getTotal();
List<Staff> staffList = page.getResult();
return new PageResult(totalRecords, staffList);
}
MyBatis Mapper Query
<select id="queryPagedList" resultType="com.sky.entity.Staff">
SELECT * FROM staff
<where>
<if test="name != null and name != ''">
<bind name="likeName" value="'%' + name + '%'"/>
AND name LIKE #{likeName}
</if>
</where>
ORDER BY create_time DESC
</select>
Global Date Formatting
To standardize date-time serialization between backend and frontend, two approaches exist:
- Annotation-based (not recommended)
Apply
@JsonFormatdirectly to entity fields for one-off formatting:@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; - Global message converter extension
Configure a centralized converter to handle all JSON date formatting:
@Override protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { log.info("Configuring custom JSON message converter for date formatting"); MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); jsonConverter.setObjectMapper(new CustomJacksonObjectMapper()); // Prioritize this converter over default implementations converters.add(0, jsonConverter); }
Enable/Disable Employee Status
For state modification operations, return a non-generic Result since no data payload is needed. Use dynamic SQL and partial entity objects to avoid overwriting existing fields:
Staff staff = Staff.builder()
.id(staffId)
.status(targetStatus)
.build();
staffService.updateStatus(staff);
MyBatis dynamic SQL skips null values in UDPATE clauses, so unpopulated entity fields won't overwrite valid database data.
Edit Employee Functionality
The edit feature relies on two core APIs:
- Retrieve employee details by ID to populate the edit form.
- Udpate employee records with submitted changes.
When extending the system with related modules (e.g., category management), follow this dependency-aware order:
- Copy Mapper interfaces and XML files.
- Implement Service interfaces and their concrete classes.
- Add Controller endpoints.
- Validate API definitions via Swagger or documentation tools.
Automated Common Field Population
Eliminate repetitive code for setting audit fields (create_time, create_user, update_time, update_user) using AOP. This workflow includes three key steps:
1. Custom Annotation for Target Methods
Create an annotation to identify methods reuqiring auto-population:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoPopulate {
DbOperationType value();
}
Where DbOperationType is an enum defining INSERT and UPDATE operations.
2. AOP Aspect for Field Injection
Implement an aspect to intercept annotated methods and populate fields via reflection:
@Aspect
@Component
@Slf4j
public class AutoPopulateAspect {
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoPopulate)")
public void autoPopulatePointcut() {}
@Before("autoPopulatePointcut()")
public void handleAutoPopulate(JoinPoint joinPoint) {
log.info("Starting common field auto-population");
// Extract operation type from method annotation
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
AutoPopulate autoPopulate = methodSignature.getMethod().getAnnotation(AutoPopulate.class);
DbOperationType operationType = autoPopulate.value();
// Retrieve target entity (assumed to be the first method parameter)
Object[] args = joinPoint.getArgs();
if (args == null || args.length == 0) return;
Object entity = args[0];
// Prepare common field values
LocalDateTime currentTimestamp = LocalDateTime.now();
Long currentUserId = BaseContext.getCurrentUserId();
try {
if (operationType == DbOperationType.INSERT) {
// Populate all four audit fields for INSERT operations
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_UPDATE_USER, Long.class);
setCreateTime.invoke(entity, currentTimestamp);
setCreateUser.invoke(entity, currentUserId);
setUpdateTime.invoke(entity, currentTimestamp);
setUpdateUser.invoke(entity, currentUserId);
} else if (operationType == DbOperationType.UPDATE) {
// Populate only update-related fields
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoPopulateConstants.SET_UPDATE_USER, Long.class);
setUpdateTime.invoke(entity, currentTimestamp);
setUpdateUser.invoke(entity, currentUserId);
}
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
log.error("Failed to auto-populate common fields: {}", e.getMessage(), e);
throw new RuntimeException("Field population failed", e);
}
}
}
3. Annotate Mapper Methods
Mark relevant Mapper methods to trigger auto-population:
@AutoPopulate(value = DbOperationType.UPDATE)
void updateStaff(Staff staff);