I am creating a Java application that communicates with MySQL database. Using XAMPP 5.6.33-0 and phpMyAdmin. I have the following method that, among other values, inserts a Timestamp into the table RATING:
PreparedStatement pst = myConn.prepareStatement("INSERT INTO RATING
(ratingDate) VALUES(?)");
java.util.Date today = new java.util.Date();
Timestamp ts = new java.sql.Timestamp(today.getTime());
pst.setTimestamp(1, ts);
pst.executeUpdate();
The schema of the RATING relation looks as follows:
CREATE TABLE RATING
(cID INT,
rID INT,
stars INT,
ratingDate TIMESTAMP,
FOREIGN KEY(cID) REFERENCES CUSTOMER(cID) on delete cascade,
FOREIGN KEY(rID) REFERENCES ROOM(rID)
) ;
So attribute ratingDate is defined as Timestamp. Everything works great except when the Timestamp is inserted its value is always set to all zeros: 0000-00-00 00:00:00
I tried converting the Timestamp to string using t.toString and can clearly see that the Timestamp object is created properly. It seems the problem is with setTimestamp() method. Also, converting the data type of ratingDate to just Date and using setDate() method works fine, but setTimestamp() function always sets the attribute value to all zeros.
There are, of course, workaround for this. I could declare the date as varchar, convert Timestamp to a String and insert it using setString() but I am really wondering what the problem may be. Running Eclipse with Tomcat server. No errors in console.
Thank you in advance for any help, I'd be happy to provide any other necessary information.
Avoid legacy date-time classes
The all-zeros values is a mystery. But I can tell you that you are using terrible date-time classes that were supplanted years ago by the java.time classes with the adoption of JSR 310. This is making your work more complicated than it needs to be.
I suggest creating a simple dummy table to narrow down your problem.
OffsetDateTime odt = OffsetDateTime.now( ZoneOffset.UTC ) ;
myPreparedStatement.setObject( … , odt ) ;
Retrieval.
OffsetDateTime odt = myResultSet.getObject( … , OffsetDateTime ) ;
Example app
I do not use MySQL. But here is a complete example app using the H2 Database Engine.
package work.basil.example;
import org.h2.jdbcx.JdbcDataSource;
import java.sql.*;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class H2DateTimeExample
{
public static void main ( String[] args )
{
H2DateTimeExample app = new H2DateTimeExample ();
app.demo ();
}
private void demo ( )
{
JdbcDataSource dataSource = new JdbcDataSource ();
dataSource.setURL ( "jdbc:h2:mem:offsetdatetime_example_db;DB_CLOSE_DELAY=-1" ); // Set `DB_CLOSE_DELAY` to `-1` to keep in-memory database in existence after connection closes.
dataSource.setUser ( "scott" );
dataSource.setPassword ( "tiger" );
// Create table.
String sql = "CREATE TABLE person_ ( \n" +
" pkey_ UUID NOT NULL DEFAULT RANDOM_UUID() PRIMARY KEY , \n" +
" name_ VARCHAR NOT NULL , \n" +
"first_contacted_ TIMESTAMP WITH TIME ZONE NOT NULL " +
") ;";
// System.out.println ( sql );
try (
Connection conn = dataSource.getConnection () ;
Statement stmt = conn.createStatement () ;
)
{
stmt.execute ( sql );
} catch ( SQLException e )
{
e.printStackTrace ();
}
// Insert row.
sql = "INSERT INTO person_ ( name_ , first_contacted_ ) \n";
sql += "VALUES ( ? , ? ) \n";
sql += ";";
try (
Connection conn = dataSource.getConnection () ;
PreparedStatement pstmt = conn.prepareStatement ( sql , Statement.RETURN_GENERATED_KEYS ) ;
)
{
OffsetDateTime odt = OffsetDateTime.now ( ZoneOffset.UTC );
pstmt.setString ( 1 , "Jesse Johnson" );
pstmt.setObject ( 2 , odt );
pstmt.executeUpdate ();
ResultSet rs = pstmt.getGeneratedKeys ();
// System.out.println( "INFO - Reporting generated keys." );
// while ( rs.next() ) {
// UUID uuid = rs.getObject( 1 , UUID.class );
// System.out.println( "generated keys: " + uuid );
// }
} catch ( SQLException e )
{
e.printStackTrace ();
}
// Query table.
sql = "TABLE person_ ;";
try (
Connection conn = dataSource.getConnection () ;
PreparedStatement pstmt = conn.prepareStatement ( sql ) ;
)
{
try ( ResultSet rs = pstmt.executeQuery () ; )
{
while ( rs.next () )
{
UUID pkey = rs.getObject ( "pkey_" , UUID.class );
String name = rs.getString ( "name_" );
OffsetDateTime firstContacted = rs.getObject ( "first_contacted_" , OffsetDateTime.class );
System.out.println ( "pkey: " + pkey + " | name: " + name + " | firstContacted: " + firstContacted );
}
}
} catch ( SQLException e )
{
e.printStackTrace ();
}
System.out.println ( "Done." );
}
}
When run.
pkey: b14fd25f-1598-4f09-9475-83ac5967a338 | name: Jesse Johnson | firstContacted: 2019-07-28T02:10:07.731005Z
Done.
After some additional research I figured it out. The problem was that the java Timestamp object uses milliseconds at the end while the timestamp attribute in the MySQL table didn't (it was in the format "yyyy-MM-dd HH:mm:ss"). So this mismatch prevented the insertion of the correct timestamp and instead put a tuple with all zeros into MySQL table. The solution is to format the Timestamp object in the java code to cut off the milliseconds and then insert the Timestamp object into MySQL table:
java.util.Date today = new java.util.Date();
java.sql.Timestamp timestamp = new java.sql.Timestamp(today.getTime());
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
pst.setObject(4, formatter.format(timestamp));
This worked like a charm. Hope it helps somebody!
Related
I have trouble understanding the behaviour of a mysql transaction in a specific concurrency scenario.
It involves a table with a primary key and another column with a unique key constraint.
In the Java test case give below two DB connections are used concurrently:
The first connection uses a transaction with isolation level "repeatable read", and tries to read a row which is not existing at this point, then tries to insert it using a INSERT ... ON DUPLICATE KEY statement, and tries to read it once more.
The second connection inserts a row with the same value for the unique column AFTER the first read on connection 1 and BEFORE the insert attempt on connection 2.
The output is as follows and I can reproduce it with MySQL 5.7 and 8.0
...
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => false
[conAutoCommit ] INSERT INTO test.tmp(uniq, t) VALUES('x', NOW()) ON DUPLICATE KEY UPDATE t = VALUES(t); => 1
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => false
[conTransaction] INSERT INTO test.tmp(uniq, t) VALUES('x', NOW()) ON DUPLICATE KEY UPDATE t = VALUES(t); => 1
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => false
[conTransaction] COMMIT => 0
[conTransaction] UNEXPECTED: no row found after INSERT ON DUPLICATE KEY
I consider this behaviour unexpected:
the insert in the transaction appears to have succeeded but the row still cannot be read from that transaction.
Is this really correct behaviour for a repeatable read transaction?
More over, if the timing is changed by uncommenting the sleep in the line marked by (A)
then the INSERT ... ON DUPLICATE KEY actually changes something and the row becomes visible:
...
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => false
[conAutoCommit ] INSERT INTO test.tmp(uniq, t) VALUES('x', NOW()) ON DUPLICATE KEY UPDATE t = VALUES(t); => 1
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => false
[conTransaction] INSERT INTO test.tmp(uniq, t) VALUES('x', NOW()) ON DUPLICATE KEY UPDATE t = VALUES(t); => 2
[conTransaction] SELECT * FROM test.tmp WHERE uniq = 'x'; => true
[conTransaction] COMMIT => 0
This is the sample test case in Java.
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.junit.Test;
public class MyTests {
Connection conTA;
Connection conAutoCommit;
#Test
public void test() throws Exception {
conAutoCommit = getConnection();
conTA = getConnection();
conTA.setAutoCommit(false);
setup(conAutoCommit);
boolean hasIt = hasRow(conTA);
assertEquals(false, hasIt);
insert(conAutoCommit);
// Thread.sleep(1200); // (A)
hasRow(conTA);
insert(conTA);
boolean found = hasRow(conTA);
execNonQuery(conTA, "COMMIT");
if (!found) {
log(conTA, "UNEXPECTED: no row found after INSERT ON DUPLICATE KEY");
fail("UNEXPECTED: no row found after INSERT ON DUPLICATE KEY");
}
}
private int execNonQuery(Connection con, String sql) throws SQLException {
try(var stmt = con.createStatement()) {
int res = stmt.executeUpdate(sql);
log(con, sql + " => " + res);
return res;
}
}
private void setup(Connection con) throws SQLException {
execNonQuery(con, "DROP TABLE IF EXISTS test.tmp");
execNonQuery(con, """
CREATE TABLE test.tmp (
id int(11) NOT NULL AUTO_INCREMENT,
uniq varchar(45) DEFAULT NULL,
t datetime DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY serverDomain (uniq)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
""");
}
private void insert(Connection con) throws SQLException {
String sql = "INSERT INTO test.tmp(uniq, t) VALUES('x', NOW()) ON DUPLICATE KEY UPDATE t = VALUES(t);";
execNonQuery(con, sql);
}
private boolean hasRow(Connection con) throws SQLException {
try(var stmt = con.createStatement()) {
String sql = "SELECT * FROM test.tmp WHERE uniq = 'x';";
try(var rs = stmt.executeQuery(sql)) {
boolean res = rs.next();
log(con, sql + " => " + res);
return res;
}
}
}
private void log(Connection con, String msg) {
String conName = con == conTA ? "conTransaction" : "conAutoCommit ";
System.out.println("[" + conName + "] " + msg);
}
private Connection getConnection() throws SQLException {
var con = DriverManager.getConnection("jdbc:mysql://localhost:3306?useSSL=false","root","");
con.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
return con;
}
}
I am writing a simple database with a query that inserts some data, modifies a entry, deletes it, then prints out the rest.
import java.sql.*;
public class SpotifyDB {
//JDBC driver name and database URL
static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
static final String DB_URL = "jdbc:mysql://localhost/spotify";
static final int portNumber = 3306;
static final String serverName = "localhost";
static final String dbName = "spotify";
//Database credentials
static final String USER = "root";
static final String PASS = "root";
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
try{
//Register JDBC driver
Class.forName("com.mysql.jdbc.Driver");
//Open a connection to the database
System.out.println("Connecting to a selected database...");
conn = DriverManager.getConnection(DB_URL, USER, PASS);
System.out.println("Connected database successfully...");
//Insert data
System.out.println("Inserting records into the table...");
stmt = conn.createStatement();
String sql = "INSERT INTO artist(artist) " +
"values('Muse')";
stmt.executeUpdate(sql);
sql = "INSERT INTO album(album, artist, genre, year)" +
"values('Drones', 'Muse', 'Rock', 2015)";
stmt.executeUpdate(sql);
sql = "INSERT INTO album(album, artist, genre, year)" +
"values('The 2nd Law', 'Muse', 'Rock', 2012)";
stmt.executeUpdate(sql);
sql = ("INSERT INTO songs(song, artist, album, tracknumber, duration)" +
"values('Madness', 'Muse', 'The 2nd Law', 2, '4:41')");
stmt.executeUpdate(sql);
sql = ("INSERT INTO songs(song, artist, album, tracknumber, duration)" +
"values('Mercy', 'Muse', 'Drones', 4, '3:52')");
stmt.executeUpdate(sql);
System.out.println("Records inserted into the table!");
//Update data
String sql1 = "UPDATE songs " +
"SET track number = 1 WHERE song in ('Madness')";
stmt.executeUpdate(sql1);
//Delete data
String sql2 = "DELETE FROM songs " +
"WHERE song = Madness";
stmt.executeUpdate(sql2);
//View records
String sql3 = "SELECT * FROM songs";
ResultSet rs = stmt.executeQuery(sql3);
while(rs.next()) {
//Retrieve by column name
String song = rs.getString("song");
String artist = rs.getString("artist");
String album = rs.getString("album");
String track = rs.getString("track number");
String duration = rs.getString("duration");
//Display the values
System.out.print("Song: " + song);
System.out.print(", Artist: " + artist);
System.out.print(", Album: " + album);
System.out.println(", Track: " + track);
System.out.println(", Duration: " + duration);
}
//Close the connection, clean up running functions
rs.close();
stmt.close();
conn.close();
}
catch(SQLException se) {
//Handle errors for JDBC driver
se.printStackTrace();
}
catch(Exception e) {
//Handle errors for Class.forName
e.printStackTrace();
}
finally {
//finally used to close resources
try{
if(stmt!=null)
stmt.close();
}
catch(SQLException se2) {
}
try{
if(conn!=null)
conn.close();
}
catch(SQLException se) {
se.printStackTrace();
}
}
System.out.println("Goodbye!");
}
}
My SQL Database table is quite simple as well;
CREATE TABLE spotify.`songs` (
`song` varchar(20) NOT NULL,
`artist` varchar(20) NOT NULL,
`album` varchar(20) NOT NULL,
`track number` int(3) NOT NULL,
`duration` varchar(10) NOT NULL,
PRIMARY KEY (`song`),
KEY `songalbum_idx` (`album`),
KEY `songartist` (`artist`),
CONSTRAINT `songalbum` FOREIGN KEY (`album`) REFERENCES `album` (`album`)
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT `songartist` FOREIGN KEY (`artist`) REFERENCES `artist` (`artist`)
ON DELETE CASCADE
ON UPDATE CASCADE);
and the console is returning me this error: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException
I am having trouble seeing where the table columns are not matching up, any help would be appreciated. Thank you!
Column names really shouldn't have spaces, for exactly this reason. But if your column names must have spaces then you need to qualify them with back-ticks exactly as you do in your CREATE TABLE statement:
INSERT INTO songs (song, artist, album, `track number`, duration) VALUES ...
Otherwise after the identifier track the query engine is expecting either a comma (to move on to another identifier) or a close parentheses (to end the list of column identifiers). It finds neither of this, and immediately finds another identifier (number, which may even be a reserved word?). This confuses the query parser.
I have a jersey java server and a mysql server:
I send a POST with an ArrayList<Long> to the server. Then I want to do a select like ...where long OR long OR long....
Is there an elegant way to solve this problem and not to do always a single select in a for loop?
How can I form a sql-statement with dynamic count of where clauses?
Thank you very mutch.
Instead of OR multiple times, you can use IN with the where clause in the SQL query.
You can read ArrayList object in a loop and set the where clause values.
JAVA code snippet:
int paramCount = list.size();
StringBuilder sqlSelect = new StringBuilder( 1024 );
sqlSelect.append( "select x,y,z from my_table " );
if( paramCount > 0 ) {
sqlSelect.append( "where long_column in ( " );
for( i = 0; i < paramCount; i++ ) {
sqlSelect.append( ( i > 0 ? ", ?" : "?" );
} // for each param
sqlSelect.append( " )" );
} // if list is not empty
// make the prepare statement (pst) with the above sql string
// ...
// now set the parameter values in the query
int paramIndex = 1;
if( paramCount > 0 ) {
for( i = 0; i < paramCount; i++ ) {
pst.setLong( paramIndex++, ( (Long)list.get( i ) ).longValue() );
} // for each param
} // if list is not empty
I am trying to search my database using mySQL through JDBC. When I use this statement:
SELECT * FROM tablename WHERE name='joe';
It does a case insensitive search and returns any rows that have 'Joe' or 'joe', which is what I want. I ran select collation(version()), and this returned 'utf8_general_ci', which is apparently supposed to be case insensitive. However, when I run the same query using JDBC within my Java applet, it does a case sensitive search. Here is my query2 function:
try {
Vector<Vector<String>> out = new Vector<Vector<String>>() ;
Connection con = connect() ;
Statement statement = con.createStatement() ;
System.out.println( "SQL: " + s ) ;
ResultSet rs = statement.executeQuery(s) ;
while( rs.next() )
{
String r = rs.getString(1) ; // RS is indexed from 1
Vector<String> q = new Vector<String>() ;
for( int i = 2 ; i <= tableSize ; i ++ )
{
q.add(r) ;
r = rs.getString(i) ;
}
q.add(r) ;
out.add(q) ;
}
con.close() ;
return( out ) ;
} catch (SQLException e) {
System.err.println( "SQL EXCEPTION" ) ;
System.err.println( s ) ;
e.printStackTrace();
}
And here is my select function that calls query2:
public static Vector<Vector<String>> select( String table , List<String> columns , List<String> values )
{
String statement = "SELECT * from `" + table + "` WHERE " ;
for( int i = 0 ; i < columns.size(); i ++ )
{
statement += columns.get(i) + "='" + values.get(i) + "'" ;
if( i + 1 < columns.size() )
statement += " AND " ;
}
statement += " ;" ;
return query2 ( statement , tableSize(table) ) ;
}
Any idea how I can make this query case insensitive?
It turns out that the search I thought was using the select function was actually using a different function. This function was selecting everything from the database and sorting through it client-side. I tested the select function and it actually is case insensitive, without having to use any of the tips given (but thanks for the advice!).
Wouldn't this line need to read
statement += "UPPER("+columns.get(i) + ")='UPPER(" + values.get(i) + ")'"
downfall... can't use indexes since it's uppering everything.
I have an error updating my database because of variables. This is my code:
UPDATE `payment` SET `paid`=1 AND `amoun`=$amountpaid WHERE `paid`=0 AND `userid`=$uid
$amountpaid is the amount of the bill that the user paid and $uid is user id. It seems like using $ in front of variable names is forbidden. How can I use variables in SQL?
Where are your variables coming from? You probably want something like this if you're using JDBC:
int setPaid = 1;
int amountPaid = x; // put $amountpaid here
int wherePaid = 0;
int userId = y; // put $uid here
String updateQuery = "UPDATE payment SET paid = ?, amoun = ?"
+ " WHERE paid = ? AND userid = ?";
PreparedStatement ps = con.prepareStatement(updateQuery);
ps.setInt(1, setPaid);
ps.setInt(2, amountPaid);
ps.setInt(3, wherePaid);
ps.setInt(4, userId);
ps.executeUpdate();
I got the solution by using String.
I converted the ArrayList to a String and then sent the data as string. The data got updated but I don't know what will happen next if I want to view the data in the client tier...