Vaadin非缓冲旋转网格实现问题及修复方案:学生月度积分按日期-学生维度展示
Let's tackle the two main issues you're facing with your Vaadin pivot grid implementation for the SchoolBook entity: duplicate rows per date, missing dates, and we'll also add the required monthly sum row to complete your grid.
Problem Breakdown
First, let's recap the core problems from your initial code:
- Duplicate rows per date: Your original
getDataForSpecificMonthmethod created a newSyntheticRowfor every singleSchoolBookentry on the same date. This split student points across multiple rows instead of grouping all students' points into one row per date. - Missing dates: Dates with no
SchoolBookrecords were completely omitted from the grid, leaving gaps in your date sequence.
Fixed Implementation
Step 1: Correct the getDataForSpecificMonth Method
Your updated method already fixes both core issues, but let's break down why it works and refine it slightly for clarity:
public List<SyntheticRow> getDataForSpecificMonth(PageRequest of, int month, int year) { List<SyntheticRow> syntheticRows = new ArrayList<>(); int numberOfDays = getNumberOfDaysInMonth(month, year); for (int day = 1; day <= numberOfDays; day++) { LocalDate currentDate = LocalDate.of(year, month, day); List<SchoolBook> schoolBooks = repository.findByDay(currentDate); // Create ONE row per date, even if there are no entries SyntheticRow syntheticRow = new SyntheticRow(); syntheticRow.setDate(currentDate); Map<Long, String> studentPointsMap = new HashMap<>(); // Populate the map with all student points for this date for (SchoolBook book : schoolBooks) { studentPointsMap.put(book.getStudentId(), book.getPoints()); } syntheticRow.setMap(studentPointsMap); syntheticRows.add(syntheticRow); } return syntheticRows; }
Key improvements here:
- We create one
SyntheticRowper date, regardless of whether there areSchoolBookentries for that day. This ensures every date in the month appears in the grid, no gaps. - All student points for the same date are added to a single map in the row, so all students' points show up in the correct columns for that date row.
Step 2: Clean Up Empty Column Values
Right now, if a student has no entry for a date, the column will display null. To make the grid look cleaner, add a default value (like "-") in your column generator:
for (Student student : students) { Long studentId = student.getStudentId(); grid.addColumn(syntheticRow -> { String points = syntheticRow.getMap().get(studentId); return points != null ? points : "-"; // Show "-" when no entry exists }).setHeader(student.getStudentName() + " (" + studentId + ")"); }
Step 3: Add the Monthly Sum Row
To implement the final sum row, we need to calculate total points (converting letter grades to numeric values) for each student in the selected month. Let's add this functionality to your service, then append the sum row to the grid.
Service Method to Calculate Monthly Sums
public Map<Long, Integer> calculateMonthlyStudentSums(int month, int year, Long classId) { Map<Long, Integer> studentTotalSums = new HashMap<>(); YearMonth targetMonth = YearMonth.of(year, month); // Fetch all SchoolBook entries for the target month in one query (better performance) List<SchoolBook> monthlyEntries = repository.findByDayBetween( targetMonth.atDay(1), targetMonth.atEndOfMonth() ); // Convert letters to numeric values and sum for each student for (SchoolBook book : monthlyEntries) { Long studentId = book.getStudentId(); int pointsValue = convertPointsToNumeric(book.getPoints()); studentTotalSums.put(studentId, studentTotalSums.getOrDefault(studentId, 0) + pointsValue); } // Initialize sum to 0 for students with no entries in the month List<Student> allStudents = userService.getAllStudentsFromClass(classId); for (Student student : allStudents) { studentTotalSums.putIfAbsent(student.getStudentId(), 0); } return studentTotalSums; } // Helper method to convert letter grades to their numeric equivalents private int convertPointsToNumeric(String points) { return switch (points.toUpperCase()) { case "A" -> 10; case "B" -> 9; case "C" -> 8; case "D" -> 7; case "E" -> 6; case "F" -> 5; default -> Integer.parseInt(points); // Handle numeric point strings }; }
Append the Sum Row to Your Grid
Modify your grid setup code to add the sum row. We'll create a special SyntheticRow for totals and update the date column to show "Total" instead of a date:
// Load regular date rows List<SyntheticRow> dateRows = syntheticRowService.getDataForSpecificMonth( PageRequest.of(query.getPage(), query.getPageSize(), VaadinSpringDataHelpers.toSpringDataSort(query)), 9, 2022 ); // Calculate monthly sums and create the sum row Map<Long, Integer> studentSums = syntheticRowService.calculateMonthlyStudentSums(9, 2022, classId); SyntheticRow sumRow = new SyntheticRow(); sumRow.setDate(null); // Mark as the sum row Map<Long, String> sumMap = new HashMap<>(); studentSums.forEach((studentId, total) -> sumMap.put(studentId, String.valueOf(total))); sumRow.setMap(sumMap); // Combine date rows and sum row, then set to grid List<SyntheticRow> allRows = new ArrayList<>(dateRows); allRows.add(sumRow); grid.setItems(allRows); // Update date column to show "Total" for the sum row grid.addColumn(syntheticRow -> { LocalDate date = syntheticRow.getDate(); return date != null ? date.toString() : "Total"; }).setHeader("Date"); // Keep the student column logic with default values for (Student student : students) { Long studentId = student.getStudentId(); grid.addColumn(syntheticRow -> { String value = syntheticRow.getMap().get(studentId); return value != null ? value : "-"; }).setHeader(student.getStudentName() + " (" + studentId + ")"); }
Final Notes
- Pagination: Since we're showing an entire month's data (max 31 rows plus 1 sum row), consider disabling pagination for this grid—it's a small dataset that doesn't need it, and pagination would split the sum row from the rest of the data.
- Performance: Fetching all monthly entries in one query (instead of per day) reduces database calls, which is more efficient for larger datasets.
内容的提问来源于stack exchange,提问作者Andromeda

