I have a java program that is calling a MySQL stored procedure that is rolled back when it gets an SQLEXCEPTION. When I added the rollback (exit handler) to the stored procedure the Java program stopped getting the SQL exception.
How can I make sure the SQL exception and MySQL error message are propagated back to the Java program?
Here is my store procedure:
DELIMITER $$
DROP PROCEDURE IF EXISTS up_OMS_insertParticipantOmsOrderOwner $$
CREATE PROCEDURE up_OMS_insertParticipantOmsOrderOwner(
IN PID int,
IN OwnerName varchar(50),
IN DisplayName varchar(50),
IN Enabled tinyint(1))
BEGIN
declare exit handler for SQLException
BEGIN
rollback;
END;
start transaction;
if (DisplayName<>'') then
insert OmsOrderOwner (ParticipantID, OmsOrderOwnerName, DisplayName, Enabled)
value (PID, OwnerName,DisplayName, Enabled);
else
insert OmsOrderOwner(ParticipantID, OmsOrderOwnerName, DisplayName, Enabled)
value (PID, OwnerName,null, Enabled);
end if;
set #OwnerID := ##identity;
insert UserOmsOrderOwnerSubscription (UserID, ParticipantID, OmsOrderOwnerID, Enabled)
select
userOrderSub.UserId, PID, #OwnerID, 1
from
Users u,
UserOmsOrderSubscription userOrderSub
where
userOrderSub.UserID = u.UserID and
u.ParticipantID = PID;
commit;
END $$
DELIMITER ;
Use RESIGNAL statement in your exit handler to rethrow the error.
That said, do you REALLY need to explicitly begin / commit / rollback transaction within your stored procedure? JDBC call will be (should be) done within its own transaction anyway, can you instead rely on it to handle the error / rollback and save yourself some trouble, perhaps?
Since you handled the error in STP, it's not an exception anymore. It should be just a normal return status of your call. You should return something from the exit handler, like
declare exit handler for SQLException
BEGIN
rollback;
select 1;
END;
start transaction;
1 or whatever will be error code for rollback.
If you still think this is an exception, you can use resignal in MySQL 6.0. In earlier version , you can just trigger an error by calling a non-existant function like this,
call ROLLED_BACK_EXCEPTION();
Related
I use Hibernate and Postgres.
I'm working on completely new project so it's convenient for me to delegate database creation to Hibernate and just verifying it's structure if needed.
During my development I configured hibernate to always delete and create tables when the app starts. I use import.sql to load initial data. I'd like to create a function in postgres.:
CREATE OR REPLACE FUNCTION someFunction() RETURNS boolean AS $$
BEGIN
RETURN (SELECT count(*) > 0 FROM user);
END; $$
LANGUAGE PLPGSQL;
I'd like this to be imported by Hibernate on startup.
However hibernate is not able to handle it due to this: $$
Caused by: org.postgresql.util.PSQLException: Unterminated dollar quote started at position 61 in SQL CREATE OR REPLACE FUNCTION someFunction() RETURNS boolean AS $$
BEGIN
RETURN (SELECT count(*) > 0 FROM user). Expected terminating $$
How can I make it work?
I have a custom ExecuteListener that executes additional statements before the statement JOOQ is currently looking at:
#Override
public void executeStart(ExecuteContext ctx) {
if (ctx.type() != READ) {
Timestamp nowTimestamp = Timestamp.from(Instant.now());
UUID user = auditFields.currentUserId(); // NOT the Postgres user!
Connection conn = ctx.connection();
try (Statement auditUserStmt = conn.createStatement();
Statement auditTimestampStmt = conn.createStatement()) {
// hand down context variables to Postgres via SET LOCAL:
auditUserStmt.execute(format("SET LOCAL audit.AUDIT_USER = '%s'", user.toString()));
auditTimestampStmt.execute(format("SET LOCAL audit.AUDIT_TIMESTAMP = '%s'", nowTimestamp.toString()));
}
}
}
The goal is to provide some DB-Triggers for auditing with context information. The trigger code is given below [1] to give you an idea. Note the try-with-resources that closes the two additional Statements after execution.
This code works fine in the application server, where we use JOOQ's DefaultConnectionProvider and ordinary JOOQ queries (using the DSL), no raw text queries.
However, in the migration code, which uses a DataSourceConnectionProvider, the connection is already closed when JOOQ attempts to execute its INSERT/UPDATE query.
The INSERT that triggers the exception looks like this:
String sql = String.format("INSERT INTO migration.migration_journal (id, type, state) values ('%s', 'IDD', 'SUCCESS')", UUID.randomUUID());
dslContext.execute(sql);
and this is the exception raised:
Exception in thread "main" com.my.project.data.exception.RepositoryException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed.
at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:51)
at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.addIDDJournalSuccessEntry(JooqMigrationJournalRepositoryUtil.java:10)
at com.my.project.demo.data.demodata.DemoDbInitializer.execute(DemoDbInitializer.java:46)
at com.my.project.shared.data.dbinit.AbstractDbInitializer.execute(AbstractDbInitializer.java:41)
at com.my.project.demo.data.demodata.DemoDbInitializer.main(DemoDbInitializer.java:51)
Caused by: org.jooq.exception.DataAccessException: SQL [INSERT INTO migration.migration_journal (id, type, state) values ('09eea5ed-6a68-44bb-9888-195e22ade90d', 'IDD', 'SUCCESS')]; This statement has been closed.
at org.jooq.impl.Tools.translate(Tools.java:1690)
at org.jooq.impl.DefaultExecuteContext.sqlException(DefaultExecuteContext.java:660)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:354)
at org.jooq.impl.DefaultDSLContext.execute(DefaultDSLContext.java:736)
at com.my.project.demo.data.migration.JooqMigrationJournalRepositoryUtil.lambda$addIDDJournalSuccessEntry$0(JooqMigrationJournalRepositoryUtil.java:12)
at com.my.project.shared.data.JOOQAbstractRepository.executeWithoutResult(JOOQAbstractRepository.java:49)
... 4 more
Caused by: org.postgresql.util.PSQLException: This statement has been closed.
at org.postgresql.jdbc.PgStatement.checkClosed(PgStatement.java:647)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:163)
at org.postgresql.jdbc.PgPreparedStatement.execute(PgPreparedStatement.java:158)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.execute(ProxyPreparedStatement.java:44)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.execute(HikariProxyPreparedStatement.java)
at org.jooq.tools.jdbc.DefaultPreparedStatement.execute(DefaultPreparedStatement.java:194)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:408)
at org.jooq.impl.AbstractQuery.execute(AbstractQuery.java:340)
... 7 more
I traced this back to DataSourceConnectionProvider.release() and therefore connection.close() being called via auditUserStmt.close(). Note that it is critical that the SET commands are executed on the same Connection. I would be fine with obtaining a Statement from JOOQ's connection that I have to close myself, but I can't find a JOOQ method to obtain such an "unmanaged" statement.
We're using the Hikari connection pool, so the connection acquired by JOOQ is a HikariProxyConnection. From within the migration code, the DataSource is configured only minimally:
HikariDataSource dataSource = new HikariDataSource();
dataSource.setPoolName(poolName);
dataSource.setJdbcUrl(serverUrl);
dataSource.setUsername(user);
dataSource.setPassword(password);
dataSource.setMaximumPoolSize(10);
How can I fix my ExecuteListener?
I am using JOOQ 3.7.3 and Postgres 9.5., with the Postgres JDBC Driver 42.1.1.
[1]: Postgres Trigger Code:
CREATE OR REPLACE FUNCTION set_audit_fields()
RETURNS TRIGGER AS $$
DECLARE
audit_user UUID;
BEGIN
-- Postgres 9.6 will offer current_setting(..., [missing_ok]) which makes the exception handling obsolete.
BEGIN
audit_user := current_setting('audit.AUDIT_USER');
EXCEPTION WHEN OTHERS THEN
audit_user := NULL;
END;
IF TG_OP = 'INSERT'
THEN
NEW.inserted_by := audit_user;
ELSE
NEW.updated_by := audit_user;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
As per #LukasEder's suggestion, I ended up solving this problem with a wrapper around the JDBC Connection instead of with an ExecuteListener.
The main complication in this approach is that JDBC does not provide anything to track the transaction status, and therefore the connection wrapper needs to re-set the context information every time the transaction was committed or rollbacked.
I documented my full solution in this gist, since it's too long to fit in an SO answer.
I have a #NamedStoredProcedureQueryit's looks like this way:
#NamedStoredProcedureQueries({
#NamedStoredProcedureQuery(name = "addLongDesc", resultClasses=Integer.class, procedureName = "myProc", parameters = {
#StoredProcedureParameter(name = "b_cus_id", type = String.class),
#StoredProcedureParameter(name = "b_case_id", type = String.class),
#StoredProcedureParameter(name = "b_user_id", type = String.class),
#StoredProcedureParameter(name = "v_customerno", type = String.class),
#StoredProcedureParameter(name = "v_tickler_id", type = String.class),
#StoredProcedureParameter(name = "v_calleruser", type = String.class),
#StoredProcedureParameter(name = "v_megalltext", type = String.class)
})
})
I'm trying to call the #NamedStoredProcedureQuery this way:
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public boolean changeLongDesc(KallerMainFormParam formObj, String longDesc) {
Integer result = 0;
try {
result = (Integer) em.createNamedQuery("addLongDesc")
.setParameter("b_cus_id", formObj.getKallerCusInstance().getId().toString())
.setParameter("b_case_id", formObj.getKallerCaseInstance().getId().toString())
.setParameter("b_user_id", formObj.getKallerUsr().getId().toString())
.setParameter("v_customerno", formObj.getKallerCusInstance().getCustomerno())
.setParameter("v_tickler_id", formObj.getKallerCaseInstance().getBKallerCaseData().getTicklerId().toString())
.setParameter("v_calleruser", formObj.getKallerUsr().getJazzUser())
.setParameter("v_megalltext", "B - " + longDesc).getSingleResult();
}
catch (Exception ex) {
LOG.warning(ex.getMessage());
}
return result != 0;
}
The procedure in the oracle database looks like this way:
CREATE OR REPLACE procedure myProc (b_cus_id number , b_case_id number, b_user_id number, v_customerno varchar2, v_tickler_id number, v_calleruser varchar2, v_megalltext varchar2)
as
result number;
begin
result := remoteDbProc#DBLINK(v_tickler_id , v_megalltext, v_calleruser);
insert into b_Tickler_Access_Log VALUES (B_CASE_TICKLER_SEQ.nextval, v_customerno, b_cus_id, b_case_id,b_user_id,v_tickler_id, 'LONG_DESC', v_jazz_result, sysdate);
end;
Everything works fine, if there is little numbers of request. But if the users starts to work with the application and the request's numbers getting more and more, i got Internal database exception bacause of ORA-02020 – Too many database links in use error.
I tried to add this line to the end of my stored procedure: EXECUTE IMMEDIATE 'ALTER SESSION CLOSE DATABASE LINK DBLINK';
But it wasn't help.
Unfortunately I can't set the INIT.ORA open_links maximum parameter, and i can't ask the DBA neither.
Can anybody please tell me any advice how to solve this problem without DBA settings?
Thank you very much!
You don't say why the EXECUTE IMMEDIATE 'ALTER SESSION CLOSE DATABASE LINK DBLINK'; doesn't help, so I can only speculate.
You can't close the DB LINK if the session is not commited.
Here some hints for troubleshooting
The open links for your session are visible in
select db_link, in_transaction from V$DBLINK;
Please note also, that while using DB LINK you open a transaction even with a SELECT (you don't need update).
DEMO NO
DEMO2 YES
You may need priviledge
grant select on V_$DBLINK to your_user;
to access this view (I know this needs DBA, but simple risk the request:)
So in the example above you may close the db link DEMO, but not the DEMO2.
Trying to do so, you get ORA-02080: "database link is in use"and the link remains open.
UPDATE
As mentioned above a DB LINK session can't be closed while the transaction is open, so the simplest possible way to close the DB LINK after the call of the remote procedure (actually is is a function) is a explicit commit.
This could be somehow as follows (I use simplified version of you procedure)
CREATE OR REPLACE procedure myProc (id number )
as
result number;
begin
result := remoteDbFun#DEMO(1);
-- add this to close transaction and close DB link
commit;
EXECUTE IMMEDIATE 'ALTER SESSION CLOSE DATABASE LINK DEMO';
--
insert into Access_Log VALUES (result, sysdate);
end;
/
A big drawback of this approach is that you ends the transaction - which will probably conflict with your transaction control. So you may use autonomous transaction to avoid it.
This is done with a sub-function defined with autonomous transaction that calls the remote function, makes the COMMIT (but autonomous i.e. not visible to the main session) and close the DB LINK.
CREATE OR REPLACE function myFun (id number ) return NUMBER as
PRAGMA AUTONOMOUS_TRANSACTION;
result number;
begin
result := remoteDbFun#DEMO(1);
commit;
EXECUTE IMMEDIATE 'ALTER SESSION CLOSE DATABASE LINK demo';
return result;
end;
/
Your PROCEDURE myProc calls instead of the remote function this new function.
It is guarantied that after the call no DB LINK is open.
DELIMITER $$
CREATE TRIGGER `classdost`.`tr_xyz_media`
BEFORE INSERT ON classdost.xyz_media
FOR EACH ROW
BEGIN
DECLARE t_id INT(20);
IF NEW.id < 5000000 THEN
INSERT INTO xyz_media_temp (insert_date) VALUES(CURDATE());
SELECT MAX(xyz_media_temp.id) INTO t_id FROM xyz_media_temp;
SET New.id = t_id;
END IF;
END$$
DELIMITER ;
I have this trigger in MySQL this is running fine when I fire any Insert query in PHPMyadmin but when same Insert is fired from java code it is not executing. I tried changing the code made all request as Ajax with async: false and then I found out that that the earlier requests are still running in background and my later request is giving exception as no ID is returned from database yet.
What can be done to avoid this issue?
If you say requests are still running in background, then it is possible that your table is locked (MyISAM tables), and you cannot modify it.
See if table is locked using this query -
SHOW OPEN TABLES FROM database_name;
This query will show you open tables, whicj cannot be edited (INSERT, UPDATE or etc.). Then check you Java application, find code that locks tables.
I have created triggers over JDBC that doenot fire. I have checked its validity in the user_objects table and its valid and enabled. I tried creating trigger using the sqlplus console and it successfully fired, so where I could be going wrong? Any idea?
here is my trigger:
create or replace trigger t2
after update of FIRST_NAME on QWERTY
referencing new as newv old as oldv
for each row
begin
if :oldv.FIRST_NAME != :newv.FIRST_NAME then
insert into log values(user,sysdate,'QWERTY','FIRST_NAME',:oldv.FIRST_NAME,:newv.FIRST_NAME);
end if;
end;
I have tried execute(query) and executeUpdate(query) functions of Statement and tried PreparedStatement too but no luck yet.
You haven't considered null values in your code -- worth investigating, I'd think.