On investigation, it turned out that the code was doing something like:
CNoteVO cnoteVO = some trash that filled it in
and then for every row of data returned by the SPL:
Of course, because cnoteVO was the same object each time, the resultList.add call was just storing a pointer to the same cnoteVO object each time, and at the end of processing those 20 rows, we now had twenty pointers to the same cnoteVO -- which meant that whatever was stored in cnoteVO most recently was duplicated 20 times.
The solution was to write a clone() method -- but of course, nothing is ever that simple, because CNoteVO is a superclass, and you can't really easily write a clone() method that will handle all the subclasses. (There is a way, with reflection, but I wanted to make sure that I understand this perfectly.) Instead, I defined clone() in CNoteVO as an abstract class, and defined a concrete clone() method in each of the subclasses. Now:
cnoteVO = loadData();
CNoteVO curRow = cnoteVO.clone();
Now every row is a distinct and different object, and I get 20 rows that are all different.
It has been many years since I ran into an issue like this; it is the only thing that helps me keep my sanity at work.
UPDATE: Of course, this isn't specific to OOPs. I have seen this mistake made (and probably made it myself) in C, where the temptation is strong to reuse a malloced block, and then store the pointer to that block instead of doing another malloc.