I have a problem using getSelectedIndices(). Occassionally, not always, as I have discovered in debug sessions in the database code, it returns an array that includes an index of -1.
Why is that so, and what could I do to prevent this? I want to use this style of logic is various places.
I have a Livestock TableView with SelectionMode.MULTIPLE.
It has a ContextMenu that includes ('Set Breed', 'Set Sex' and 'Set Has Calf')
The logic to set one of these attributes is the same:
The user will, (1) select the relevant rows (using or + mouse-click), then (2) select the relevant MenuItem
This will then invoke setLivestockAttribute() as follows:
private void setLivestockAttribute( String attribute )
{
String value = "";
// 1. Get a List of the Indices of Rows selected.
ObservableList<Integer> selectedIndices = null;
selectedIndices = FXCollections.observableList(tblView_Livestock.getSelectionModel().getSelectedIndices());
// If ONE or MORE rows have been selected.
if ( selectedIndices.size() > 0 )
{
// 2. Get the VALUE of the relevant ATTRIBUTE.
switch (attribute)
{
case LM_Constant.BREED:
value = LM_Utility.getChoice_Breed();
break;
case LM_Constant.SEX:
value = LM_Utility.getChoice_Sex();
break;
case LM_Constant.HAS_CALF:
value = LM_Utility.getChoice_HasCalf();
break;
}
// If there is a VALUE.
if ( value.length() > 0 )
{
ObservableList<LivestockModel> dataList = tblView_Livestock.getItems();
// 3. Update each Livestock record.
DataStore.getInstance().updateLivestock(dataList, selectedIndices, attribute, value);
// 4. Refresh the TableView to show changes.
setLivestockData();
}
}
}
I have decided to:
update the JDK ( as suggested by #sillyfly)
adjust the code to avoid the error (see below)
subscribe to Oracle to receive notices of future upgrades and bug fixes
Code segment ensures database update, ONLY when the index i >= 0.
public boolean updateLivestock(ObservableList<LivestockModel> livestockData, ObservableList<Integer> selectedIndices, String attribute, String value)
{
boolean proceed = true;
boolean updated = false;
int rowCount = 0;
int counter = 0;
String sql = "";
LivestockModel lm;
switch (attribute)
{
case LM_Constant.BREED:
sql = "UPDATE livestock SET (breed, last_updated) = (?, DEFAULT) WHERE rfid = ? AND begin_event = ?";
break;
case LM_Constant.SEX:
sql = "UPDATE livestock SET (sex, last_updated) = (?, DEFAULT) WHERE rfid = ? AND begin_event = ?";
break;
case LM_Constant.HAS_CALF:
sql = "UPDATE livestock SET (has_calf, last_updated) = (?, DEFAULT) WHERE rfid = ? AND begin_event = ?";
break;
}
try ( PreparedStatement preparedUpdate = connection.prepareStatement(sql) )
{
for (Integer i : selectedIndices)
{
if ( proceed != true )
break;
if ( i >= 0 )
{
lm = livestockData.get(i);
preparedUpdate.setString(1, value);
preparedUpdate.setString(2, lm.getRFID());
preparedUpdate.setInt(3, lm.getBeginEvent());
rowCount = preparedUpdate.executeUpdate();
if ( rowCount == 1 )
counter++;
}
else
{
counter++;
LM_Utility.showError_Dialog("Update Livestock", "Index Error", "index = " + i.toString());
}
}
if ( counter == selectedIndices.size() )
{
connection.commit();
updated = true;
}
}
catch (SQLException sqle)
{
LM_Utility.showSQL_Exception("updateLivestock()", sqle);
proceed = false;
}
return updated;
}
Related
I'm working on a Java project with FX and SQL. In this Java project I have a store where I can add products to the database.
Now I also want that I can update a product. I can only update the price of the product but not the brand name and type name. When I want to update the brand name or the type name I receive an error in the console.
This is the code to update a product:
public int editProduct(Double price, int brand, int type, int id) {
String sql = "UPDATE products SET price=?, id_brand=?, id_type=? WHERE id=?";
try {
Class.forName(DRIVER);
Connection con = DriverManager.getConnection(DB_URL, USER, PASS);
PreparedStatement stmt = con.prepareStatement(sql);
stmt.setDouble(1, price);
stmt.setInt(2, brand);
stmt.setInt(3, type);
stmt.setInt(4, id);
int rows = stmt.executeUpdate();
getProducts();
con.close();
return rows;
} catch (ClassNotFoundException | SQLException ex) {
System.out.println("SQL error updaten product");
System.out.println(ex.getMessage());
}
return -1;
}
-
public class EditProductController {
#FXML TextField productId, productBrand, productType, productPrice;
#FXML Label berichtMelding;
#FXML Button saveButton;
private StoreDataAccesObject storeDDA;
public void fill (Product data) {
berichtMelding.setText(Integer.toString(data.getId()));
productType.setText(data.getType());
productBrand.setText(data.getBrand());
productPrice.setText(Double.toString(data.getPrice()));
}
public void updateProductButton() {
storeDDA = new StoreDataAccesObject("noorderpoort", "toets");
ArrayList<String> errorList = new ArrayList<>();
String brand = productBrand.getText();
String type = productType.getText();
String price = productPrice.getText();
String id = berichtMelding.getText();
int idBrand;
int idType;
if (brand.trim().length() <= 0 || type.trim().length() <= 0 || price.trim().length() <= 0) {
errorList.add("Alle velden moeten ingevuld zijn.");
} else {
if (storeDDA.nameExists("brands", brand) > 0) { //checking if brand exists in database.
idBrand = storeDDA.nameExists("brands", brand);
} else {
idBrand = storeDDA.nameExists("brands", brand);
if (idBrand < 0) {
errorList.add("Brand niet opgeslagen");
}
}
if (storeDDA.nameExists("types", type) > 0) {
idType = storeDDA.nameExists("types", type);
} else {
idType = storeDDA.nameExists("types", type);
if (idType < 0) {
errorList.add("Type niet opgeslagen");
}
}
if (storeDDA.editProduct(Double.parseDouble(price), idBrand, idType, Integer.parseInt(id)) <= 0) {
berichtMelding.setText("Product niet geupdate.");
} else {
Stage editProduct = (Stage) saveButton.getScene().getWindow();
editProduct.close();
}
}
if (!errorList.isEmpty()) {
String errorText = "";
for (String error : errorList) {
errorText += error;
}
berichtMelding.setText(errorText);
}
}
}
Tables:
'Data truncation: Out of range value for column 'id_brand' at row 1'?
The data you are trying to store in id_brand does not fit. For example, string might be too long or number too large.
Check the value of unsigned id_brand column if it meets the range.
You have defined the length of id_brand column as 11 and i think you are trying to add value with length more than 11. I reproduced the same error at my end.
create table Test(id integer(11) unsigned);
insert into Test values(1234567891011);
Error:Out of range value for column 'id'ar row 1
And if you are trying to add String value in Integer column then it will not throw the above mentioned error instead it will show:
Incorrect integer value: 'A' for column 'id' at row 1
I noticed that when I try to insert a record into a Primary Key column, I'm getting a NULL not allowed for column error, even if the value I'm trying to enter for that column is not null. Does anyone know why this would happen!? My PreparedStatement.toString() is below along with the stack trace. Thank you very much for any help!
PreparedStatement = 'prep0: MERGE INTO results (rank,lastname,firstname,country,time) VALUES (?,?,?,?,?); {1: '1', 2: 'smith', 3: 'john', 4: 'USA', 5: '2:30'}'...
Exception in thread "main" org.h2.jdbc.JdbcSQLException: NULL not allowed for column "FIRSTNAME"; SQL statement:
MERGE INTO results (rank,lastname,firstname,country,time) VALUES (?,?,?,?,?); [23502-193]`
at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
at org.h2.message.DbException.get(DbException.java:179)
at org.h2.message.DbException.get(DbException.java:155)
at org.h2.table.Column.validateConvertUpdateSequence(Column.java:311)
at org.h2.table.Table.validateConvertUpdateSequence(Table.java:784)
at org.h2.command.dml.Merge.merge(Merge.java:157)
at org.h2.command.dml.Merge.update(Merge.java:106)
at org.h2.command.CommandContainer.update(CommandContainer.java:98)
at org.h2.command.Command.executeUpdate(Command.java:258)
at org.h2.jdbc.JdbcPreparedStatement.execute(JdbcPreparedStatement.java:201)
at Table.merge(Table.java:272)
at CSV.writeToDatabase(CSV.java:643)
at CLI.combineData(CLI.java:152)
at CLI.run(CLI.java:48)
at CLI.main(CLI.java:163)`
here is the method that creates the query:
void merge( Map<String,Object> values ) throws SQLException
{
if ( values.isEmpty() )
{
System.out.println("No data in values map...");
return;
}
query = new StringBuilder("MERGE INTO " + name + " (");
List<String> attrs = new ArrayList<>(values.keySet());
for ( int a=0; a < attrs.size(); a++ )
{
query.append( a > 0 ? "," : "" ).append( attrs.get(a).toLowerCase() );
}
query.append(") VALUES (");
for ( int a=0; a < attrs.size(); a++ )
{
query.append( a > 0 ? ",?" : "?" );
}
query.append(");");
PreparedStatement ps = db.conn.prepareStatement(query.toString());
for ( int v=0; v < attrs.size(); v++ )
{
Object value = values.get(attrs.get(v));
if ( value == null )
{
ps.setString(v+1, null);
}
else if ( value instanceof String || value instanceof Date)
{
ps.setString(v+1, value.toString() );
}
else if ( value instanceof Integer )
{
ps.setInt(v+1, (Integer) value);
}
else if ( value instanceof Double )
{
ps.setDouble(v+1, (Double) value);
}
else
{
ps.setString(v+1, null);
}
}
System.out.println("PreparedStatement = '" + ps.toString() + "'...");
ps.execute();
}
I am making a code generator that makes a new code and then querys a database to see if it exists. If it does, try again to make a different code. If it doesn't exist, add it into the database. But when I add the one code into the database, the query adds 3 different rows with 3 different values. One of the values, is the one supposed to be added, and the other two I don't know where they come from. Why is it inserting 3 when I only set it to add one. My full class file is:
package com.xium.accesscode;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.concurrent.ThreadLocalRandom;
import com.xium.log.ServerLogger;
import com.xium.sql.DBConnections;
import com.xium.utils.StringUtils;
public class NewAccessCode {
static String AccessCodeDBuser = "root";
static String AccessCodeDBpass = "";
static String AccessCodeDBhost = "localhost";
static String newAccessCode;
static String randS;
static String randFinal;
static int min = 000000000;
static int max = 999999999;
static int randI;
public static void AccessCode() {
if(newAccessCode() == 0) {
ServerLogger.writeLog("[ALERT] Database Error");
} else if(newAccessCode() == 1) {
//Reruns the code generator, to make a unique code
newAccessCode();
} else if(newAccessCode() == 2) {
ServerLogger.writeLog("[NOTE] New Access Code: " + newAccessCode);
}
}
/*
* Return Codes:
* 0 - Database Error
* 1 - Code Already Exists
* 2 - New Access Code Added
*/
private static int newAccessCode() {
genAccessCode();
newAccessCode = randFinal;
//Does it already exist?
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet results = null;
String statement = "SELECT count(*) FROM `xium`.`accesscodes` WHERE `accesscode`='" + newAccessCode + "'";
String statement2 = "INSERT INTO `xium`.`accesscodes` (`accesscode`, `used`, `assignedto`) VALUES ('" + newAccessCode + "', '0', '')";
try {
connection = DBConnections.getAccessCodeDB(AccessCodeDBuser, AccessCodeDBpass, AccessCodeDBhost);
preparedStatement = connection.prepareStatement(statement);
results = preparedStatement.executeQuery();
results.next();
if(results.getInt(1) == 0) {
} else if(results.getInt(1) >= 1) {
return 1;
}
connection = DBConnections.getAccessCodeDB(AccessCodeDBuser, AccessCodeDBpass, AccessCodeDBhost);
preparedStatement = connection.prepareStatement(statement2);
preparedStatement.executeUpdate();
return 2;
} catch (SQLException e) {
return 0;
}
}
private static String genAccessCode() {
randI = ThreadLocalRandom.current().nextInt(min, max + 1);
randS = randI + "";
randFinal = StringUtils.toMD5(randS);
return randFinal;
}
}
Every time you run your AccessCode() function, the if statements are running the statement as well. So don't do:
if(newAccessCode() == 0)
You should make a new integer value set equal to the value of your newAccessCode() function and then check the value of the int.
So:
int returnValue = newAccessCode();
Then check the value of the returnValue.
if(returnValue == 0)
That should fix your problem.
There is a call to newAccessCode() method in AccessCode() static method three times.
Change that to
public static void AccessCode() {
int newAccessCodeReturn = newAccessCode();
if(newAccessCodeR`enter code here`eturn == 0) {
ServerLogger.writeLog("[ALERT] Database Error");
} else if(newAccessCodeReturn == 1) {
//Reruns the code generator, to make a unique code
newAccessCode();
} else if(newAccessCodeReturn == 2) {
ServerLogger.writeLog("[NOTE] New Access Code: " + newAccessCode);
}
}
You're calling newAccessCode() repeatedly in your if/else if code. Every time you do this it inserts into the DB. Call it once and save the result in a variable.
int result = newAccessCode();
if(result == 0) {
ServerLogger.writeLog("[ALERT] Database Error");
} else if(result == 1) {
//Reruns the code generator, to make a unique code
newAccessCode();
} else if(result == 2) {
ServerLogger.writeLog("[NOTE] New Access Code: " + newAccessCode);
}
or use a switch statement:
switch (newAccessCode()) {
case 0:
ServerLogger.writeLog("[ALERT] Database Error");
break;
case 1:
//Reruns the code generator, to make a unique code
newAccessCode();
break;
case 2:
ServerLogger.writeLog("[NOTE] New Access Code: " + newAccessCode);
break;
}
I have a ResultSet with a sql select query :
ResultSet rst = DB.search("select '"+col+"' from stud where '"+col+"' like '" + S3 + "%'");
In here col = FName(FName is a column);
Here's how FName gets assigned to col :
private void column(){
switch (search_fields.getSelectedItem().toString()) {
case "FName":
col = "FName";
break;
case "MName":
col="MName";
break;
case "LName":
col="LName";
break;
case "DOB":
col="DOB";
break;
case "Address":
col="Address";
break;
case "MotherTP":
col="MotherTP";
break;
case "FatherTP":
col="FatherTP";
break;
case "School":
col="School";
break;
case "Grade":
col="Garde";
break;
case "Email":
col="Email";
break;
}
}
Search_field is a combobox.
There is no error but when I type a First Name(FName) the name of the column FName gets returned.
Here is the Whole Code :
private JTextField txtComboItemName;
private String S3;
private boolean bbb;
private void ComboItemSearch() {
bbb = false;
txtComboItemName = (JTextField) search_txt.getEditor().getEditorComponent();
txtComboItemName.addKeyListener(new KeyAdapter() {
#Override
public void keyReleased(KeyEvent evt) {
if (!(
evt.getKeyCode() == KeyEvent.VK_DOWN ||
evt.getKeyCode() == KeyEvent.VK_UP ||
evt.getKeyCode() == KeyEvent.VK_LEFT ||
evt.getKeyCode() == KeyEvent.VK_RIGHT ||
evt.getKeyCode() == KeyEvent.VK_ENTER)) {
try {
S3 = txtComboItemName.getText();
ResultSet rst = DB.search("select '"+col+"' from stud where '"+col+"' like '" + S3 + "%'");
System.out.println("col:"+ col);
boolean b = rst.next();
boolean bb = false;
if (b) {
search_txt.removeAllItems();
bb = true;
}
while (b) {
if (rst.getString(col).startsWith(S3)) {
search_txt.addItem(rst.getString(1));
}
b = rst.next();
}
search_txt.setSelectedItem(S3);
txtComboItemName.setCaretPosition((search_txt.getSelectedItem() + "").length());
search_txt.showPopup();
int i = search_txt.getItemCount();
if (i > search_txt.getMaximumRowCount()) {
search_txt.setMaximumRowCount(1000);
} else {
search_txt.setMaximumRowCount(i);
}
bbb = true;
} catch (Exception ex) {
ex.printStackTrace();
}
} else if (
evt.getKeyCode() == KeyEvent.VK_ENTER &&
bbb == true && evt.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
boolean bIT = false;
String Sr123 = (String) search_txt.getSelectedItem();
try {
ResultSet Rst23 = DB.search("select '"+search_fields.getSelectedItem().toString()+"' from stud");
while (Rst23.next()) {
if (Sr123.equals(Rst23.getString(search_fields.getSelectedItem().toString()))) {
bIT = true;
break;
} else {
bIT = false;
}
}
bbb = false;
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});
}
At least one problem is the query generated will be as:
select 'COL' from stud where 'COL' like ..
When it should look like
select COL from stud where COL like ..
-- or whatever is appropriate for the database (also note selecting into
-- a well-known column in this second case)
select [COL] as result from stud where [COL] like ..
That is, the column names are incorrectly quoted as strings, and not used as identifiers in SQL.
There are other issues, SQL Injection - as the value supplied to LIKE should be bound by a placeholder, and an over complexity of code, and possibly more.
Consider these additional notes:
List<String> allowedNames = Arrays.asList<String>("FName", ..);
// Ensures the name is valid, or throws an Exception;
// it could also return a normalized name or a boolean, but an
// Exception is the quickest way to ensure "fail fast".
private void assertSearchableColumn(string colName) {
if (!allowedNames.contains(colName)) {
throw new RuntimeException("Invalid column");
}
}
// Then before a particular column is replaced in the SQL command, but there
// is no need to have function that merely sets the global variable.
String col = search_fields.getSelectedItem().toString();
assertSearchableColumn(col);
// Only replace columns, note that the columns are *not* quoted as strings
// in the resulting SQL, and that ? represents "a placeholder".
String sql = String.format("select %s from stud where %s like ?", col, col);
// And then bind the SQL with the appropriate value to use with LIKE.
// (I have no idea what "DB" is or how/if it supports placeholders, however..
// but if it does not already, it *should* support placeholders
// or else it is too easy for SQL Injection, accidental or otherwise.)
I have a function which reads a List of Notifications Object. Now I have to read the Notifications object in batches of 100(suppose) and update it.
public boolean addBSInfoToTemp(List<ParentNotification> pNotify)
throws DaoException, BatchUpdateException {
int cnt = 0;
final String query = "insert into Temp values ?,?,?,'Y','N',?,?,?,?";
while (!pNotify.isEmpty()) {
try {
pst = getConnection().prepareStatement(query);
for (ParentNotification pn : pNotify) {
cnt++;
pst.setInt(1, pn.getUserId());
pst.setString(2, pn.getEmail());
pst.setString(3, pn.getNotificationType());
Date createdDate = (Date) pn.getCreatedDate();
pst.setDate(4, createdDate);
pst.setString(5, pn.getCreatedBy());
Date icsesCreatedDate = (Date) pn.getIcsesCreatedDate();
pst.setDate(6, icsesCreatedDate);
pst.setString(7, pn.getMiscellaneous());
pst.addBatch();
if(cnt==batchCount){
break;
}
}
int[] batch = pst.executeBatch();
if (batch.length != 0) {
flag = true;
}
} catch (BatchUpdateException b) {
flag = false;
} catch (SQLException sqlx) {
flag = false;
} finally {
close(pst, null);
}
}
return flag;
}
What I am trying to do is read the List with batchCount = 100 then update and go back to 101 record and then update another 100 records untill the List is empty.
You have this:
while (!pNotify.isEmpty()) {
but I don't see that you ever remove objects from the list, so you have infinite loop. I would just make the outside loop to loop through all of the elements like this:
for (int i=0; i<=pNotify.size(); i++){
// and inside check if it is devisible by batch length
if(i % batchCount == 0){
break;
}
}