<p>I'm using the following code to query a database from my jsp, but I'd like to know more about what's happening behind the scenes.</p>

<p>These are my two primary questions.</p>

<p>Does the tag access the ResultSet directly, or is the query result being stored in a datastructure in memory?</p>

<p>When is the connection closed?</p>

<pre><code>&lt;%@ taglib prefix="sql" uri="http://java.sun.com/jsp/jstl/sql" %&gt;

&lt;sql:query var="query" dataSource="${ds}" sql="${listQuery}"&gt;&lt;/sql:query&gt;
&lt;c:forEach var="row" items="${query.rows}" begin="0"&gt;
    ${row.data }
    ${row.more_data }
&lt;/c:forEach&gt;
</code></pre>

<p>Note: I've always been against running queries in the jsp, but my result set is too large to store in memory between my action and my jsp. Using this tag library looks like the easiest solution.</p>

The key thing here is this: javax.servlet.jsp.jstl.sql.Result
That's what JSTL uses as the result of a SQL Query. If you look at the interface, it has this method:
public java.util.SortedMap[] getRows()
c:forEach "knows" about javax.servlet.jsp.jstl.sql.Result, since Result isn't anything else that forEach knows about (Collections, arrays, iterators, etc).
So, all of that implies that the SQL query will suck the entire result set in to RAM.
If you moved your query in to the JSP because you didn't want to load the entire result set in to a collection, then it doesn't look like the SQL tag will solve that problem for you.
In truth you should look up Value List Pattern.
But a "simple" solution to your problem would be to create a custom Iterator that "knows" about your ResultSet. This one wraps a result set and closes everything if it encounters an exception or if the result runs its course (like it would in a forEach). Kind of a special purpose thing.
```
public class ResultSetIterator implements Iterator {
```
```
```
Connection con;
Statement s;
ResultSet rs;
Object curObject;
boolean closed;

public ResultSetIterator(Connection con, Statement s, ResultSet rs) {
    this.con = con;
    this.s = s;
    this.rs = rs;
    closed = false;
}

public boolean hasNext() {
    advance();
    return curObject != null;
}

public Object next() {
    advance();
    if (curObject == null) {
        throw new NoSuchElementException();
    } else {
        Object result = curObject;
        curObject = null;
        return result;
    }
}

public void remove() {
    throw new UnsupportedOperationException("Not supported yet.");
}

private void advance() {
    if (closed) {
        curObject = null;
        return;
    }
    if (curObject == null) {
        try {
            if (rs.next()) {
                curObject = bindObject(rs);
            }
        } catch (SQLException ex) {
            shutDown();
            throw new RuntimeException(ex);
        }
    }
    if (curObject == null) {
        // Still no object, must be at the end of the result set
        shutDown();
    }
}

protected Object bindObject(ResultSet rs) throws SQLException {
    // Bind result set row to an object, replace or override this method
    String name = rs.getString(1);
    return name;
}

public void shutDown() {
    closed = true;
    try {
        rs.close();
    } catch (SQLException ex) {
        // Ignored
    }
    try {
        s.close();
    } catch (SQLException ex) {
        // Ignored
    }
    try {
        con.close();
    } catch (SQLException ex) {
        // Ignored
    }
}
```
```
}
This is, naturally, untested. But since JSTLs forEach can work with an Iterator, it's the simplest object you could really pass to it. This will prevent you from loading the entire result set in to memory. (As an interesting aside, it's notable how almost, but not quite, completely unlike Iterator a ResultSets behavior is.)