Strange behaviour of ArrayList - java

I am writing a website using JSP, JSTL, Servlets and JavaBeans.
At one point of my code, I am trying to use an ArrayList of objects, and a strange thing is happening: when I add the first object it is fine, and when I add a second object it adds it in the second place, but the object at index(0) gets the same values as the object at index(1).
Maybe a problem is in the
ArrayList<Article> articleList = new ArrayList<Article>();
Article newArticle = new Article();
Since articleList is ArrayList of Article class.
Can somebody point me to what I am doing wrong?
Below is my code:
public ArrayList<Article> getArticles()
{
baseIO mySql = new baseIO();
ArrayList<Article> articleList = new ArrayList<Article>();
int articleId = 0;
try
{
String sql =
"select * from jsp_blog_article order by article_id Desc Limit 3";
con = (Connection)mySql.getConnection();
pstmt = (PreparedStatement) con.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
Article newArticle = new Article();
newArticle.setArticleAuthor(rs.getString("article_name"));
newArticle.setArticleBody(rs.getString("article_body"));
newArticle.setArticleAuthor(rs.getString("article_author"));
newArticle.setArticleDate(rs.getString("article_date"));
articleId = Integer.parseInt(rs.getString("article_id"));
newArticle.setArticleId(String.valueOf(articleId));
newArticle.setArticleComments(this.getCommentsNum(articleId));
articleList.add(newArticle);
}
con.close();
pstmt.close();
}
catch(Exception e)
{
return null;
}
return articleList;
}
And the Article class
package objects;
import java.io.Serializable;
public class Article implements Serializable{
private String articleName;
private String articleBody;
private String articleAuthor;
private String articleComments;
private String articleDate;
private String articleId;
public Article()
{
}
// all the getters and setters in place, but it is too long
// so i am not going to post them in forum
}

I would try it this way and see what this does.
int x = 0;
while (rs.next()) {
articleList.add(new Article());
articleList.get(x).setArticleName(rs.getString("article_name"));
articleList.get(x).setArticleBody(rs.getString("article_body"));
articleList.get(x).setArticleAuthor(rs.getString("article_author"));
articleList.get(x).setArticleDate(rs.getString("article_date"));
articleList.get(x).setArticleId(rs.getString("article_id"));
articleList.get(x).setArticleComments(this.getCommentsNum(articleId));
x++;
}

You are calling newArticle.setArticleAuthor twice...I know that's not part of your list problem, but that is an observation.

The code should be cleaned up per the other comments, but functionally, it should work.
Here's what I think is happening.
Your code has the following two lines in it:
newArticle.setArticleAuthor(rs.getString("article_name"));
newArticle.setArticleAuthor(rs.getString("article_author"));
and there is no corresponding call to:
newArticle.setArticleName(rs.getString("article_name"));
this means that your object has no article name specified (even though the author is specified). I'll bet that you are then doing some sort of processing before you display the list that somehow merges articles with the same name.
As a general approach to debugging, I recommend that you mock up your code so you can run it in a debugger and see what's actually going on (right now, your system has so many moving parts that it's going to be difficult for you to hone in on the actual problem).
In the current case, this would be as simple as running the one method outside of your web container, and using a debugger to take a look at the objects in the list that gets returned. You will find that the objects in the list are, indeed, separate objects - just having the same articleName property.

Code looks fine, how are you displaying the list that makes you think the same value is at both indexes? Perhaps your problem is with that code.

Are you adding articles to the database concurrently while reading them? I think that it is possible, depending on your storage engine, that you'd have problems reading while updating is going on.

Related

Java Realm get latest inserted item

