This question already has answers here:
Returning a ResultSet
(8 answers)
Closed 2 years ago.
I'm working on creating a program to manage a MySQL server. I have a working UI and code that can add new entries to the database as it should. Pictured below is the proto GUI that I'm using, the part that's important for the question is the table box that will show the entries of the database when it's working.
The code that I have for reading the contents of the database works as it should. The program is structured so that I have separate classes for the interface, scene controller and SQL commands. The issue that I'm dealing with is that I can pull the needed data from the database but getting to the scene controller class to write it to the database simply isn't working.
The relevant pieces of code are included below is included below.
SQL functions:
public ResultSet readDataBase() throws Exception{
try {
// Establish connection to server
Class.forName("com.mysql.cj.jdbc.Driver");
connect=DriverManager.getConnection("jdbc:mysql://localhost/library?"+"user=tempUser&password=12345");
statement=connect.createStatement();
// Execute query and write the results
resultSet=statement.executeQuery("select * from library.books");
return resultSet;
// writeResultSet(resultSet);
} catch (Exception e){
throw e;
} finally {
close();
}
}
Scene controller code:
public void fillTable() throws SQLException{
ResultSet resultSet=null;
try {
resultSet=commands.readDataBase();
while(resultSet.next()) {
String title = resultSet.getString("title");
String author = resultSet.getString("author");
String genre = resultSet.getString("genre");
String format = resultSet.getString("format");
String isbn = resultSet.getString("isbn");
System.out.println("Title: " + title);
System.out.println("Author: " + author);
System.out.println("Genre: " + genre);
System.out.println("Format: " + format);
System.out.println("ISBN: " + isbn);
System.out.println();
}
}catch (Exception e){
e.printStackTrace();
}
}
The purpose of the code in the scene controller block is simply to test for and read the result set for now. The code for writing to write the data from the result to their respective tables will be added later. This code was selected because I originally had it in the SQL functions class and it worked there, so I knew the code was good and did its job.
Whenever I run the code however, I get this error result.
java.sql.SQLException: Operation not allowed after ResultSet closed
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63)
at com.mysql.cj.jdbc.result.ResultSetImpl.checkClosed(ResultSetImpl.java:445)
at com.mysql.cj.jdbc.result.ResultSetImpl.next(ResultSetImpl.java:1726)
at library.test.windows.interfaceSceneController.fillTable(interfaceSceneController.java:108)
at library.test.windows.winInterface.main(winInterface.java:33)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:464)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:363)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at java.base/sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:1051)
I've done some reading into this prior but the only really relevant source of info I could find was in this post from over seven years ago. The answer for that question made reference to using JavaBeans as a place to put the information as an in-between but then used a class called a 'Biler' in their sample code. I can't find any reference to a Biler anywhere except his post and my IDE (IntelliJ if that's relevant) did not recognize it at all. I've been doing some experimentation with JavaBeans but I'm not sure it solving my problem.
In summary, my question is this. What do I need to do in order to properly pass a ResultSet from the function accessing the SQL server to the class containing the code for writing it to the table in my interface? I know this must be possible somehow but I can't seem to figure it out.
Your database access code should be kept entirely separate from your user-interface code. The UI should not deal with an active ResultSet.
You need to copy the data out of the result set, or use a utility to do so for you.
CachedRowSet
A RowSet may be the solution. This interface extends ResultSet. See the Java Tutorials by Oracle for explanations.
You could use the CachedRowSet interface that keeps a copy of the result set data in memory, detached from the database. Oracle provides an implementation, as might other vendors such as your JDBC driver.
A CachedRowSet implementation is disconnected from the database. In contrast, a ResultSet maintains a database connection (the root of your problem). To quote the Javadoc:
A CachedRowSet object is a container for rows of data that caches its rows in memory, which makes it possible to operate without always being connected to its data source. Further, it is a JavaBeans™ component and is scrollable, updatable, and serializable.
That interface is extended by a few more interfaces.
POJOs
Plain old Java objects is another option, copying every field of every row from your ResultSet into properties of a Java object.
You can simply loop the result set while instantiating records. Or you can use any of a variety of frameworks to assist.
Records
Defining a class for such POJOs is much simpler when using the new records feature arriving in Java 16, now previewed in Java 15. The constructor, getters, toString, and equals & hashCode are all synthesized by the compiler. You simply declare the properties.
A record can be declared as a stand-alone class, or as a nested class, or even locally within a method.
Well, I assume you are closing the conneciton in your close() method. So that's why it is closed. The finally is executed after the method body but before the result is passed to the calling funtion.
try {
return resultSet;
} finally {
close();
}
IMHO it is a bad pratice to pass back a resultset anyway because the resultset is not really typesafe. If you change the query you will break the fillTable() function without noticing. And, of course, you would need to find a way to close the resultset and the underlying DB connection at some point. This might be challenging.
Related
I see a Cross Scripting injection error on the statement that follows the execute update. How do I sanitize the output to rid it of Cross scripting errors..
I have a simple code snippet which uses PreparedStatement to run a select and returns the values which is retrieved on the front end GUI screen.
XSS Flaw message:"The tainted data originated from an earlier call to java.sql.ResultSetMetaData.getColumnName. "
Java code:
Connection conn = null;
Statement stmt = null;
PreparedStatement pstmt = null;
try {
List alist= new ArrayList();
pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
int count = rs.getMetaData().getColumnCount();
for (int i = 1; i <= count; i++) {
alist.add(rs.getMetaData().getColumnName(i)) //Flaw seems to point to this.
}
}catch(Exception e){
//close connections
}
return alist;
Full Veracode Flaw:
Attack Vector: javax.servlet.jsp.JspWriter.print
Number of Modules Affected: 1
Description: This call to javax.servlet.jsp.JspWriter.print() contains a cross-site scripting (XSS) flaw. The application populates the HTTP response with untrusted input, allowing an attacker to embed malicious content, such as Javascript code, which will be executed in the context of the victim's browser. XSS vulnerabilities are commonly exploited to steal or manipulate cookies, modify presentation of content, and compromise confidential information, with new attack vectors being discovered on a regular basis. The first argument to print() contains tainted data from the variable getHtml(). The tainted data originated from an earlier call to java.sql.ResultSetMetaData.getColumnName. The tainted data is directed into an output stream returned by javax.servlet.jsp.JspWriter.
I don't know why a ResultSetMetaData object is calling a servlet method, but if I do understand it correctly it is used to print something in the servlet. But because all the data in your ResultSetMetaData is comming from your database's meta data (which can't possibly include javascript code or other XSS riscs) it should be save in this case.
ResultSetMetaData is a common used class (and used sinze a long time) so I realy don't think that this is a problem in current java versions.
So I'm not shure how to fix this, but I'm pretty shure that this is not a problem, because it's not possible to add any maleware code (javascript or sql injection code) in the metadata of your database.
So, I was finally able to figure out the problem.
My Query was:
SELECT COLUMN_A, COLUMN_A||'-'||COLUMN_B FROM SOME_TABLE WHERE COLUMN_C IS NULL;
There are a few things I did...
1) Add an alias name to my column and used rs.getMetaData().getcolumnlabel(i) instead of rs.getMetaData().getColumnName(i). This makes sure to eliminate pipeline or hyphens from the output resultset name.
2) Used OWASP's ESAPI.encoder().encodeForHTML(unsafeString) instead of using Encode.forHtml(escapeHtml) and StringEscapeUtils.escapeHtml(escapeHtml) on the variable passed to the prepared statement.
Ouput behavior of || and - when passing them through the various escape functions.
Input: String unsafeString ="<'HELLO'-||>";
Outputs:
Encode.forHtml(unsafeString) is <'HELLO'-||>
StringEscapeUtils.escapeHtml(unsafeString) is <'HELLO'-||>
ESAPI.encoder().encodeForHTML(unsafeString) is <'HELLO'-||>
ESAPI.encoder().decodeForHTML(safeString from above line) is <'HELLO'-||>
If the Output is sent to the jsp to be rendered back on the html then you dont have to do the decodeStep..
So I'm trying to understand how to use the RowSet API, specifically CachedRowSet, and I feel like I've been bashing my head against a wall for the last hour or so and could use some help.
I've got some very simple tables set up in a MySQL database that I'm using to test this. I should also add that everything I'm attempting to do with RowSet I've been able to do successfully with ResultSet, which leads me to believe that the issue is with my usage of the ResultSet API, rather than the operation I'm attempting to do itself.
Anyway, I'm trying to insert a new row using ResultSet. I'll paste my code here, then add some notes about it below:
CachedRowSet rowSet = null;
try {
RowSetFactory rsFactory = RowSetProvider.newFactory();
rowSet = rsFactory.createCachedRowSet();
rowSet.setUrl("jdbc:mysql://localhost:3306/van1");
rowSet.setUsername("####");
rowSet.setPassword("####");
rowSet.setKeyColumns(new int[]{1});
} catch (SQLException e) {
e.printStackTrace();
}
String query = "select * from phone";
try {
rowSet.setCommand(query);
rowSet.execute();
printTable(rowSet);
rowSet.moveToInsertRow();
rowSet.setInt(1, 4);
rowSet.setString(2, "Mobile");
rowSet.setString(3, "1");
rowSet.setString(4, "732");
rowSet.setString(5, "555");
rowSet.setString(6, "1234");
rowSet.setString(7, "");
rowSet.insertRow();
rowSet.moveToCurrentRow();
rowSet.acceptChanges();
printTable(rowSet);
} catch (SQLException e) {
e.printStackTrace();
}
So, as you can see, I'm trying to update a table of phone numbers with a new phone number. Here are the details:
1) All the phone number fields are datatype char, so that leading zeroes are not lost.
2) I'm using the default CachedRowSet implementation provided by the JDBC API, as opposed to anything specific from the MySQL driver. Not sure if that matters or not, but I'm putting it here just in case. Also, I didn't see an option to import CachedRowSet from the driver library anyway.
3) I'm setting a value for every column in the table, because the RowSet API doesn't allow for rows to be inserted without a value for every column.
4) I've tried the operation using both the setter methods and the update methods. Same result either way.
5) As far as I can tell, I'm on the insert row when executing the insertRow() method. I also return to the current row before invoking acceptChanges(), but since my code never gets that far I can't really comment on that part.
6) The exception is a SQLException (no chained exception within it) thrown on the invocation of the insertRow() method. Here is the stack trace:
java.sql.SQLException: Failed on insert row
at com.sun.rowset.CachedRowSetImpl.insertRow(Unknown Source)
at firsttry.RowSetPractice.rowSetTest(RowSetPractice.java:87)
at firsttry.RowSetPractice.main(RowSetPractice.java:20)
So, I'm out of ideas. Any help would be appreciated. I've searched every thread on this site I could find, all I see is stuff about it failing on the acceptChanges() method rather than insertRow().
I have problem with java and MySQL. My code:
Connection connection;
// ...
for (String query : updateAndInsertQuery) {
Statement stm = connection.createStatement();
stm.execute(query);
stm.close();
}
Statement stm2 = connection.createStatement();
System.out.println("Before query");
System.out.flush();
ResultSet Result = stm2.executeQuery(selectQuery);
System.out.println("After query");
System.out.flush();
int vfrom, vto;
while (Result.next()) {
// ...
}
When I run program i see in MySQL queries and run
show processlist;
selectQuery is visible on list wth status Sending data or Writing to net. On console print: Before query. Next
show processlist;
returns empty list, but application don't print After query. Do you have similar problem?
-- edit
I resolve my problem.
I think:
wen MySQL returns data and query isn't visible on processlist in MySQL
I should immediately get on console message: After query
but console was empty, java process works (cpu usage was 90-100%) so I think it was my mistake, but after 1h application throws Exception
Increase memory limit resolve my problem.
So I have next question why application throw exception after hour? Garbage collection try dealocate unised objects?
executing queries manually usually leads into many different problems - all of which are platform-specific and DB-specific. I think your best answer will be : "switch to ORM".
This framework has proven to be exceptionally good, wrappig all your SQL-data into Entities and transactions (if required) will resolve most of your problems at the same time - you will only need to annotate your entities and relationships correctly. Database-queries can be executed via JPA-"criteria"s which are platform-independent AND allow you to avoid a lot of problems as well as making your code READABLE.
Tutorial : http://www.vogella.com/tutorials/JavaPersistenceAPI/article.html
SO-question : https://stackoverflow.com/questions/743065/what-is-the-fastest-way-to-learn-jpa
With JPA, you wont need to care about statements or queries anymore (well, at least most of the time) and your mentioned problem will disappear - PLUS : it only takes 30-60min to implement.
Additional tip : use Maven & Eclipselink (JPA2 implementation) - thats a very powerful, portable combination
I've a strange situation with a little application in Java using a JDBC-OBDC. I'm inspecting a Database using the DatabaseMetaData Class. When I execute the program, everything works without anyproblem. But when I want to debug to see the values inside the Resulset containing the DatabaseMetaData a java.sql.SQLException is thrown only if I put a breakpoint within the while. Here's my code:
DatabaseMetaData patrol = con.getMetaData();
ResultSet answer = patrol.getTables(null, null, null, null);
while(answer.next()) {
if (answer.wasNull() == false) {
tableNamesAsOne = tableNamesAsOne + answer.getString("TABLE_NAME") + " ";
}
}
answer.close();
Why I cannot put my breakpoint in this section of code??
This is the printStackTrace.
Exception in thread "main" java.sql.SQLException: No data found
at sun.jdbc.odbc.JdbcOdbc.standardError(Unknown Source)
at sun.jdbc.odbc.JdbcOdbc.SQLGetDataString(Unknown Source)
at sun.jdbc.odbc.JdbcOdbcResultSet.getDataString(Unknown Source)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(Unknown Source)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(Unknown Source)
at Snooper.inspect(Snooper.java:56)
at Snooper.<init>(Snooper.java:26)
at Snooper.createAndShowGUI(Snooper.java:112)
at Snooper.main(Snooper.java:125)
Line Snooper.java:56 in my code refers to
tableNamesAsOne = tableNamesAsOne + answer.getString("TABLE_NAME") + " ";
Thanks.
I have installed SQL Server to reproduce your problem and verify it.
Short explanation
You must read the values ONLY ONCE and in the ORDER they appear in the SELECT. JdbcOdbc sucks. While debugging, you're reading them multiple times.
Long explanation
What you are doing is inspecting a stateful object in the debugger, which leads to dynamic results.
In this case it's the sun.jdbc.odbc.JdbcOdbcResultSet and executing the expression resultSet.getString(...) multiple times. The first time, it will work (in case your breakpoint suspends the Thread before the resultSet is asked). Then, the second time you (or your debugger) inspects the value of the expression again, the getString() method is called again and this method changes the internal state of the ResultSet object.
Although the method's name suggests it's a simple getter, it's not. It does more than that. It may actually retrieve the data from the database, change its internal position counters etc. You cannot execute the getter methods multiple times.
The ODBC Driver is a very bad thing and of low quality. Expect strange behavior and other dragons. You can get some debug information by enabling JdbcOdbc Tracing. That is done by setting a LogWriter on the DriverManager, before the JdbcOdbc-Bridge is activated:
java.sql.DriverManager.setLogWriter(new PrintWriter(System.out));
Then, you will get verbose debugging output of the JdbcOdbc-Driver like the following. It may help you to debug the problem you have. When debugging, just ensure to store the data retrieved from the ResultSet objects in local objects, so you can inspect them multiple times in the debugger.
DriverManager.getConnection("jdbc:odbc:testdbodbc")
JdbcOdbcDriver class loaded
registerDriver: driver[className=sun.jdbc.odbc.JdbcOdbcDriver,sun.jdbc.odbc.JdbcOdbcDriver#7b479feb]
DriverManager.initialize: jdbc.drivers = null
JDBC DriverManager initialized
trying driver[className=sun.jdbc.odbc.JdbcOdbcDriver,sun.jdbc.odbc.JdbcOdbcDriver#7b479feb]
*Driver.connect (jdbc:odbc:testdbodbc)
JDBC to ODBC Bridge: Checking security
No SecurityManager present, assuming trusted application/applet
JDBC to ODBC Bridge 2.0001
Current Date/Time: Wed Jan 26 00:31:27 CET 2011
Loading JdbcOdbc library
Allocating Environment handle (SQLAllocEnv)
hEnv=115724512
Allocating Connection handle (SQLAllocConnect)
hDbc=116219184
Connecting (SQLDriverConnect), hDbc=116219184, szConnStrIn=DSN=testdbodbc
RETCODE = 1
WARNING - Generating SQLWarning...
SQLWarning: reason([Microsoft][ODBC SQL Server Driver][SQL Server]Changed database context to 'master'.) SQLState(01000) vendor code(5701)
SQLWarning: reason([Microsoft][ODBC SQL Server Driver][SQL Server]Changed language setting to us_english.) SQLState(01000) vendor code(5703)
*Connection.getMetaData
*DatabaseMetaData.getDriverName
Get connection info string (SQLGetInfo), hDbc=116219184, fInfoType=6, len=300
SQLSRV32.DLL
*DatabaseMetaData.getDriverVersion
Get connection info string (SQLGetInfo), hDbc=116219184, fInfoType=7, len=300
06.01.7600
*DatabaseMetaData.getDriverName
Get connection info string (SQLGetInfo), hDbc=116219184, fInfoType=6, len=300
SQLSRV32.DLL
Driver name: JDBC-ODBC Bridge (SQLSRV32.DLL)
*DatabaseMetaData.getDriverVersion
P.S. And this was the reproduced exception, including line numbers of the Sun code for JDK 1.6.0_22. As you can see in the first line, this is what is printed out on the console when I inspected the getString() method.
Get string data (SQLGetData), hStmt=108067024, column=3, maxLen=257
RETCODE = 100
ERROR - No data found
java.sql.SQLException: No data found
at sun.jdbc.odbc.JdbcOdbc.standardError(JdbcOdbc.java:7138)
at sun.jdbc.odbc.JdbcOdbc.SQLGetDataString(JdbcOdbc.java:3907)
at sun.jdbc.odbc.JdbcOdbcResultSet.getDataString(JdbcOdbcResultSet.java:5698)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(JdbcOdbcResultSet.java:354)
at sun.jdbc.odbc.JdbcOdbcResultSet.getString(JdbcOdbcResultSet.java:411)
at sandbox.DatabaseMetadataTest.testDBMetadata(DatabaseMetadataTest.java:27)
Yeah, the debugger runs in a different thread than the metadata obtained by con.getMetaData();... so, you know, it's a different transaction with a different metadata.
Well, ok, that would be my 1st guess. I have not looked into Obdc driver source code to confirm.
Edit:
thanks for mhaller excellent remark a made a 2nd look/guess: you call wasNull() prematurely, it has meaning after some get operation of the ResultSet. Copy from javadoc
* Note that you must first call one of the getter methods
* on a column to try to read its value and then call
* the method <code>wasNull</code> to see if the value read was
* SQL <code>NULL</code>.
phew, me sucks tonight!
I am trying to get all data from a table contained in a database, and parse it into a List. I have tried a few different techniques and can't get it working. I am trying to serialize the ResultSet so I can send it over a socket. The code I have got so far is:
public Object readJavaObject(String query, Connection con) throws Exception
{
PreparedStatement stmt = con.prepareStatement(query);
//stmt.setString(1, id);
query_results = stmt.executeQuery();
while (query_results.next())
{
dataObject = query_results.getObject(1);
}
query_results.close();
stmt.close();
return dataObject;
}
public void run()
{
try
{
Class.forName(dbClass);
con = DriverManager.getConnection (dbUrl, user, pass);
query = "SELECT * FROM Bench_table";
dataList = (List) this.readJavaObject(query, con);
con.close();
}
}
I'm not sure what code to write around readJavaObject. Can anyone help to point out where the issue is?
Thanks for any help.
Take a look at DbUtils (from apache-commons).
If you need a more complex solution for mapping sql results to java objects, take a look at object-relational mapping. In Java the standard is JPA (Java Persistence API), with Hibernate, EclipseLink and OpenJPA as the most famous implementors.
There are several issues with your code.
You loop through the results but
only return data from the last row.
Instead create a List in the
readJavaObject method and add an
element each pass through the loop.
Instead of getting the item in the first column, I assume you want all the data. You are going to have to be more specific with which "get" methods you use to pull data off the ResultSet. Possibly create a POJO to represent a row of data, then inside the loop populate it and store it in the list.
Assuming you use a POJO, you might want to use generics with your list to make it easier to get items out of it without having to cast.
I don't know exactly what you want to achieve, but can I introduce you to the DAO (Data Access Object) Pattern?
I recently discovered the CachedRowSet, which does exactly what OP needs. It notably allows to serialize a ResultSet, and to restore the original when needed.