I'm developing a web application in which users can insert a number of "products". These products will be inserted in a MySQL database. I have a problem when I try to retrieve data from a table of my database. Here is my method:
public ArrayList<Product> getProductByAppId(int appId) {
ArrayList<Product> list = new ArrayList<Product>();
String query = "select prodId from app_prod where appId = ?";
try {
preparedStatement = connection.prepareStatement(query);
preparedStatement.setInt(1, appId);
resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
Product item = getProductById(resultSet.getInt("prodId"));
list.add(item);
}
return list;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
This method simply gets an int as a parameter and retrieves from the table app_prod all the objects I have stored. The method getProductById it's an helper method and it works properly. When I try to debug my code, I see that I enter in the while cycle only once! So all I see is the very first element in my DB, but I have more than a single product in my DB.
To make things shorter, I've omitted methods to open and close connection because they work properly.
I think the error is something very obvious, but I can't really see it.
OK the problem is the following:
resultSet is declared as a global variable and is being used by both methods.
When the second method changes its contents and gets through it by :
resultSet.next();
And reaches the end of it:
The main outer loop tries to do resultSet.next(), it directly exits from the loop since it had already reached its end beforehand in the getProductById method.
List<Product> list = new ArrayList<>();
try (PreparedStatement preparedStatement = connection.prepareStatement(query)) {
preparedStatement.setInt(1, appId);
try (resultSet = preparedStatement.executeQuery()) {
while (resultSet.next()) {
Product item = getProductById(resultSet.getInt("prodId"));
list.add(item);
}
return list;
}
} catch (Exception e) {
e.printStackTrace();
}
The try-with-resources ensure that statement and resultset are closed (even despite the return).
Also now the variables are local. And that might be the problem: maybe you reused those global fields in getProductById. resultSet would be my guess. (Pardon me.)
Related
I'm trying to my a very simple webapplication, webshop, for cupcakes.
From the webApp you can choose a cupcake form the dropdown with three attributes
(top, bottom, quantity). These are stored in an ArrayList on my sessionScope but all in numbers e.g. Chokolate as 1 and Vanilla as 2. I want to use these topId numbers to ask my DB (MySQL) for what is in 1 and then have it return Chokolate.
I think I am almost there with my code, but can't get it to return my String, as my topId is an Int.
public static Top getTopById(int topId) {
readFromArrayPutInSQL();
String sql = "INSERT INTO cupcaketopping (toppingType, toppingPrice) VALUES (?, ?)";
try {
ConnectionPool connectionPool = new ConnectionPool();
String query = "SELECT toppingType FROM cupcaketopping";
Statement statement = connectionPool.getConnection().createStatement();
ResultSet rs = statement.executeQuery(query);
rs.getString(topId);
} catch (SQLException e) {
throw new RuntimeException(e);
}
return topId; //Here is the problem - I GUESS?
}
Code after changes due to input in comments, seem to be working!
public static Top getTopById(int topId) {
readFromArrayPutInSQL();
String query = "SELECT toppingType FROM cupcaketopping WHERE toppingID = "+topId+"";
try {
ConnectionPool connectionPool = new ConnectionPool();
PreparedStatement preparedStatement = connectionPool.getConnection().prepareStatement(query);
ResultSet rs = preparedStatement.executeQuery(query);
rs.next();
return new Top(rs.getString(1));
//connectionPool.close(); //NOTE! Won't run, IntelliJ is asking me to delete!
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
There are a few problems:
You're selecting all rows from the cupcaketopping table, regardless of the topId. You should probably be using a PreparedStatement, and then use topId as part of your query.
You never call ResultSet#next(). The result set always starts "before" the first row. You have to call next() for each row in the result set (it returns true if there is a row to read).
The ResultSet#getString(int) method gets the String value of the column at the given index of the result. You only select one column, so the argument should probably be 1 (not topId).
You never close the Statement when done with it.
Depending on how your connection pool class works, you might actually need to close the Connection instead.
You never try to use the String returned by rs.getString(topId).
You never try to convert the query result to a Top instance.
Given it's possible the query will return no result, you might want to consider making the return type Optional<Top>.
The sql string seems to have no purpose.
Your code should look more like this:
public Optional<Top> getTopById(int topId) {
Connection conn = ...;
String query = "SELECT toppingType FROM cupcaketopping WHERE id = ?";
// closes the statement via try-with-resources
try (PreparedStatement stat = conn.prepareStatement(query)) {
stat.setInt(1, topId);
ResultSet rs = stat.executeQuery();
// assume unique result (as it's assumed the ID is the primary key)
if (rs.next()) {
// assumes 'Top' has a constructor that takes a 'String'
return Optional.of(new Top(rs.getString(1)));
} else {
return Optional.empty();
}
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}
Your actual implementation may vary, depending on how the rest of your code is designed.
public ResultSet getAllCustomer() throws SQLException{
//List L = new ArrayList();
try {
stm = con.prepareStatement("select * from customer");
resultSetSubject = stm.executeQuery();
while (resultSetCustomer.next()) {
//L.add(resultSetCustomer);
resultSetCustomer.getInt(1);
resultSetCustomer.getString(2);
//System.out.println(resultSetCustomer.getInt("id")+" "+resultSetCustomer.getString("name"));
}
} catch (Exception e) {
System.out.println(e);
}
return resultSetCustomer;
}
Let me brief, I am fetching all record from the customer table and returning as resultset. I am accessing returned resultset as
ResultSet rs = T.getAllCustomer();
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
There are several issues in your code:
You are obtaining a ResultSet object, looping over all of the rows in it, getting the column values, and then returning this ResultSet object. By the time it is returned to the caller, it has already been consumed by the loop that processed the rows (this is the cause of the error in your question).
Don't return the ResultSet object, instead create a Customer entity class that maps to the data in the DB, and return an instance of Customer. Returning a ResultSet object is really bad as it doesn't give control over how the object is closed, nor does it make your code object-oriented.
You don't need to create a prepared statement if you don't have a parameterized query.
The Statement (and ResultSet) must be closed after done. You should wrap the code that processes the whole result in a try-with-resources statement.
Your code needed little amendment
1 > Ensure resultset variable you use are same as defined (one place its different in your code).
2> use only
public ResultSet getAllCustomer() throws SQLException{
try {
stm = con.prepareStatement("select * from customer");
resultSetCustomer = stm.executeQuery();
} catch (Exception e) {
System.out.println(e);
}
return resultSetCustomer;
}
3> Close your connection/resultset/statement in separate method otherwise you will get error. you can use as below
public void close() throws SQLException{
if (resultSetCustomer != null) {
resultSetCustomer .close();
}
if (stm !=null) {
stm.close();
}
}
4> use resultset(your resultset instance).next() while accessing
The following Java code returns an empty result set but the SQL shows that there is data in the table:
#Override
public List<Category> list() {
List<Category> list = new ArrayList<>();
if (!connection.isPresent()) {
return list;
}
Connection con = connection.get();
try {
Statement stmnt = con.createStatement();
ResultSet rs = stmnt.executeQuery(LIST_SQL);
while (rs.next()) {
System.out.println("Got a record"); // DEBUG
int id = rs.getInt(1);
String description = rs.getString(2);
list.add(new Category(id, description));
}
} catch (SQLException e) {
LOG.error("Error retrieving Category list", e);
return list;
}
return list;
}
LIST_SQL is:
SELECT id, description FROM category
If I copy this query into sqlite3 and execute it, several rows are returned. connection works with other queries (but they are prepared statements and not an executeQuery). There is no error. "Got a record" is never printed.
Any ideas about what could be going wrong?
Sorry guys. My list() method was never getting executed! Why is another question. Thanks for all your responses.
It doesn't look like you remembered to include the ";" in the LIST_SQL string. Semi-colon is required to end the query statement.
A question: I deal with lots of update statements that at this moment I add to the ArrayList and then pass the array list to the function that loops over all update statements. They are not prepared.
How would you address this? I am thinking about 'universal' update function which receives lists of tables and parameters and then 'prepares' everything.
public void updateFromList(ArrayList<String> updateQueriesList) {
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection("jdbc:oracle:thin:#:1521:", "", "");
PreparedStatement pstmt = null;
for (String s : updateQueriesList) {
pstmt = con.prepareStatement(s);
pstmt.addBatch();
}
pstmt.executeBatch();
con.close();
}
} catch (Exception ex) {
}
}
I'm not sure if what you're asking is really possible. Not easily at least and without some work arounds that would make for bad unreadable code.
My suggestion would be to have a "major" update statement for each table that you are trying to update. One function per table. Then you would pass in objects that would be used to prepare the statement no matter what information they may contain. You would then loop the through the list of objects and call the "major" update statement on each one.
In this solution you don't add any statements to a list, you just have one statement within your major function that applies to all data that may ever go in that table.
Example:
public class ObjectToBeUpdated
{
//data
//getters and setters
}
public void updateObject(ObjectToBeUpdated object) {
Connection connection = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
connection = DriverManager.getConnection("jdbc:oracle:thin:#:1521:", "", "");
String sql = UDPATE_STATEMENT with ? for parameters
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, object.getValue1()); //etc
//get all values from object passed in and set them in order
preparedStatement.executeUpdate();
connection.commit();
connection.close();
}
} catch (Exception ex) {
}
finally {
connection.close();
}
}
This is a very common way of doing it a lot of applications that allow for readable and very manageable code that only has to apply to one type of object/table at a time.
In my situation, I am querying a database for a specific return (in this case registration information based on a username).
//Build SQL String and Query Database.
if(formValid){
try {
SQL = "SELECT * FROM users WHERE username=? AND email=?";
Collections.addAll(fields, username, email);
results = services.DataService.getData(SQL, fields);
if (!results.next()){
errMessages.add("User account not found.");
} else {
user = new User();
user.fillUser(results); //Is it ok to pass ResultSet Around?
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
services.DataService.closeDataObjects(); //Does this close the ResultSet I passed to fillUser?
}
}
So once I query the database, if a result is found I create a new User object and populate it with the data I received from the database. I used to do all of this directly in the method that I was pulling the resultset into, but I realized I was doing a lot of redundant coding throughout my project so I moved it all into one central method that lives in the actual User bean.
public void fillUser(ResultSet data) throws SQLException{
setUserId(data.getInt("id"));
setFirstName(data.getString("first_name"));
setLastName(data.getString("last_name"));
setUsername(data.getString("username"));
setType(data.getString("type"));
setEmail(data.getString("email"));
}
I have done a few tests and from what I can determine, because I close the original resultset in the finally block of the query, the resultset that I pass into the fillUser method also gets closed. Or am I wrong and am I seriously leaking data? This is actually the second time I pass a resultset (so its two instances of one) because the block I use to query my database is
public static ResultSet getData(String SQL, ArrayList fields) throws SQLException {
try{
connection = Database.getConnection();
preparedStatement = connection.prepareStatement(SQL);
for(int i=0; i<fields.size(); i++){
Integer num = i + 1;
Object item = fields.get(i);
if(item instanceof String){
preparedStatement.setString(num, (String) item); //Array item is String.
} else if (item instanceof Integer){
preparedStatement.setInt(num, (Integer) item); //Array item is Integer.
}
}
resultSet = preparedStatement.executeQuery();
return resultSet;
}finally{
}
}
All of these code snippets live in separate classes and are reused multiple times throughout my project. Is it ok to pass a resultset around like this, or should I be attempting another method? My goal is to reduce the codes redundancy, but i'm not sure if i'm going about it in a legal manner.
Technically, it's OK to pass result sets, as long as you are not serializing and passing it to a different JVM, and your JDBC connection and statement are still open.
However, it's probably a better software engineer and programming practice to have DB access layer that returns you the result set in a Java encoded way (a list of User in your example). That way, your code would be cleaner and you won't have to worry if the ResultSet is already opened, or you have to scroll it to the top, you name it...
As everyone before me said its a bad idea to pass the result set. If you are using Connection pool library like c3p0 then you can safely user CachedRowSet and its implementation CachedRowSetImpl. Using this you can close the connection. It will only use connection when required. Here is snippet from the java doc:
A CachedRowSet object is a disconnected rowset, which means that it makes use of a connection to its data source only briefly. It connects to its data source while it is reading data to populate itself with rows and again while it is propagating changes back to its underlying data source. The rest of the time, a CachedRowSet object is disconnected, including while its data is being modified. Being disconnected makes a RowSet object much leaner and therefore much easier to pass to another component. For example, a disconnected RowSet object can be serialized and passed over the wire to a thin client such as a personal digital assistant (PDA).
Here is the code snippet for querying and returning ResultSet:
public ResultSet getContent(String queryStr) {
Connection conn = null;
Statement stmt = null;
ResultSet resultSet = null;
CachedRowSetImpl crs = null;
try {
Connection conn = dataSource.getConnection();
stmt = conn.createStatement();
resultSet = stmt.executeQuery(queryStr);
crs = new CachedRowSetImpl();
crs.populate(resultSet);
} catch (SQLException e) {
throw new IllegalStateException("Unable to execute query: " + queryStr, e);
}finally {
try {
if (resultSet != null) {
resultSet.close();
}
if (stmt != null) {
stmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
LOGGER.error("Ignored", e);
}
}
return crs;
}
Here is the snippet for creating data source using c3p0:
ComboPooledDataSource cpds = new ComboPooledDataSource();
try {
cpds.setDriverClass("<driver class>"); //loads the jdbc driver
} catch (PropertyVetoException e) {
e.printStackTrace();
return;
}
cpds.setJdbcUrl("jdbc:<url>");
cpds.setMinPoolSize(5);
cpds.setAcquireIncrement(5);
cpds.setMaxPoolSize(20);
javax.sql.DataSource dataSource = cpds;