As usual, I didn't get all that on the first try, so I'll stumble around blindly, offering suggestions and we'll see how we can work things out from there.
First of all, Request Scope is almost 100% useless in JSF. JSF is heavily invested in Postback processing, and request-scope objects are completely destroyed and re-created with each posting. With very rare exceptions,
you should always be using View or Session scope. In the case where you are using one of JSF's model classes (SelectItem or DataModel), Request scope
is 100% useless, since the re-created models will lack cursor context.
So much for the UI side.
On the database performance side, pulling thousands of records is usually going to be a noticeable delay, certainly. For that matter, if you pull them into a session-scope (or view-scope) object, it's also going to require typically a big chunk of RAM, which will probably be virtual, thus putting you at risk for excessive Virtual Memory paging (thrashing). For Request scope, of course, you're going to be pulling them in for EACH time that the request object is re-created. Ouch!
I'm going to stop for a moment and wag my finger at your user. I've done apps that had to be able to return thousands of rows, but in practical terms, after the first hundred or so, my eyes begin to
water. Sweeping up everything in sight may be good for the NSA, but for ordinary people, it would be better to be able to home in on items of specific interest.
However, if that's what they insist on (sigh). Ordinarily one would use the slice-and-dice approach to information retrieval. Although there's no official SQL definition for making a selection, then returning for subsets of it, many databases have something that will do that. and it's relatively straightforward to use on paged displays.
In the case of continuous scrolling, you'll need something more subtle, though. Basically, what I'd look at is a subclass of the JSF DataModel that supports virtual rows. That is, to the dataTable, it appears to wrap a complete table of all the matching rows. But in reality, it does an on-demand fetch and cache of only the rows that people are actually viewing. For performance reasons, you may want to use the slice-and-dice technique to grab, say, 20 rows at a time, but only grab rows that people actually pull into their view.
I can't give specific details, since this isn't a trivial task and I don't have any code lying around. But I think that you'll find that this strategy will work for you.