Java Print MySQL Table Using MetaData - java

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.

Related

JDBC for array (or list) values determined at run time

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)";

How can i change Only one field value in MySQL?

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 ?

Handle Dates in generic preparedStatement

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 !

How can an SQL query return data from multiple tables?

How can an SQL query return data from multiple tables?
for ex. my sql query suppose to list all students in specific classroom
but instead it shows only 1 student. Can this be done by system output?
if (forms.Validation.textNotEmpty(tfId)) {
try {
ResultSet rs = hogDB.getData("select * from student where sleepRoom = ("+tId.getText()+");");
if (rs.next()) {
tfStudentId1.setText(rs.getString("student_id"));
tfForNamne1.setText(rs.getString("fornamne"));
tfAfterNamne1.setText(rs.getString("AfterNamn"));
tfSleep1.setText(rs.getString("sleepRoom"));
}
} catch(Exception e) {
e.printStackTrace();
}
}
To find out whether you are really retrieving just one student or several, here’s a suggestion for a piece of test code.
try {
ResultSet rs = hogDB.getData("select * from student where sleepRoom = ("
+ tId.getText() + ");");
int count = 0;
while (rs.next()) {
System.out.format("%-10s%-20s%-20s%-8s%n",
rs.getString("student_id"), rs.getString("fornamne"),
rs.getString("AfterNamn"), rs.getString("sleepRoom"));
count++;
}
System.out.println("" + count + " students retrieved");
} catch(Exception e) {
e.printStackTrace();
}
Another possible issue, is tId.getText() a number? If it isn’t, you should probably enclose it in single quotes in the query. In any case, the recommended way is to pass a value to the database is through a ? placeholder and some setXx() call on the prepared statement.

JDBC - Invalid Cursor State

I am a beginner in writing programs, I have created a search record function for my assignment.
public void searchRecord(){
for(int ct = 0; ct< 1; ct++){
System.out.println("Please insert student ID");
System.out.print("Student ID: ");//prompt for student ID to search
String data = k.nextLine().trim();
String sql = "SELECT * FROM Students WHERE StudentID = '"+data+"'";
try{
rs = st.executeQuery(sql);
if(rs.next()){
displayRecord();
rs.next();
showMenu();
}
else{
System.out.print("No record found. ");
ct--;
}
}catch (Exception ex){
System.out.println("Problem searching.");
}
}
However, after I try to get data from the result, it shows Invalid Cursor State.
If I want to retrieve data, I'll have to execute the Display next record method which is:
try{
if(rs.next()){
displayRecord();
}
else{
rs.previous();
System.out.println("No more records!");
}
}catch (Exception ex){
System.out.println("There is problem showing next data.");
}
I tried adding "rs.next" at the search record method after "displayRecord" but it wouldn't solve the problem. Is there anyway to solve this?
By the way my display record method:
public void displayRecord(){//get and display data from current row of record
try{
String ID = rs.getString(1);
String fname = rs.getString(2);
String lname = rs.getString(3);
String conNo = rs.getString(4);
String mail = rs.getString(5);
String plate = rs.getString(6);
Date date = rs.getDate(7);
System.out.print("Student ID: "+ID+"\nName: "+fname+" "+lname+"\nCar Number: "+plate+"\nContact Number: 0"+conNo+"\nE-Mail Address: "+mail+"\nDate Registered: "+date);
}catch (Exception ex){
System.out.println(ex);
}
}
Any helps or advices are appreciated.
I don't quite understand what your code is supposed to do. But I suspect you don't fully understand how JDBC statements and result sets work.
If you execute a query that returns many rows, you would write something like this:
ResultSet rs = stmt.executeQuery(sqlQuery);
while (rs.next()) {
String id = rs.getString(1);
String name = rs.getString(2);
// do something with the current row
}
Note that ResultSet instance allows you to access the data from the current result row. next() both advances to the next result row and tells you if there is next row (or whether you have advanced beyond the last row). Initially, it's positioned before the first result row. So next() needs to be called before accessing the first row's data. But that's all done in the single line while (rs.next()).
In your case, I would expect that the query only returns a single result row as the StudendID is most likely a unique key. So the code would look like this:
ResultSet rs = stmt.executeQuery(sqlQuery);
if (rs.next()) {
String id = rs.getString(1);
String name = rs.getString(2);
// do something with the current row
} else {
// no student with specified ID found
}
However, you code contains two calls to next() and another one to previous(), which is confusing.
There error message Invalid Cursor State indicates that you are trying to access the current row's data when in fact the result set is position before the start or after the end of the result set.

Categories