I'm trying to get to grips with a Java app that talks to a SQL Server 2008 R2 DB. The app imports data into the DB, and it has a 'test mode'; the DB requests are wrapped up in a transaction, which is rolled back at the end.
With a particular dataset, the tool disables a trigger, and then re-enables it after the import. In test mode, on the first pass, everything works as expected - the dataset in 'imported' without problems. However, if I try to repeat the exercise, the app hangs at the point where it tries to disable the trigger.
Looking at SQL Profiler, I can see an RPC:Completed trace item, which suggests that SQL Server has received and successfully processed the request. At which point, I would expect the Java app to pick up control and continue -except that it doesn't, I'm struggling to think where to look next.
Java code:
String sql = "ALTER TABLE MyTable DISABLE TRIGGER ALL";
PreparedStatement stmt = mDBConnection.prepareStatement (sql);
stmt.execute();
Trace TextData:
declare #p1 int
set #p1=1
exec sp_prepare #p1 output,N'',N'ALTER TABLE MyTable DISABLE TRIGGER ALL',1
select #p1
Q: Any idea what the problem might be? Or any suggestions as to how I investigate further?
UPDATE:
Of course, the trace above only only shows the sp_prepare. There is a corresponding sp_execute statement - and the lack of RPC:Completed trace item, indicates that the problem is on SQL Servers side. A modified trace shows an RPC:Starting entry ('exec sp_execute 1'), but no matching RPC:Completed.
I can run sp_prepare & sp_execute in SSMS (providing I remove the set statement), as expected - it executes OK on the first pass after all.
Solution:
Using sp_who2 (see below), I could see that there the first connection/spid was blocked the second; on commit, the db connection was closed, but on rollback it wasn't. Since I'm running in test-and-rollback mode, this was the crux of my problem - closing the connection solved the problem.
sp_who2:
CREATE TABLE #sp_who2
(
SPID INT,
Status VARCHAR(1000) NULL,
Login SYSNAME NULL,
HostName SYSNAME NULL,
BlkBy SYSNAME NULL,
DBName SYSNAME NULL,
Command VARCHAR(1000) NULL,
CPUTime INT NULL,
DiskIO INT NULL,
LastBatch VARCHAR(1000) NULL,
ProgramName VARCHAR(1000) NULL,
SPID2 INT,
RequestID int
)
GO
INSERT INTO #sp_who2 EXEC sp_who2
GO
SELECT spid, status, blkby, command, ProgramName FROM #sp_who2 WHERE DBName = 'rio7_bch_test'
GO
DROP TABLE #sp_who2
GO
This very much sounds like you have locks that aren't released properly and block your DDL execution.
When your statement hangs, run the stored procedure sp_who2.
In the result of that procedure you'll which session is blocking your DDL and then you can take the approriate actions.
Don't use a PreparedStatement for this. Use just a plain Statement.
Statement stmt = mDBConnection.createStatement(sql);
The "ALTER TABLE" statement is DDL (Data Definition Language). DDL must wait for all DML (Data Manipulation Language) statements to complete. If you have an unclosed ResultSet, Statement, or PreparedStatement that is querying the table or a view upon that table, or a join with that table, or updating with auto-commit turned off - then that is DML that is not complete.
Before altering the table like this, ensure that every possible result set open on it has been explicitly closed, and similarly any statements. That will ensure that all DML is complete and DDL can be performed.
In general it is better to use PreparedStatements over Statements. A PreparedStatement is compiled once. A Statement every time it is executed. This means there is no difference for unparameterised statements like yours, and a potential benefit for any parameterised once.
Assuming a trusted JDBC implementation, there is no time a Statement might work when a PreparedStatement does not.
You may also find this question helpful.
Related
When I run the code in non-batch mode it works:
PreparedStatement preparedStatement = connection.prepareStatement(
"DELETE FROM myTable WHERE id=58");
preparedStatement.execute();
However as soon as I try to run it in batch mode:
PreparedStatement preparedStatement = connection.prepareStatement(
"DELETE FROM myTable WHERE id=58");
preparedStatement.executeBatch();
It will no longer delete the entry from the table. All my INSERTS work perfectly well with executeBatch, in fact everything so far except the DELETE command. It doesn't come back with any kind of error, it just seems to ignore the command and skip over it. And if I inspect the number of columns affected by looking at the int[] returned it's empty (int[].length = 0).
Update: I don't believe it's a permission issue because the user account has full root privileges and access to all commands. And if it was a permission issue then it shouldn't work in non-batch mode.
The issue was that for the delete SQL statement for whatever reason I forgot to add the following line:
preparedStatement.addBatch();
Omitting this line means the PreparedStatement was never added to the batch and hence never executed. There are of course no warnings or errors because the SQL statement is never executed, it's just omitted. As there were other SQL batch PreparedStatement in the batch there was no need for an empty batch exception to be thrown (some drivers will throw an exception but this is not guaranteed so don't rely on it).
Therefore the correct code would be:
PreparedStatement preparedStatement = connection.prepareStatement(
"DELETE FROM myTable WHERE id=58");
preparedStatement.addBatch();
preparedStatement.executeBatch();
Now as pointed in a comment you would normally not want to execute a single SQL command with batching, the reason this was done was to isolate the issue to the specific SQL command.
I need to initialize a database from my Java application. For reasons of code maintainability, I would like to maintain the SQL code separately from the Java code (it is currently in a separate source file).
The first few lines of the file are as follows:
-- 1 - Countries - COUNTRIES.DAT;
drop table Countries if exists;
create table Countries(
CID integer,
ECC varchar(2),
CCD varchar(1),
NAME varchar(50));
I read the SQL code from the file and store it in a string. Then I do:
PreparedStatement stmt = dbConnection.prepareStatement(sqlString);
This fails with the following exception:
java.sql.SQLSyntaxErrorException: unexpected token: CREATE : line: 2
This looks as if JDBC doesn't like multiple SQL statements in a single PreparedStatement. I have also tried CallableStatement and prepareCall(), with the same result.
Does JDBC provide a way to pass the entire SQL script in one go?
The JDBC standard (and the SQL standard for that matter) assumes a single statement per execute. Some drivers have an option to allow execution of multiple statements in one execute, but technically that option violates the JDBC standard. There is nothing in JDBC itself that supports multi-statement script execution.
You need to separate the statements yourself (on the ;), and execute them individually, or find a third-party tool that does this for you (eg MyBatis ScriptRunner).
You might also want to look at something like flyway or liquibase.
To run a hardcoded / loaded from file queries you can use execute like:
Statement stmt = null;
String query = "drop table Countries if exists;
create table Countries(
CID integer,
ECC varchar(2),
CCD varchar(1),
NAME varchar(50));";
try {
stmt = con.createStatement();
stmt.execute(query);
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmt != null) { stmt.close(); }
}
If you want to run dynamic queries for example to append values you have to use PreparedStatement. For running a query from a file it's not recommended to put dynamic queries in it.
Short version of my question is:
PreparedStatement ps;
ps = connection.prepareStatement("Insert into T values (?)");
ps.setBoolean(1, true);
ps.executeUpdate();
What can be the reasons for this code sample to produce query with value wrapped in quotes?
Long version of my question is:
I have JavaEE application with plain JDBC for DB interactions and recently I noticed that there are some MySQLDataTruncation exceptions appearing in my logs. These exceptions were occurring on attempt to save entity into DB table which have boolean column defined as BIT(1). And it was because generated query looked like this:
Insert into T values ('1');
Note that value is wrapped with quotes. Query was logged from application with Log4J log.info(ps); statement.
Previous logs demonstrate that there where no quotes.
Furthermore, even MySQL server logs started to look different. Before this happened I had given pairs of records for each query executed:
12345 Prepare Insert into T values (?)
12345 Execute Insert into T values (1)
And after:
12345 Query Insert into T values ('1')
It is worth noting that those changes wasn`t a result of deploying new version of application or even restarting MySQL/Application server and code, responsible of query generation, is as straightforward as example in this question.
Application server restart fixed the issue for about 12 hours, and then it happened again. As a temporary solution I changed BIT columns to TINYINT
P.S. Examining both aplication and MySQL logs allowed to narrow down the time span when something went wrong to about 2 minutes, but there were nothing abnormal in the logs in this period.
P.P.S. Application server is Glassfish 2.1.1, MySQL server version is 5.5.31-1~dotdeb and MySQL Connector/J version is 5.0.3.
Well, it turned out it was actually an issue with unclosed prepared statements.
When opened statements count at MySQL server reached its allowed maximum, application was still able to continue working somehow, withoout producing sql error:
Error Code: 1461 Can’t create more than max_prepared_stmt_count statements
But in that mode it started to wrap boolean values with quotes, causing all my troubles affecting BIT(1) columns.
I work at a gaming cybercafe, and we've got a system here (smartlaunch) which keeps track of game licenses. I've written a program which interfaces with this system (actually, with it's backend MySQL database). The program is meant to be run on a client PC and (1) query the database to select an unused license from the pool available, then (2) mark this license as in use by the client PC.
The problem is, I've got a concurrency bug. The program is meant to be launched simultaneously on multiple machines, and when this happens, some machines often try and acquire the same license. I think that this is because steps (1) and (2) are not synchronised, i.e. one program determines that license #5 is available and selects it, but before it can mark #5 as in use another copy of the program on another PC tries to grab that same license.
I've tried to solve this problem by using transactions and table locking, but it doesn't seem to make any difference - Am I doing this right? Here follows the code in question:
public LicenseKey Acquire() throws SmartLaunchException, SQLException {
Connection conn = SmartLaunchDB.getConnection();
int PCID = SmartLaunchDB.getCurrentPCID();
conn.createStatement().execute("LOCK TABLE `licensekeys` WRITE");
String sql = "SELECT * FROM `licensekeys` WHERE `InUseByPC` = 0 AND LicenseSetupID = ? ORDER BY `ID` DESC LIMIT 1";
PreparedStatement statement = conn.prepareStatement(sql);
statement.setInt(1, this.id);
ResultSet results = statement.executeQuery();
if (results.next()) {
int licenseID = results.getInt("ID");
sql = "UPDATE `licensekeys` SET `InUseByPC` = ? WHERE `ID` = ?";
statement = conn.prepareStatement(sql);
statement.setInt(1, PCID);
statement.setInt(2, licenseID);
statement.executeUpdate();
statement.close();
conn.commit();
conn.createStatement().execute("UNLOCK TABLES");
return new LicenseKey(results.getInt("ID"), this, results.getString("LicenseKey"), results.getInt("LicenseKeyType"));
} else {
throw new SmartLaunchException("All licenses of type " + this.name + "are in use");
}
}
You must do two things:
Wrap your code in a transaction (to avoid autocommit releasing locks immediately)
Use SELECT ... FOR UPDATE and mysql will give you the lock you need (released on commit)
SELECT ... FOR UPDATE is better than LOCK TABLE as it can possibly get by with row-level locking, instead of automatically locking the whole table
According to the online manual, the correct syntax for locking is:
LOCK TABLES ...
and you have
LOCK TABLE ...
but you don't have any error checking. Hence you're probably failing to get the lock and it's silently ignoring that.
FWIW, I'd put your cleanup code (UNLOCK TABLES, conn.commit(), etc) in a finally block to ensure that you always clean up properly in the event of an exception.
As it is, you appear to be potentially leaking database connection handles, and never releasing the lock if there's no free license.
I would like to suggest just doing an update statement and checking how many rows where updated. i will write it out in psudo code.
int uniqueId = SmartLaunchDB.getCurrentPCID();;
int updatedRows = execute('UPDATE `licensekeys` SET `InUseByPC` = uniqueId WHERE `InUseByPC` NOT null LIMIT1')
if (updatedRows == 1)
SUCCESS
else
FAIL
If it succeeds you can then get the licence key/ID by doing a select.
As is so often the case, OP is an idiot. The code I posted was actually working, but I've just discovered a duplicate row in the database - I guess someone entered the same license twice by mistake. This led me to believe that a concurrency bug I had fixed (by introducing table locks) was still unfixed.
Thanks for the general advice, I've introduced better exception handling to this method.
currently I setting up a test environment for an application. I'm using jUnit and Spring in my test environment. Before a test execution I want to set up a database test environment state. I already has written the SQL scripts (schema and data) and they runs fine in Oracles SQLDeveloper. As I tried to execute them by using the oracle thin jdbc driver, the execution fails. It looks like that the thin driver doesn't like create trigger statements.
I read that I have to use an oci driver instead of thin driver. The problem with the oci driver is that it is not platform independent and it takes time to set it up.
Example of my code:
CREATE TABLE "USER"
(
USER_ID NUMBER(10) NOT NULL,
CREATOR_USER_FK NUMBER(10) NOT NULL,
...
PRIMARY KEY (USER_ID)
);
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1;
CREATE TRIGGER "USER_ID_SEQ_INC" BEFORE
INSERT ON "USER" FOR EACH ROW BEGIN
SELECT SEQ_USER.nextval
INTO :new.USER_ID
FROM DUAL;
END;
If I execute the the trigger statement the execution fails, but I looks like that the first part of the query (CREATE TRIGGER "USER_ID_SEQ_INC" ... "USER" ... BEGIN ... FROM DUAL;) is executed successfully, but the trigger seems to be corrupt if I try to use it. The execution fail error comes with the second part of the statement END; "ORA-00900: invalid SQL statement".
Do anyone know a solution for that problem? I just want to create a trigger with platform independent thin jdbc driver.
Cheers!
Kevin
Thank you guys for your answers, It works fine now. The reason was a syntax mistake or the interpretation of my SQL code file with Spring Framefork. When I execute the statements directly by using the execute method of jdbc it works, when I use the Spring functionality for script execution the execution fails. With oracle sql code it seems to be tricky, because if I use hsqldb sql code it works fine.
test-condext.xml:
...
<jdbc:initialize-database data-source="dataSource"
ignore-failures="DROPS" enabled="${jdbc.enableSqlScripts}">
<jdbc:script location="${jdbc.initLocation}" />
<jdbc:script location="${jdbc.dataLocation}" />
</jdbc:initialize-database>
...
schema.sql:
DROP SEQUENCE SEQ_USER;
DROP TABLE "USER" CASCADE CONSTRAINTS;
PURGE TABLE "USER";
CREATE TABLE "USER"
(
USER_ID NUMBER(10) NOT NULL,
CREATOR_USER_FK NUMBER(10) NOT NULL,
PRIMARY KEY (USER_ID)
);
ALTER TABLE "USER" ADD CONSTRAINT FK_USER_CUSER FOREIGN KEY (CREATOR_USER_FK) REFERENCES "USER" (USER_ID);
CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1;
CREATE TRIGGER "USER_ID_SEQ_INC" BEFORE
INSERT ON "USER" FOR EACH ROW
WHEN (new.USER_ID IS NULL)
BEGIN
SELECT SEQ_USER.nextval
INTO :new.USER_ID
FROM DUAL;
END;
/
ALTER TRIGGER "USER_ID_SEQ_INC" ENABLE;
This works fine! Its important to remove ; at the end of statements excepts the trigger statement!!!
#Before
public void executeSomeSql() {
Connection c;
try {
c = dataSource.getConnection();
c.createStatement()
.execute("CREATE TABLE \"USER\" (USER_ID NUMBER(10) NOT NULL, CREATOR_USER_FK NUMBER(10) NOT NULL, PRIMARY KEY (USER_ID))");
c.createStatement()
.execute("CREATE SEQUENCE SEQ_USER START WITH 1 INCREMENT BY 1");
c.createStatement()
.execute("CREATE OR REPLACE TRIGGER \"USER_ID_SEQ_INC\" BEFORE INSERT ON \"USER\" FOR EACH ROW WHEN (new.USER_ID IS NULL) BEGIN SELECT SEQ_USER.nextval INTO :new.USER_ID FROM DUAL; END;");
} catch (SQLException e) {
logger.debug(e);
}
}
Creating triggers works with any type of JDBC driver; there must be something wrong with the SQL syntax -- which is odd because Oracle should report that when you run the CREATE TRIGGER (not when you use it the first time).
Since you use BEGIN ... END; make sure that you really have a ; after END in the SQL which you send to the DB.
If that isn't the cause, check this article.
I know this is a old post but here's my answer.
By default, Spring "initialize-database" instruction split the specified script by using the semicolon character : ";".
In a trigger, there often is a semicolon inside the trigger, thus the queries are badly splitted and executed.
The solution is to use another split character ("|" for example) like this :
<jdbc:initialize-database>
<jdbc:script location="classpath:myscript.sql" separator="|"/>
</jdbc:initialize-database>