I am facing troubles when building a generic preparedStatement : I have 8 SQL Tables, which are all manipulated the same way, so I'd like to build a unique manager which could insert into / select from any of the 8 tables.
To do so, each table has a descriptor, which can provide the fields of a table, its name and the array of values when inserting.
In the manager, the prepared statement to insert is of the following form :
"INSERT INTO " + table_name + " VALUES (?)"
Then, I fill the gap with something like
myPreparedStatement.setString(1, values.getAllValues());
the getAllValues() method must return a string which holds every fields, like " 'This', 'Is', 3, 'example' ".
I have no problem with strings and numbers, but I can't add any date in those values...
Using September 3rd, 2008 as example, I used the following formats :
2008-09-03,
08-09-03,
080903,
03092018, but all fail. "yyMMdd" format seemed like the best option from what I saw here and there, but I have the error :
"java.sql.SQLDataException: ORA-01843: not a valid month"
And I have no idea why... has anyone faced this issue before ?
I know there are lots of posts here that talks about inserting dates in database, but they all use the
preparedStatement.setDate(pos, Date);
Statement, and I can't do that since the dates aren't in the same position in all of my tables.
EDIT :
As asked in the comment, here is a minimal sample that reproduce what I'm trying to do. If you want to reproduce as well, I let you handle the connection and database setup :
public class Sample {
public void saveAll() throws ServiceException {
Connection c = null;
PreparedStatement ps = null;
String sql = "INSERT INTO " + getTableName() +" VALUES (?)";
try {
c = getConnection();
c.setAutoCommit(false);
batch = c.prepareStatement(sql);
batch.setString(getAllFieldValues());
int res = batch.executeUpdate();
c.commit();
} catch (BatchUpdateException b) {
throw new ServiceException("Erreur lors de l'exécution du batch", b);
} catch (SQLException s) {
throw new ServiceException("Impossible de sauvegarder les beans en base.", s);
} finally {
getManager().close(batch);
freeConnection(c);
}
}
public String getAllFieldValues() {
return "'Hello', 'World', 42, '171228'";
}
public String getTableName() {
return "myTableName";
}
}
There is no such thing as generic preparedStatement in JDBC. To insert four columns in table T you must use
INSERT into T (col1,col2,col3,col4) values (?,?,?,?)
You may ommit the first list with the column names, but this is a bad practice as you trust on the actual columns of the table that may change.
Using only
INSERT into T values (?,?,?,?)
work fine until somebody modifies the table by adding or dropping a column and will fail afterwards.
All bind variables must be set extra with the setXXX method with appropriate type and index of the column starting with 1.
stmt.setInt(1,100)
stmt.setString(2,'xxx')
If I can understand your question correctly. For dynamically placing your date value in prepared statement you can override setString() method to have your custom code to check for date value or else.
Or rather in case you can also have local method to check if coming string is of format date.
For this you can simply pass Date String with some prefix attached so that you can check it in custom setString() method.
setString(String string, int position){
if(string.contains("SPECIFIC_PREFIX_CONSTANT")){
//batch.setDate(position, string.substring("REMOVE PREFIX AND ATTACH"));
}else{
//batch.setString(position, string);
}
}
Ok guys, I managed to have my stuff working, big thanks to all of you !
In case somebody else would end on my question, I'll recap the Code I have now, which works :)
So, as said previously, we have one Manager that interacts with the database and which has no knowledge of the table's he interacts with.
Here is the code of the save method of this manager :
public void saveAll(AbstractBeanClass[] values, String refSelected) {
// connexion setup
Connection c = null;
PreparedStatement batch = null;
// fetch table's fields, to prepare the placeholders
String fields = values[0].getAllFields();
String sql = "INSERT INTO " + values[0].getTableName() + " (" + fields + ") VALUES (";
StringBuffer places = new StringBuffer();
int [] res = null;
// Start at 1 to have one field left, to end the parenthesis
for(int i = 1; i < values[0].getNumberOfFields(); i++) {
places.append("?, ");
}
// last field
places.append("?)");
sql = sql.concat(places.toString()); // We now have a full (?, ..., ?)
try {
c = getConnection();
c.setAutoCommit(false);
batch = c.prepareStatement(sql);
// Filling the batch
int j = 1;
for(AbstractBeanClass bean : values) {
int i = 1;
for(String type : bean.getAllTypes()) {
switch(type) {
case "int" : {
batch.setInt(i, (int) bean.getOrderedValue(i));
}
break;
case "String" : {
batch.setString(i, (String)bean.getOrderedValue(i));
}
break;
case "Date" : {
batch.setDate(i, (java.sql.Date) bean.getOrderedValue(i));
}
break;
}
i++;
}
batch.addBatch();
// In case of numerous insertions, some Databases don't allow more than 1000 inserts at a time
if(j%1000 == 0) {
res = batch.executeBatch();
for(int k : res) {
if(k == Statement.EXECUTE_FAILED) {
getManager().close(batch);
freeConnection(c);
throw new RuntimeException("Error while inserting values.");
}
}
}
j++;
}
// last execution
res = batch.executeBatch();
for(int i : res) {
if(i == Statement.EXECUTE_FAILED) {
getManager().close(batch);
freeConnection(c);
throw new RuntimeException("Error while inserting values in database.");
}
}
c.commit();
logger.debug("Insertion succeeded, we inserted " + j + " lines.");
} catch (BatchUpdateException b) {
throw new RuntimeException("Error in batch : ", b);
} catch (SQLException s) {
throw new RuntimeException("Error : we couldn't save the values : ", s);
} finally {
getManager().close(batch);
freeConnection(c);
}
}
So this is the main part of the program, but it needs the table descriptor. To keep it simple, I made an abstract class which declares the methods I need, and all table descriptors extends this class, here is the declaration :
package com.fr.sncf.fret.boctarification.domaine.referentiel;
import java.io.Serializable;
import java.text.SimpleDateFormat;
public abstract class DaoGenericReferentielBean implements Serializable {
private static final long serialVersionUID = 1L;
protected String allFields;
// the date Format used to insert the dates in base
protected final SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd");
public DaoGenericReferentielBean() {
// empty constructor
}
/**
* Return all columns' names, ordered according to database's order
* #return
*/
public String getAllFields() {
return this.allFields;
}
/**
* Returns all values ordered by columns' order
* #return String
*/
public abstract String getAllFieldsValues();
/**
* #return the table name
*/
public abstract String getTableName();
/**
* #return the number of field in this table
*/
public abstract int getNumberOfFields();
/**
* Returns the ordered list of column's type
*/
public abstract String[] getAllTypes();
/**
* Return the value corresponding to the given index
* Values are treated here according to the database's columns order
* #param index the column's number
* #return an Object, either an int, or a String, or a Date
*/
public abstract Object getOrderedValue(int index);
}
All you need now is to describe your table according to this model, Hope it helps !
Related
I am looking at this post How to insert array values in Mysql database using java with keen interest. But just to get started I find that MySQL Workbench rejects the indicated sql statements for table creation and update(insert).
I ended up loading a single line dataframe from R into my MySQL demo database to create table itemtable. This is not ideal since the item values are intended to be integers, but loading like this from R creates the fields as double.
itemtable fields are "time", "item1", "item2", "duration"
initial line values are 0.0, 1, 1, 0.0
I created an Item class:
public class Item {
String name;
double value;
public Item(String name, double value) {
this.name = name;
this.value = value;
}
In my real application Items are far more complex and value is calculated at different times. So I use a LinkedList to hold these. The quantity of items is variable from model to model as an input of other data tables just as I did from R.
The full test code - That works!!! as follows:
public class Item {
String name;
double value;
public Item(String name, double value) {
this.name = name;
this.value = value;
}
}
import java.sql.*;
import java.util.LinkedList;
public class ItemTest {
public static void main(String[] args) throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
Statement stmt = null;
ResultSet rs = null;
String dbUrl = "jdbc:mysql://localhost:3306/demo?useSSL=false";
String user = "student";
String pass = "student";
LinkedList<Item> itemList = new LinkedList<>();
itemList.add(new Item("item1", 0.0));
itemList.add(new Item("item2", 1.0));
double timeNow = 30.0;
double duration = 0.0;
String qMarks = "";
String names = "";
for(int i = 0; i < itemList.size(); i++) {
qMarks = qMarks + "?,";
names = names + itemList.get(i).name + ",";
}
System.out.println(qMarks);
System.out.println(names);
String pquery = "insert into itemtable(time," + names +
"duration) values(?," + qMarks + "?)";
System.out.println(pquery);
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/demo", "student" , "student");
pstmt = conn.prepareStatement(pquery);
pstmt.setDouble(1, timeNow);
for(int i = 0; i<itemList.size(); i++) {
pstmt.setDouble(i+2, itemList.get(i).value);
}
pstmt.setDouble(itemList.size()+2, duration);
int count = pstmt.executeUpdate();
System.out.println(count + " inserted");
stmt = conn.createStatement();
rs = stmt.executeQuery("select * from itemtable");
while (rs.next()) {
System.out.println(rs.getDouble("time") + ", " + rs.getDouble("item1"));
}
}
catch (SQLException exc){
}
finally {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
}
}
}
Full verification of the table insert is confirmed in MySQL Workbench.
It would still be nice if I could create the itemtable from within java code.
As the linked SO post tells you: SQL's 'ARRAY' concept (which is mirrored by JDBC's .setArray and .getArray and java.sql.Array) just doesn't work in mysql. At all.
I suggest you use a better DB engine, such as postgres. Otherwise, well, you can't have arrays; what you can try to do is make a table to represent it. The usual many-to-one stuff in DBs: Instead of 'an array', you have a second table containing 1 row for each 'slot' in all arrays across the entire dataset: Each row in this 'array table' contains the data as well as a reference.
So, for example:
-- This works in PSQL:
CREATE TABLE example (id integer, whatever text, names text[], PRIMARY KEY(id));
-- but the above does not work in MySQL, so there do e.g.:
CREATE TABLE example (id integer, whatever text, PRIMARY KEY(id));
CREATE TABLE example_names (id integer, example_id integer, name text, PRIMARY KEY(id));
CREATE INDEX example_names_lnk ON example_names(example_id);
It turns out that what I was trying to do with the list item with quantity defined at runtime was indeed different than trying to enter an array in a MySQL table, which I now understand cannot be done. I do appreciate the earlier discussion, which sparked my interest in solution I needed.
The create table sql is generated in a straight forward way, the key to handling the unknown content at runtime was to build the query string using a loop for the item columns. A single loop built internal string items for the create statement as well as the prepared statements.
So the sql for creating the table in java code is straight forward. Item values can now be integers as intended.
String qMarks = "";
String names = "";
String items = "";
for(int i = 0; i < itemList.size(); i++) {
qMarks = qMarks + "?,";
names = names + itemList.get(i).name + ",";
items = items + itemList.get(i).name +" INTEGER,";
}
String createsql = "CREATE TABLE ITEMTABLE (" +
"time DOUBLE, " + items +
"duration DOUBLE)";
I have a phonebook project I'm trying to make for school.
I want to be able to change a value of a certain field.
I print the field that are changeable.
before i had PRIMARY KEY it changed everyone for example if i changed the name, it would change everyone that is on the list name.
Now I tried to catch a specific one with a id as primary key and it throws this exception:
Parameter index out of range (2 > number of parameters, which is 1).
(this code now works if someone will see it in the future :)
I incremented the contactNum in the editConatct() to match it with the id field in my MySQL data base )
my edit code:
protected void editContawct(){
System.out.print("Which One You Want To Edit: ");
ArrayList<String> fields = editContactHelper();
ArrayList<Person> people = checkMoreThanOne();
int contactNum = menu(people);
int option = menu(fields);
System.out.println("Please Enter The Changes");
String changes = MyUtills.readStringFromUser();
String sql = "UPDATE " + DB_NAME + " SET " + fields.get(option) + " = ? WHERE ID = ?";
try {
statement = db.prepareStatement(sql);
statement.setString(1, changes);
statement.setInt(2,contactNum + 1); //contactNum `incremented to match 'id' field in my MySQL.`
statement.executeUpdate();
System.out.println("worked ?");
} catch (SQLException e) {
e.printStackTrace();
}
}
//todo add a param that accepts a String that is the data base name.
private ArrayList<String> editContactHelper(){ // should accept data base name
ArrayList<String> fieldsName = new ArrayList<>();
//todo switch the constant to the accepted argument (data base name). so if someone else wants to use this method :)
String sql = "SELECT * FROM " + DB_NAME; // should be databaseName;
try {
ResultSet resultSet = db.prepareStatement(sql).executeQuery();
ResultSetMetaData meta = resultSet.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++) {
if(meta.getColumnName(i).equals("id")){
continue;
}
fieldsName.add(meta.getColumnName(i));
}
System.out.println("it worked Homie");
return fieldsName;
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
Each ? is your query should relate to one parameter
But you have a mismatch
statement.setString(1, changes);
statement.setInt(2,contactNum);
as you only have one ?
So im getting this NPE whenever im trying to return an ArrayList object into an FXCOLLECTIONS list.
Here is the Code.
Method return the al to FXCOLLECTIONS.
public ArrayList<MeetBooking> selectMeetBookings()
{
ArrayList<MeetBooking> meetBookingArrayList = new ArrayList<>();
System.out.println("Trying to select from table meetBooking");
try
{
//Query the database and storing the result in an object of type ResultSet
sqlString = "SELECT * FROM CPC.meetBooking ORDER BY bookingID ASC";
rs = stmt.executeQuery(sqlString);
//Use the methods of class ResultSet in a loop
// to display all of the data in the result
while (rs.next())
{
int id = rs.getInt("bookingID");
Date bookingDateTime = rs.getDate("bookingDateTime");
System.out.println(bookingDateTime.toString());
int empID = rs.getInt("employeeID");
String bookingcmnt = rs.getString("bookingComment");
MeetBooking meetBooking = new MeetBooking();
meetBooking.setBookingID(id);
meetBooking.setBookingDate(bookingDateTime.toString());
meetBooking.setEmployeeID(empID);
meetBooking.setBookingComment(bookingcmnt);
meetBookingArrayList.add(meetBooking);
}
}
catch (Exception e)
{
e.printStackTrace();
}
return meetBookingArrayList;
}
Public Class MeetBooking
//Method to set a date as a string utilises Stringproperty and //simplestringproperty
public void setBookingDate(String value)
{
bookingDate.set(value);
}
//Controller access to first method
public ArrayList<MeetBooking> getMeetBookings()
{
return dbcon.selectMeetBookings();
}
// UI access to controller.getMeetBooks
meetBookingObservableList = FXCollections.observableArrayList(controller.getMeetBookings());
Im not quite sure how to solve this, but i think the Date is the culprit.
However I've been debugging this for a long time and need your expertise.
One potential reason for the NPE is that your database can contain Null values in some of your columns.
If you have Nulls in the bookingDateTime or the employeeID columns, then you will get an exception in one of the following lines:
Date bookingDateTime = rs.getDate("bookingDateTime");
System.out.println(bookingDateTime.toString());
int empID = rs.getInt("employeeID");
If I remember my Java, the Date class can contain a null. However, if the value is null, then bookingDateTime.toString() will attempt to convert the Null to string. A Null does not have a method, so you get that NullPointerException.
Also, an int cannot store Nulls, so you have a similar issue when attempting to force a Null into empID.
How would I modify this so that it uses meta data to print the table?
public String formatStudentList() throws SQLException
{
String result = String.format("%-15s %-25s %-25s", "Student ID", "Student Name", "Degree
Scheme\n" + String.format("%-15s %-25s %-25s\n", "**********", "" +
"********************", "****************************"));
int count = 0;
try
{
conn = SimpleDataSource.getConnection();
prepStat = conn.prepareStatement("SELECT * FROM student");
ResultSet print = prepStat.executeQuery();
while (print.next())
{
String id = print.getString("studentId");
String name = print.getString("studentName");
String degree = print.getString("degreeScheme");
result += String.format("%-15s %-25s %-25s\n", id, name, degree);
count++;
}
if (result == null)
{
result = "No Students Were Found In Database\n";
}
else
{
result += "\nTotal Number Of Student = " + count;
}
} catch (SQLException ex)
{
result = "Error Occurred: " + ex.getMessage();
} finally
{
conn.close();
}
return result;
}
It does what I want perfectly, but using the meta data there is a way that it does the table spacing and headers automatically. Could anyone help me change this?
From your SimpleDataSource.getConnection() you can call getMetaData() to get the DatabaseMetaData getMetaData().
and from there you can call getColumns
see javadocs
MetaData is not a report that contains spacing formats for print or display purpose.
As per ResultSetMetaData documentation:
An object that can be used to get information about the types and properties of the columns in a ResultSet object.
It is clear that it contain's no specific information on how it has to be used for display or printing.
Your way of solution combined into a result string for presentation is not good.
It would always be a good practice to encapsulate the data read from ResultSet to form a Data Object. In your case it can be a StudentDegreeScheme object.
Create a class with necessary getter and setters and fill them from reading the ResultSet and return the student... object to the caller.
Caller should then use the same object to display or print after formatting using, for example, any HTML layout.
Hope you got the point.
What is the best way to handle Java ResultSet? I'm creating a Desktop application that will connect to an oracle database using JDBC.
However, I am having problem with handling ResultSets since I can't do comparison using for loop.
// create a database connection object
DB db = new DB();
// get rows of my first table and second table
ResultSet firstRS = db.getMyFirstTable();
ResultSet seconRS = db.getSecondTable();
// compare the two tables
while(firstRS.next())
{
// get the row of my first table
String firstRSRow = firstRS.getString("col1");
while(secondRS.next())
{
// get the row of my second table
String secondRSRow = seconRS.getString("col1");
// compare row from first table to the row in second table
if(firstRSRow.startsWith(secondRSRow))
{
// do some processing here....
} // end if
} // end while
} // end while
I know that this could be accomplish with Hibernate with a few lines of code but I don't have the luxury of time to study it.
I also found commons DbUtils from apache but it seems complicated for me as a novice programmer.
Is there other way/tools/libraries/framework simple enough to get ResultSet from databases and manipulate it in simple and straightforward manner?
I will also appreciate if you could direct me to a website that has sample codes concerning java database connectivity.
Thank you very much for your support.
Why ResultSet? You could easily compare values with SELECT statement. In case you still want to ResultSet then think about CachedRowSet.
DB db = new DB();
// get rows of my first table and second table
ResultSet firstRS = db.getMyFirstTable();
ResultSet seconRS = db.getSecondTable();
// compare the two tables
while(firstRS.next() || secondRS.next())
{
// get the row of my first table
String firstRSRow = firstRS.getString("col1");
String secondRSRow = seconRS.getString("col1")
// compare row from first table to the row in second table
if(firstRSRow.startsWith(secondRSRow))
{
// do some processing here....
} // end if
} // end while
public static boolean resultset(String SQL1, String SQL2){
public boolean status=false;
ResultSet ViewResultset = st.executeQuery(SQL1);
ResultSet QueryResultset = st.executeQuery(SQL2);
while (QueryResultset.next() | ViewResultset.next())
if (ViewResultset.getRow() == 0 | QueryResultset.getRow() == 0) {
break;
} else {
for (int i = 1; i < ViewResultset.getMetaData().getColumnCount() + 1; i++) {
System.out.println("OOO"+ QueryResultset.getMetaData().getColumnCount());
String columnName = ViewResultset.getMetaData().getColumnName(i);
System.out.println("ColumnName :" + columnName);
for (int j = 1; j < QueryResultset.getMetaData().getColumnCount() + 1; j++)
if (ViewResultset.getMetaData().getColumnName(i).equals(QueryResultset.getMetaData().getColumnName(j))&& !(QueryResultset.getString(columnName) == null || ViewResultset.getString(columnName) == null))
if (ViewResultset.getString(columnName).toUpperCase().contains(QueryResultset.getString(columnName).toUpperCase())) {
System.out.println(" Actual "+ ViewResultset.getMetaData().getColumnName(i) + " Expected: "+ ViewResultset.getString(columnName));
status=true;
}
else {System.out.println(" Actual "+ ViewResultset.getMetaData().getColumnName(i) + " Expected: "+ ViewResultset.getString(columnName));
}
}
}
return status;
}
}
ResultSet is a subclass of Object ultimately and we can compare two or more objects directly. A little specific to your question:
ResultSet rs=st.executeQuery(sql1);
ResultSet rs1=st.executeQuery(sql2);
where sql1 and sql2 are 2 Statements
while(rs.next() && rs1.next()){
int a=rs.next();
int b=rs1.next();
if(a==b){
System.out.println("Compairing two ResultSets");
}
}