I use JDBC Java MySQL driver, but I found it is some how slow in cases when I run tests where I do drop table populate data and read it from straight after. data appears not there, But is I do delay for some second - data is there. I am afraid that not only Tests works this way and production code also may not read last data written.
So I need read just after written. without delay.
Would some caching help? Googled for and tried manuals still could not find answer.
here is some code i do use for inserting and retrieving:
private Connection conn = null;
private Statement stmt0;
private Statement stmt1;
#Inject
public DBConnectorMysql(#Named("JDBC_URL") String url, #Named("JDBC_CRED") String creds) throws SQLException {
conn = DriverManager.getConnection(url + creds);
stmt0 = conn.createStatement();
stmt1 = conn.createStatement();
}
#Override
public ResultSet executeQuery(String query) throws SQLException {
return stmt0.executeQuery(query);
}
#Override
public int executeUpdate(String query, String... values) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS);
int i = 0;
for (String s: values){
stmt.setString( ++i, s );
}
stmt.executeUpdate();
int newId = -1;
ResultSet rs = stmt.getGeneratedKeys();
if (rs.next()) {
newId = rs.getInt(1);
}
rs.close();
stmt.close();
return newId;
}
I do use Java SE with Mysql driver
I actively use three tables without foreign key.
From your code, it seems that you use multiple statement / prepared statement objects from the same connection that are open at the same time. This may have unexpected results (the JDBC spec doesn't really specify how this is handled, it's down to each vendor's implementation and I vaguelly recall a similar behaviour with an older version of MySQL/JDBC connector).
You should make sure that only one of them is open at any time. You could replace all your statements with a simple one, but as I see that's not practical since you want both Statement in executeQuery and a PreparedStatement in executeUpdate.
Otherwise you can move the createStatement calls inside the methods that use them. The best practice for executeQuery would be to return an ArrayList with your results (and close the ResultSet and stmt0 before returning).
Another, less elegant solution would be to write a closeQuery method that closes stmt0 after you're done with the ResultSet from executeQuery.
You have to call conn.commit() before rs.close() in order for your changes to take effect.
Related
I'm getting the SQLNonTransientException error when trying to update one of my rows in a H2 database.
public static void setNewServiceInformationsToShown() {
try (Connection conn = DriverManager.getConnection("jdbc:h2:" + Main.config_db_location,
Main.config_db_username, Main.config_db_password)) {
//read data from database
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM BCSTASKS_SERVICE");
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
if(rs.getString("Status").equals("Neu") && rs.getBoolean("wasShown") == false) {
rs.updateBoolean("WASSHOWN", true);
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
The error message already suggests that I should use conn.createStatement and set the ResultSet to CONCUR_UPDATABLE. The error occurs at the line with rs.updateBoolean(...);
Error Message:
The result set is readonly. You may need to use conn.createStatement(.., ResultSet.CONCUR_UPDATABLE). [90140-210]
The problem is I don't know where and how I should use this method. In the same function or at the start of the program?
Most DB code I see doesn't attempt to use the fact that resultsets are updatable, and will instead fire off an additional UPDATE query, which works fine.
However, sure, H2 supports updateable resultsets too. However, some of the features that ResultSets have actually have quite a cost; the DB engine needs to do a boatload of additional bookkeeping to enable such features which have a performance cost. Lots of database queries are extremely performance sensitive, so by default you do not get the bookkeeping and therefore these features do not work. You need to enable them explicitly, that's what the error is telling you.
You're currently calling the 'wrong' preparedStatement method. You want the more extended one, where you pick and choose which additional bookkeeping you want H2 to do for you, in order to enable these things. You want this one.
conn.prepareStatement(
"SELECT * FROM BCSTASKS_SERVICE",
ResultSet.TYPE_SCROLL_INSENSITIVE, // [edited]
ResultSet.CONCUR_UPDATABLE);
That CONCUR_UPDATABLE thing is just a flag you pass to say: Please do the bookkeeping so that I can call .update.
[edited] This used to read 0 before, but as #MarkRotteveel pointed out, that's not valid according to the documentation.
You have to put update query for update data in database but you are going with select query that is the problem.
Select query is used if you have to fetch data from database.
Update query is used for update data in database where data already stored in database but you just overwrite data.
Here down is modified code:
public static void setNewServiceInformationsToShown() {
try (Connection conn = DriverManager.getConnection("jdbc:h2:" + Main.config_db_location,
Main.config_db_username, Main.config_db_password)) {
PreparedStatement stmt = conn.prepareStatement("UPDATE BCSTASKS_SERVICE SET wasShown = ? WHERE status = ? AND wasShown = ?");
stmt.setBoolean(1, true);
stmt.setString(2, "Neu");
stmt.setBoolean(3, false);
stmt.executeUpdate();
stmt.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
You need to create a separate query/prepareStatement for an update. In your case as far as I can see you need only one update query:
conn.prepareStatement("UPDATE BCSTASKS_SERVICE SET WASSHOWN=true where
Status = 'Neu' and wasShown = false "
I am using temporary tables inside my code in order to [some long sequnce of reasons here] in SQL Server, Java. I was executing my sql queries with using Stament object in java. However, recently I decided to use PreparedStatement in order to avoid injection thing.
My problem is when create a temporary table with using PreparedStatement, I can not reach it with the same prepared statement again. Here is a simple illustration:
sql = "select * into #someTable from (select someColumns from someOtherTable where smth = ? and smth2 = ?)"
PreparedStatement preparedStatement = conn.prepareStatement(sql);
for(int i=0; i<parameters.size(); i++){
preparedStatement.setString(i+1, parameters.get(i).toString());
}
this.rs = preparedStatement.executeQuery();
Until here, it is ok. After getting ResultSet and doing something with it, or without getting a resultSet just for preparedStatement.execute() does not makes difference, I can not reach the #someTable object again.
sql = "select count(*) from #someTable"
preparedStatement = conn.prepareStatement(sql);
this.rs = preparedStatement.executeQuery();
Here this.rs = preparedStatement.executeQuery(); part gives 'Invalid object name #someTable'. I am doing all of the things above with using one Connection object only and without closing or reopening it. I need to use that temp table again. Is there any way to create temp table with PreparedStatement object in java and reuse this temp table again and again? Regards,
Rather late to the party, but facing the same problem and finding the above answer wrong:
Read this article about the problem: https://learn.microsoft.com/en-us/sql/connect/jdbc/using-usefmtonly?view=sql-server-2017
I found that using a PreparedStatement to create the temp table wouldn't work, but if I changed to use a Statement to create the temp table it would work (even without the useFmtOnly).
So start with this (from the MS article) and build on it:
final String sql = "INSERT INTO #Bar VALUES (?)";
try (Connection c = DriverManager.getConnection(URL, USERNAME, PASSWORD)) {
try (Statement s = c.createStatement()) {
s.execute("CREATE TABLE #Bar(c1 int)");
}
try (PreparedStatement p1 = c.prepareStatement(sql); PreparedStatement p2 = c.prepareStatement(sql)) {
((SQLServerPreparedStatement) p1).setUseFmtOnly(true);
ParameterMetaData pmd1 = p1.getParameterMetaData();
System.out.println(pmd1.getParameterTypeName(1)); // prints int
ParameterMetaData pmd2 = p2.getParameterMetaData(); // throws exception, Invalid object name '#Bar'
}
}
The temp table you create in the first statement exists for the scope\lifetime of that request. As soon as you call another query, you're in a different scope so it is no longer present as it would have been cleaned up.
Solutions are either make 2 requests in the same call (not great) or create a global temp table that can be accessed by the second query (still not great).
The better solution is to create a stored procedure that does everything you need, with the temp table creation, querying and tidy up encapsulated in the procedure.
PS I can't see any surrounding code, but beware of SQL Injection when building queries in code like this.
Related info:
Scope of temporary tables in SQL Server
This piece of code uses an SQL query to return how many entries there are in a certain table.
public int countAmountOfEntries() {
int amount;
try (Connection conn = DriverManager.getConnection(Connection.JDBC_URL);
PreparedStatement query = conn.prepareStatement("SELECT COUNT(*) FROM Table")) {
try (ResultSet rs = query.executeQuery();) {
if (rs.next()) {
amount = rs.getInt("COUNT(*)");
}
}
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
return amount;
}
This should return any int other than 0. Initialising the variable to 0 will result in a NullPointerException being thrown as I'm using the return value of this to set the length of an array. Using the same code in another class returns the int it should return. I've tried using an alias for the COUNT(*) but to no avail.
Running the query directly into MySQL returns the int as well. I've tried removing the nested try (it was pretty much obsolete since I know it won't throw an exception if no one messes with my DB).
Did you register the JDBC driver before using it?
Class.forName("com.mysql.jdbc.Driver");
Is it required to provide an username/password upon connecting?
DriverManager.getConnection(url, user, pass);
Did you create a Connection class yourself which overwrites the Connection class returned upon opening the connection. The reason I ask this is because you retrieve the URL to connect to using Connection.JDBC_URL which is (as far as I know) not in the Connection class.
Is there already a connection opened and your database only allows 1 open connection?
Note: do not forget to close the resultset, statement, and connection before returning:
rs.close();
query.close();
conn.close();
Besides that, restructure your function because a try without catch does not help at all.
This looks really weird:
amount = rs.getInt("COUNT(*)");
Try this instead
amount = rs.getInt(1);
I am new to JDBC and was a bit confused about the syntax. Say I wanted to create multiples queries ( or views in SQL) and refer to a view to make the subsequent queries, how do I do that with prepared statements?
For example:
PreparedStatement first = conn.PreparedStatement("SELECT * FROM clients);
first.execute()
Question Now can I pass the 'PreparedStatement' reference 'first' in the query I am about to create below?
PreparedStatement second = conn.PreparedStatement("SELECT * FROM first WHERE age > 10);
The easiest pattern for JDBC is:
try (PreparedStatement first = conn.prepareStatement(
"SELECT * FROM clients WHERE age BETWEEN ? AND ?")) {
first.setInt(1, 80);
first.setInt(2, 140);
try (ResultSet rs = first.executeQuery()) {
while (rs.next()) {
int age = rs.getInt("age");
...
}
}
}
This syntax: try-with-resources closes first and rs, even if you return or an exception is thrown.
And the statements: first and second are entirely independent.
One does not need to keep the prepared statements open for the entire database session.
The execution is best triggered by executeQuery for SELECT and executeUpdate for UPDATE/INSERT where no ResultSet is yielded.
This question already has answers here:
mysql prepared statement error: MySQLSyntaxErrorException
(2 answers)
Closed 6 years ago.
I've a course table with the columns,
id, teacher_id and name.
This is the method that I'm using to get a course by id.
public static Course getById(int id) throws SQLException {
String query = "SELECT * FROM courses WHERE id = ?" ;
Course course = new Course();
Connection connection = null;
PreparedStatement statement = null;
ResultSet resultSet = null;
try{
DriverManager.registerDriver(new com.mysql.jdbc.Driver ());
connection = (Connection) DriverManager.getConnection(ConnectDb.CONN_STRING, ConnectDb.USERNAME, ConnectDb.PASSWORD);
statement = (PreparedStatement) connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_READ_ONLY);
statement.setInt(1, id);
resultSet = statement.executeQuery(query);
while (resultSet.next()) {
course.setId(resultSet.getInt("id"));
course.setName(resultSet.getString("name"));
course.setTeacherId(resultSet.getInt("teacher_id"));
}
}catch (SQLException e) {
System.err.println(e);
}finally{
if (resultSet != null) resultSet.close();;
if (statement != null) statement.close();
if(connection != null) connection.close();
}
return course;
}// end of method
When I run this method, I get an output id :0, teacher_id : 0
The server log says that I've an SQLException
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '?' at line 1
The bug is here:
resultSet = statement.executeQuery(query);
You're not calling PreparedStatement#executeQuery, you're calling Statement#executeQuery (Statement is a superinterface of PreparedStatement). So the parameter substitution isn't happening and you're actually sending that ? to the server.
Change it to:
resultSet = statement.executeQuery();
// No argument here ---------------^
(And yes, this is an API design flaw; and no, you're not the first to fall into it.)
There are a few other things about that code that could use improvement:
You're always returning a Course, even if an exception occurred. Best practices would be to allow the exception to propagate to the caller; second-best practices would be to return some kind of flag to the caller that an error occurred, such as null.
The try-with-resources statement can make that code both shorter and clearer
You shouldn't have to cast the return values of getConnection or prepareStatement.
You're using while, but you're expecting only a single result. if would make more sense.
On that topic, you can give the driver a hint in that regard by using setMaxRows.
Your method declares that it can throw SQLException, which is literally true since it calls close, but the only useful SQLException is actually being caught, logged, and suppressed by the code, making declaring it on the method a bit misleading.
I'm told modern JDBC drivers don't need the registerDriver call anymore. (I personally haven't used JDBC for a while now, so...)
Here's an example incoporating the above. It allows an exception to propagate, so errors (exceptional conditions) are not handled in the normal flow of code; it returns null if there's no matching course:
public static Course getById(int id) throws SQLException {
String query = "SELECT * FROM courses WHERE id = ?";
try (
Connection connection = DriverManager.getConnection(ConnectDb.CONN_STRING, ConnectDb.USERNAME, ConnectDb.PASSWORD);
PreparedStatement statement = connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
) {
statement.setInt(1, id);
statement.setMaxRows(1);
try (
ResultSet resultSet = statement.executeQuery();
) {
if (resultSet.next()) {
Course course = new Course();
course.setId(resultSet.getInt("id"));
course.setName(resultSet.getString("name"));
course.setTeacherId(resultSet.getInt("teacher_id"));
return course;
}
// No matching course
return null;
}
}
} // end of method
That can probably be improved further, but you get the idea.