In Realm database solution how can i get latest inserted item with best performance?
this code is my solution to resolve that
List<Transactions> allItems = realm.where(Transactions.class).findAll().sort("createdAt", Sort.DESCENDING);
String latestId = allItems.get(0).getId();
getUserLatestTransactions(Integer.parseInt(latestId));
is there any other solution or Realm has implement that?
Here is what you could do - considering Realm reads make no copies, meaning it is not expensive and won't affect your UI thread much, after storing your item, you can findAllSorted("createdAt) or findAllSorted("id"), then find last id using last() method like this:
//after commitTransaction,
RealmResults<Transactions> allTransactions = realm.where(Transactions.class).findAllSorted("createdAt");
//If you have an incrementing id column, do this
long lastInsertedId = allTransactions.last().getId();
This should give you the last inserted ID of the given model.
It is also important to mention that for this to work, you MUST have a column in your model like this with id;
public class Transactions extends RealmObject{
#PrimaryKey #Index
private long id;
//getters and setters accordingly!
}
I hope this helps! Good luck and happy coding!
UPDATE
I just realized that realm.copyToRealm(obj) returns an object!
That means you can simply do this:
realm.beginTransaction();
Transactions transaction = realm.copyToRealm(newTransaction);
long id = transaction.getId();
realm.commitTransaction();
Please try this and let me know!
Transactions item = realm.where(Transactions.class).findAll().last()
Note:if you want only get last insert data,no sort method
val obj = realm.where<Transactions>()
.sort("createdAt", Sort.DESCENDING)
.findFirst()
if(obj != null){
val latestId = obj.id
}
If Id is unique & incremental you can sort by it or put the time as long value and do same as above it should work.

ResultSet how to put it into a ArrayList

Is "ResultSet" considered to be an ArrayList? I'm talking about jdbc. If no, then
how do I put the information i get from my DB using the
while (result.next()) {
....
}
syntax into an ArrayList called something like hotelResult?
I hope it was understandable.
A ResultSet is not an ArrayList. Rather, it is a special object (Interface) to hold data retrieved by queries via JDBC connections.
A ResultSet object cannot be updated, and can only be traversed forward... not back. By default, you can only iterate through it once, from the first row to the last (though with a bit of coding, you can generate a ResultSet object that can be edited and traversed bi-directionally).
The records stored within a ResultSet object can easily be placed within an ArrayList. Here is an example on how you can do this:
Connection con = ... ;
Statement stmt = ... ;
ResultSet results = stmt.executeQuery("...");
//Stores properties of a ResultSet object, including column count
ResultSetMetaData rsmd = results.getMetaData();
int columnCount = rsmd.getColumnCount();
ArrayList<String> hotelResultList = new ArrayList<>(columnCount);
while (results.next()) {
int i = 1;
while(i <= columnCount) {
hotelResultList.add(results.getString(i++));
}
}
NOTE: This example assumes a single String being returned in the query, such as a Hotel name. You will likely want to hold multiple pieces of data about each hotel, in which case you would create a "Hotel" object, and then create the ArrayList as a List of Hotel objects. By using a rowmapper, each hotel object can be populated with the associated data.
In addition, using one of the popular JDBC frameworks to handle JDBC connections, queries, and result sets can simplify the process further.
I will help u out :)!
Create the needed variables in the class see my example :)
public class HotelData {
private String hotelName = null;
private int hotelTelephone = 0;
public HotelData(String hotelName, int hotelTelephone) {
this.hotelName = hotelName;
this.hotelTelephone = hotelTelephone;
}
}
Now create the ArrayList:
public ArrayList<HotelData> hotelResult = new ArrayList<HotelData>();
With the while method now:
while(result.next()) {
hotelResult.add(new HotelData(result.getString("Enter columnname"), result.getInt("Enter colummname")));
}
Hope this will help u buddy :)! If u need to get the data from the ArrayList u can simply write ur own get methods in the HotelData class!
No, ResultSet is not considered an ArrayList but rather a table.
If hotelResult for example has the type of String you can fill the list with this piece of code(if the column from the ResultSet is a String).
while(result.next()) {
hotelResult.add(result.getString("Enter the columnname here");
}
For each datatype there is a method to get the value from the ResultSet.
Look in the Java API for the different kinds of methods.
i believe that will clear- ArrayList hotels holds objects of HotelDtos
public class HotelDto {
private String hotelName;
private String hotelAddress;
private int hotelRank;
public String getHotelName() {
return hotelName;
}
public void setHotelName(String hotelName) {
this.hotelName = hotelName;
}
public String getHotelAddress() {
return hotelAddress;
}
public void setHotelAddress(String hotelAddress) {
this.hotelAddress = hotelAddress;
}
public int getHotelRank() {
return hotelRank;
}
public void setHotelRank(int hotelRank) {
this.hotelRank = hotelRank;
}
}
public class HotelDao {
public List<HotelDto> getHotlInfo(String hotelName) {
List<HotelDto> hotels = new ArrayList<HotelDto>();
try {
String query = "SELECT hotelName, hotelAddress,hotelrank " + "FROM HOTELS_TABLE "
+ "WHERE hotelName = " + "'" + hotelName + "'" + " ";
ResultSet resultSet = DBConnection.getDBConnection().createStatement().executeQuery(query);
int i = 0;
while (resultSet.next()) {
HotelDto hDto = new HotelDto();
hDto.setHotelName(resultSet.getString(1));
hDto.setHotelAddress(resultSet.getString(2));
hDto.setHotelrank(resultSet.getInt(3));
hotels.add(i, hDto);
i++;
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
return hotels;
}
}
}
To answer your first question, u don't really need the HotelData class.
The only thing this class does is holding the data nice and clean in one Object (for each specific hotel).
If you implement it your way, you must cast all the values to Strings first(if they do not contain String values) before u can store your items in the hotelInfo list. This is because the hotelInfo list has the String type, in my implementation this is also not needed(the casts) because i have created a constructor with one String value and one int value.
If u want your example to work implement it like this:
not this: hotelInfo.add(result.getString("hotelNo"));
but like this: hotelInfo.add("" + result.getInt("hotelNo")); //Notice the cast here!
If you're asking how I can get the database values which is added in my list .Here is the solution below
pom.xml file add below snippet of dependency
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
call the method from the main program .
List output = readRows(rs);
here rs is the ResultSet object, readRows() is the method
below is the code snippet for the readRows() method
private static List<List<Object>> readRows(ResultSet rs) throws SQLException
{
ImmutableList.Builder<List<Object>> rows = ImmutableList.builder();
int columnCount = rs.getMetaData().getColumnCount();
while (rs.next()) {
List<Object> row = new ArrayList<>();
for (int i = 1; i <= columnCount; i++) {
row.add(rs.getObject(i));
}
rows.add(row);
}
return rows.build();
}
I would like to complete above technical solution with performance tuning advice.
Everyone didn't mention performance implication when storing entire SELECT output into program level variable in Java (applicable to any programming language).
Commercial DB size has growth to 1TB easily, and a single table is normal to be 300GB. iPhone 14 with 512GB is available today as well, in case you think 300GB of data is unlikely for the output of the SELECT statement.
Putting the table size of 1TB in mind, storing the entire SELECT output into simple variable like array, will indirectly used up 1TB of RAM. In Java, that is consider Java heap memory.
Certainly they are members who suggest storing the data in array of HotelDao class which contains setHotelName(), getHotelName(), etc. This might double its Java internal memory usage from 1TB to 2TB.
Running any Java application, you have to specify its heap memory with Java parameter -Xmx (assuming only heap memory involve) during application startup. If you are hosting it in J2EE, then it could be Apache Tomcat startup, IBM WebSphere Application Server startup, JBoss, etc. If you are doing it via reporting server, e.g. Cognos, Informatica, then they have their own configuration to define -Xmx parameter.
Next, multi-user, multi-instance, concurrent access is norm in today's technology. If multiple users are trigger from GUI to request to run the same SELECT statement concurrently, e.g. flight booking system, reporting, then there will be multiple instance of the same SELECT with 1TB table size, and minimum 1TB Java array variable size in Java heap memory. Imagine 10 concurrent users, such as Expedia, will easily created 10 of such array variable, each of 1TB. The total variable size will be 10TB, if there is only 1 such SELECT-Array design in the entire program.
The problem here it is impractical to define 2TB of Java heap memory during Java application startup. Configures 10TB Java heap memory is way beyond the hardware limitation of today's RAM max size, unless you are looking for heavy paging to the OS swap memory area.
Neither Eclipse Temurin nor Oracle Java 19 document the default max heap size (-Xmx) anymore, but Eclipse Java 9 (https://www.eclipse.org/openj9/docs/openj9_defaults/) indicated the default is 25% RAM size. In other word, 128GB RAM machine will allocate 32GB. Either value will not even fit 1TB, and immediately the entire Java application crashed once it fully used up 32GB (default), or entire RAM, 128GB.
Certainly Windows admin will tell you Windows will auto tune its pagefile as needed. My question will be how poor will be business users' experience when finding a hotel in Expedia takes minutes. Even Windows allow to allocate 10TB pagefile, we have to consider overall application performance (end user experience) degradation with the trade off of OS paging to disk; due to Java application programmer storing entire SELECT output in local variable, and even bigger if stored as Java class object.
In entire StackOverflow.com, it is often members do not consider application performance when designing JDBC application, and consider about volume size. The latest Intel workstation single socket CPU can only accommodate 128GB RAM DDR4 (ref Xeon E-2388G). I have not see any programmer who have access to 1TB RAM top end server, even that can't handle Java application that need 10TB RAM due to this design
Note: Latest Intel desktop CPU Core i9 14th gen that supports DDR5 - max RAM size 128GB RAM today (2022-10). Ref www.crucial.com

Database doesn't like reading values from a loop

I have a java database successfully connected to my java code. Thats all fine as it works and all.
When I store a result from the database into a variable ... it works perfectly.
Now as I have to do this 8 times I used a loop and a array however by using a try catch tool it gives out a error of, Error is: java.lang.NullPointerException
Futher investigation shows that it seems to not like the loop strangely.
public String Title []; //in class but out of any methods
public void gettinginfo ()
{
try
{
int AB = 0; //array base starts from 0
//ID in database starts from 1
for (int i = 1; i<=8; i++)
{
String query = "SELECT * FROM students WHERE ID = " + i;
Rs = St.executeQuery(query);
while (Rs.next())
{
Title[AB] = Rs.getString("StudentName");
AB++;
}
}
}
catch (Exception ex)
{
System.out.println("Error is: " + ex);
}
}
What line is your NullPointerException occurring on? Likely your Title array has not been initialized. If you know how many rows the query will return, you can say:
Title = new String[numRows];
But if you don't, you'll need to either run a SELECT count(*) ... query or use an ArrayList or other resizable list, instead of an array.
Your code is also very poorly structured, which is no small part of why you're having trouble debugging this. I've cleaned up your code below, with comments explaining my changes:
public class YourClass
{
private static final int MAX_ID = 8; // or however you want to set the size
private String[] title; // [] after the type is easier to read, lower case variables
private Connection conn; // I'm assuming the class will be provided a DB connection
// Note the Statement and ResultSet objects are not defined in the class, to
// minimize their scope.
public void queryInfo() // name suggests a query (potentially expensive) will be run
{
title = new String[MAX_ID]; // **initialize title**
// We use a try-with-resources block to ensure the statement is safely closed
// even better would be to use a PreparedStatement here
try(Statement st = conn.statement())
{
// You're executing 8 separate queries here, where one will do
String query = "SELECT * FROM students WHERE ID >= 1 AND ID <= "+MAX_ID;
// Again, we need to close the result set when we're done
try(ResultSet rs = st.executeQuery(query))
{
int i = 0;
while (rs.next())
{
title[i++] = rs.getString("StudentName");
}
} // close our ResultSet
} // close our Statement
}
// provide a separate getter method, rather than making the array public
public String[] getTitles()
{
return title;
}
}
There's still more that could be improved - using an array seems like a poor design, as does calling a method which populates a class variable rather than simply having queryInfo() return a new array. You can also look into using PreparedStatement. Hopefully these suggestions help.
Make sure that Title array and Statement St objects are and not null. These are the only two reasons that I suspect. Give FULL stacktrace if it doesn't work.
Title array is NULL. "new" this array to the size equal to number of rows. If you don't know the rows, fire a count(*) query first, find out the no of rows and then intantiate Title array or use ArrayList<String> instead of String array.
I am assuming that you have not initialized your Title array, you have to set it equal to something or it will just be null which will cause a nullPointerException, but as others have stated there is no way to be sure since your haven't given us a full stack trace or even the line number of the exception. In this case the exception should be handled as such:
try{
//your code here
}catch(Exception ex){
ex.printStackTrace();
}
This code will give you the full stack trace making it much easier to track down the issue.
Also you may want to consider using an ArrayList instead of an array:
List<String> Title = new ArrayList<String>();
Then to add to it:
Title.add(Rs.getString("StudentName"));
If you need it as an array later then:
String[] title = Title.toArray(new String[Title.size()]);
You can read more about ArrayLists here.

How do I call this object to return all strings it finds?

I have the following code that defines a getParts method to find a given Part Name and Part Number in the system. Note that this code comes from our system's API, so if no one can help I'll just delete this question. I figured someone could potentially see a solution or help me along the way.
<%! private QueryResult getParts( String name, String number )
throws WTException, WTPropertyVetoException {
Class cname = wt.part.WTPart.class;
QuerySpec qs = new QuerySpec(cname);
QueryResult qr = null;
qs.appendWhere
(new SearchCondition(cname,
"master>name",
SearchCondition.EQUAL,
name,
false));
qs.appendAnd();
qs.appendWhere
(new SearchCondition(cname,
"master>number",
SearchCondition.EQUAL,
number,
false));
qr = PersistenceHelper.manager.find(qs);
System.out.println("...found: " + qr.size());
return qr;
}
%>
But I would like to allow the user more flexibility in finding these parts. So I set up conditional statements to check for a radio button. This allows them to search by part name and part number, find all, or search using a wildcard. However, I'm having trouble implementing the two latter options.
To attempt to accomplish the above, I have written the below code:
<%
String partName = request.getParameter("nameInput");
String partNumber = request.getParameter("numberInput");
String searchMethod = request.getParameter("selection");
//out.print(searchMethod);
QueryResult myResult = new QueryResult();
if(searchMethod.equals("search"))
myResult = getParts(partName, partNumber);
else if(searchMethod.equals("all"))
{
//Should I write a new function and do this?
//myResult = getAllParts();
//or is there a way I could use a for each loop to accomplish this?
}
//else if(searchMethod.equals("wildcard"))
//get parts matching %wildcard%
while(myResult.hasMoreElements())
{
out.print(myResult.nextElement().toString());
}
%>
Basically, it accepts user input and checks what type of search they would like to perform. Is there an easy way to pass all the values into the myResult object? And likewise for the wildcard search? Like I said before, it may be futile trying to help without access to the API, but hopefully it isn't.
Thanks!
You can (and should) reuse the function, but in order to do so, you will need a part name and number (as those are its input parameters). So for the multi-result options you will need to get a list/collection of part names+numbers and feed them individually to the function, then collect the result in the format that is most appropriate for your needs

How to protect against SQL injection when the WHERE clause is built dynamically from search form?

I know that the only really correct way to protect SQL queries against SQL injection in Java is using PreparedStatements.
However, such a statement requires that the basic structure (selected attributes, joined tables, the structure of the WHERE condition) will not vary.
I have here a JSP application that contains a search form with about a dozen fields. But the user does not have to fill in all of them - just the one he needs. Thus my WHERE condition is different every time.
What should I do to still prevent SQL injection?
Escape the user-supplied values? Write a wrapper class that builds a PreparedStatement each time? Or something else?
The database is PostgreSQL 8.4, but I would prefer a general solution.
Thanks a lot in advance.
Have you seen the JDBC NamedParameterJDBCTemplate ?
The NamedParameterJdbcTemplate class
adds support for programming JDBC
statements using named parameters (as
opposed to programming JDBC statements
using only classic placeholder ('?')
arguments.
You can do stuff like:
String sql = "select count(0) from T_ACTOR where first_name = :first_name";
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
return namedParameterJdbcTemplate.queryForInt(sql, namedParameters);
and build your query string dynamically, and then build your SqlParameterSource similarly.
I think that fundamentally, this question is the same as the other questions that I referred to in my comment above, but I do see why you disagree — you're changing what's in your where clause based on what the user supplied.
That still isn't the same as using user-supplied data in the SQL query, though, which you definitely want to use PreparedStatement for. It's actually very similar to the standard problem of needing to use an in statement with PreparedStatement (e.g., where fieldName in (?, ?, ?) but you don't know in advance how many ? you'll need). You just need to build the query dynamically, and add the parameters dynamically, based on information the user supplied (but not directly including that information in the query).
Here's an example of what I mean:
// You'd have just the one instance of this map somewhere:
Map<String,String> fieldNameToColumnName = new HashMap<String,String>();
// You'd actually load these from configuration somewhere rather than hard-coding them
fieldNameToColumnName.put("title", "TITLE");
fieldNameToColumnName.put("firstname", "FNAME");
fieldNameToColumnName.put("lastname", "LNAME");
// ...etc.
// Then in a class somewhere that's used by the JSP, have the code that
// processes requests from users:
public AppropriateResultBean[] doSearch(Map<String,String> parameters)
throws SQLException, IllegalArgumentException
{
StringBuilder sql;
String columnName;
List<String> paramValues;
AppropriateResultBean[] rv;
// Start the SQL statement; again you'd probably load the prefix SQL
// from configuration somewhere rather than hard-coding it here.
sql = new StringBuilder(2000);
sql.append("select appropriate,fields from mytable where ");
// Loop through the given parameters.
// This loop assumes you don't need to preserve some sort of order
// in the params, but is easily adjusted if you do.
paramValues = new ArrayList<String>(parameters.size());
for (Map.Entry<String,String> entry : parameters.entrySet())
{
// Only process fields that aren't blank.
if (entry.getValue().length() > 0)
{
// Get the DB column name that corresponds to this form
// field name.
columnName = fieldNameToColumnName.get(entry.getKey());
// ^-- You'll probably need to prefix this with something, it's not likely to be part of this instance
if (columnName == null)
{
// Somehow, the user got an unknown field into the request
// and that got past the code calling us (perhaps the code
// calling us just used `request.getParameterMap` directly).
// We don't allow unknown fields.
throw new IllegalArgumentException(/* ... */);
}
if (paramValues.size() > 0)
{
sql.append("and ");
}
sql.append(columnName);
sql.append(" = ? ");
paramValues.add(entry.getValue());
}
}
// I'll assume no parameters is an invalid case, but you can adjust the
// below if that's not correct.
if (paramValues.size() == 0)
{
// My read of the problem being solved suggests this is not an
// exceptional condition (users frequently forget to fill things
// in), and so I'd use a flag value (null) for this case. But you
// might go with an exception (you'd know best), either way.
rv = null;
}
else
{
// Do the DB work (below)
rv = this.buildBeansFor(sql.toString(), paramValues);
}
// Done
return rv;
}
private AppropriateResultBean[] buildBeansFor(
String sql,
List<String> paramValues
)
throws SQLException
{
PreparedStatement ps = null;
Connection con = null;
int index;
AppropriateResultBean[] rv;
assert sql != null && sql.length() > 0);
assert paramValues != null && paramValues.size() > 0;
try
{
// Get a connection
con = /* ...however you get connections, whether it's JNDI or some conn pool or ... */;
// Prepare the statement
ps = con.prepareStatement(sql);
// Fill in the values
index = 0;
for (String value : paramValues)
{
ps.setString(++index, value);
}
// Execute the query
rs = ps.executeQuery();
/* ...loop through results, creating AppropriateResultBean instances
* and filling in your array/list/whatever...
*/
rv = /* ...convert the result to what we'll return */;
// Close the DB resources (you probably have utility code for this)
rs.close();
rs = null;
ps.close();
ps = null;
con.close(); // ...assuming pool overrides `close` and expects it to mean "release back to pool", most good pools do
con = null;
// Done
return rv;
}
finally
{
/* If `rs`, `ps`, or `con` is !null, we're processing an exception.
* Clean up the DB resources *without* allowing any exception to be
* thrown, as we don't want to hide the original exception.
*/
}
}
Note how we use information the user supplied us (the fields they filled in), but we didn't ever put anything they actually supplied directly in the SQL we executed, we always ran it through PreparedStatement.
The best solution is to use a middle that does data validation and binding and acts as an intermediary between the JSP and the database.
There might be a list of column names, but it's finite and countable. Let the JSP worry about making the user's selection known to the middle tier; let the middle tier bind and validate before sending it on to the database.
Here is a useful technique for this particular case, where you have a number of clauses in your WHERE but you don't know in advance which ones you need to apply.
Will your user search by title?
select id, title, author from book where title = :title
Or by author?
select id, title, author from book where author = :author
Or both?
select id, title, author from book where title = :title and author = :author
Bad enough with only 2 fields. The number of combinations (and therefore of distinct PreparedStatements) goes up exponentially with the number of conditions. True, chances are you have enough room in your PreparedStatement pool for all those combinations, and to build the clauses programatically in Java, you just need one if branch per condition. Still, it's not that pretty.
You can fix this in a neat way by simply composing a SELECT that looks the same regardless of whether each individual condition is needed.
I hardly need mention that you use a PreparedStatement as suggested by the other answers, and a NamedParameterJdbcTemplate is nice if you're using Spring.
Here it is:
select id, title, author
from book
where coalesce(:title, title) = title
and coalesce(:author, author) = author
Then you supply NULL for each unused condition. coalesce() is a function that returns its first non-null argument. Thus if you pass NULL for :title, the first clause is where coalesce(NULL, title) = title which evaluates to where title = title which, being always true, has no effect on the results.
Depending on how the optimiser handles such queries, you may take a performance hit. But probably not in a modern database.
(Though similar, this problem is not the same as the IN (?, ?, ?) clause problem where you don't know the number of values in the list, since here you do have a fixed number of possible clauses and you just need to activate/disactivate them individually.)
I'm not confident if there is a quote() method, which was widely used in PHP's PDO. This would allow you a more flexible query building approach.
Also, one of the possible ideas could be creating special class, which would process filter criterias and would save into a stack all placeholders and their values.

Categories

Resources