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.
Related
I'm trying to set a Lock for the row I'm working on until the next commit:
entityManager.createQuery("SELECT value from Table where id=:id")
.setParameter("id", "123")
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 10000)
.getSingleResult();
What I thought should happen is that if two threads will try to write to the db at the same time, one thread will reach the update operation before the other, the second thread should wait 10 seconds and then throw PessimisticLockException.
But instead the thread hangs until the other thread finishes, regardless of the timeout set.
Look at this example :
database.createTransaction(transaction -> {
// Execute the first request to the db, and lock the table
requestAndLock(transaction);
// open another transaction, and execute the second request in
// a different transaction
database.createTransaction(secondTransaction -> {
requestAndLock(secondTransaction);
});
transaction.commit();
});
I expected that in the second request the transaction will wait until the timeout set and then throw the PessimisticLockException, but instead it deadlocks forever.
Hibernate generates my request to the db this way :
SELECT value from Table where id=123 FOR UPDATE
In this answer I saw that Postgres allows only SELECT FOR UPDATE NO WAIT that sets the timeout to 0, but it isn't possible to set a timeout in that way.
Is there any other way that I can use with Hibernate / JPA?
Maybe this way is somehow recommended?
Hibernate supports a bunch of query hints. The one you're using sets the timeout for the query, not for the pessimistic lock. The query and the lock are independent of each other, and you need to use the hint shown below.
But before you do that, please be aware, that Hibernate doesn't handle the timeout itself. It only sends it to the database and it depends on the database, if and how it applies it.
To set a timeout for the pessimistic lock, you need to use the javax.persistence.lock.timeout hint instead. Here's an example:
entityManager.createQuery("SELECT value from Table where id=:id")
.setParameter("id", "123")
.setLockMode(LockModeType.PESSIMISTIC_WRITE)
.setHint("javax.persistence.lock.timeout", 10000)
.getSingleResult();
I think you could try
SET LOCAL lock_timeout = '10s';
SELECT ....;
I doubt Hibernate supports this out-of-box. You could try find a way to extend it, not sure if it worth it. Because I guess using locks on a postges database (which is mvcc) is not the smartest option.
You could also do NO WAIT and delay-retry several times from your code.
There is the lock_timeout parameter that does exactly what you want.
You can set it in postgresql.conf or with ALTER ROLE or ALTER DATABASE per user or per database.
The hint for lock timeout for PostgresSQL doesn't work on PostreSQL 9.6 (.setHint("javax.persistence.lock.timeout", 10000)
The only solution I found is uncommenting lock_timeout property in postgresql.conf:
lock_timeout = 10000 # in milliseconds, 0 is disabled
For anyone who's still looking for a data jpa solution, this is how i managed to do it
First i've created a function in postgres
CREATE function function_name (some_var bigint)
RETURNS TABLE (id BIGINT, counter bigint, organisation_id bigint) -- here you list all the columns you want to be returned in the select statement
LANGUAGE plpgsql
AS
$$
BEGIN
SET LOCAL lock_timeout = '5s';
return query SELECT * from some_table where some_table.id = some_var FOR UPDATE;
END;
$$;
then in the repository interface i've created a native query that calls the function. This will apply the lock timeout on that particular transaction
#Transactional
#Query(value = """
select * from function_name(:id);
""", nativeQuery = true)
Optional<SomeTableEntity> findById(Long id);
I'm using jdbi (but would prepared to use raw jdbc if needed). My DB is currently Oracle. I have an update that updates the first row matching certain criteria. I want to get the primary key of the updated row from the same statement. Is this possible?
I tried
Integer rowNum = handle
.createUpdate(sqlFindUpdate)
.bind("some var", myVal)
.executeAndReturnGeneratedKeys("id")
.mapTo(Integer.class)
.findOnly();
but I guess this isn't a generated key, as it doesn't find it (illegal state exception, but the update succeeds).
Basically, I have a list of items in the DB that I need to process. So, I want to get the next and mark it as "in progress" at the same time. I'd like to be able to support multiple worker threads, so it needs to be a single statement - I can't do the select after (the status has changed so it won't match anymore) and doing it before introduces a race condition.
I guess I could do a stored procedure that uses returning into but can I do it directly from java?
I'm answering my own question, but I don't think it's a good answer :) What I'm doing is kind of a hybrid. It is possible to dynamically run PL/SQL blocks from jdbi. Technically, this is from Java as I had asked, not via a stored procedure. However, it's kind of a hack, in my opinion - in this case why not just create the stored procedure (as I probably will, if I don't find a better solution). But, for info, instead of:
String sql = "update foo set status = 1 where rownr in (select rownr from (select rownr from foo where runid = :runid and status = 0 order by rownr) where rownum = 1)";
return jdbi.withHandle((handle) -> {
handle
.createUpdate(sql)
.bind("runid", runId)
.executeAndReturnGeneratedKeys("rownr")
.mapTo(Integer.class)
.findOnly();
});
you can do
String sql = "declare\n" +
"vRownr foo.rownr%type;\n" +
"begin\n" +
"update foo set status = 1 where rownr in (select rownr from (select rownr from foo where runid = :runid and status = 0 order by rownr) where rownum = 1) returning rownr into vRownr;\n" +
":rownr := vRownr;\n" +
"end;";
return jdbi.withHandle((handle) -> {
OutParameters params = handle
.createCall(sql)
.bind("runid", runId)
.registerOutParameter("rownr", Types.INTEGER)
.invoke();
return params.getInt("rownr");
});
Like I said, it's probably better to just create the procedure in this case, but it does give you the option to still build the SQL dynamically in java if you need to I guess.
Based on this question, as linked by #APC in the comments, it is possible to use the OracleReturning class without the declare/begin/end.
String sql = "update foo set status = 1 where rownr in (select rownr from (select rownr from foo where runid = ? and status = 0 order by rownr) where rownum = 1) returning rownr into ?";
return jdbi.withHandle((handle) -> {
handle
.createUpdate(sql)
.bind(0, runId)
.addCustomizer(OracleReturning.returnParameters().register(1, OracleTypes.INTEGER))
.execute(OracleReturning.returningDml())
.mapTo(Integer.class)
.findOnly();
});
However, OracleReturning doesn't support named parameters, so you have to use positionals. Since my main reason for using JDBI over plain JDBC is to get named parameter support, that's important to me, so I'm not sure which way I'll go
Pretty hard dependency on it being an Oracle DB you're calling...
Update: enhancement for named parameters in OracleReturning was merged to master, and will be included in 3.1.0 release. Kudos to #qualidafial for the patch
At the first stage i'm managing my app via JDBC, so i'm the resposible to build and validate all the SQL. But i was wondering if JPA could give me a hand on these tasks.
So, at this moment i've already have a DbaUser model, which was generated from the DBA_USERS table on the OracleDB, and i can actually list all of them.
However, i'm trying to manage to create or update more, but whenever i try to create using
em.createQuery("CREATE USER C##ANTONIO IDENTIFIED BY Antionio123").executeUpdate();
An syntax exception is launched: The query does not start with a valid identifier, has to be either SELECT, UPDATE or DELETE FROM.
Could you guys enlighten me a bit more or pointing me to some proper tutorials? I've been googling but nothing concrete apprears on Oracle DBs system tables.
Update1 (Thanks to #JB Nizet)
After replacing the execution of the query from JPQL to Native SQL, i've got an error such as:
Query: DataModifyQuery(sql="CREATE USER C? IDENTIFIED BY ANTONIO123").
I've replaced the hashtags with a scape character "...C##..." with "...C\#\#..." but the issue earns a different flavour:
Query: DataModifyQuery(sql="CREATE USER C\? IDENTIFIED BY ANTONIO123")
... and i really need to send the "##" to the Oracle DB. How can i force these special characters?
Update 2
So...after googling a bit more, i've found out positional parameters, and i've also discovered that we cannot have named paramteres on JPA native queries. After this, i've tried:
em.createNativeQuery("CREATE USER ?1 IDENTIFIED BY ANTONIO123").setParameter(1, "C##ANTONIO").executeUpdate();
Which triggers: ORA-01935: missing user or role name
alongside with
Error Code: 1935
Call: CREATE USER ? IDENTIFIED BY ANTONIO123
Which tells me that this binding doesn't work. Is there another way to do it?
Kind regards and thanks in advance,
Sammy
createQuery() expects a JPQL query. What you passed is not JPQL. It's SQL.
Use createNativeQuery() to execute SQL.
To create a common user (prefixed with C##) you (i.e. your JPA connection pool user) need a specific priviledges.
CREATE ROLE and
SET CONTAINER
Those are not a typical privileges granted to a JPA connection, so I'm guessing you will fail with the creation of a new common user.
Additionally you need to be connected to the root container.
The further example are plain JDBC called from Groovy, it should be easy possible to pass it to JPA if you get the DB connection.
def stmt = con.prepareStatement("SELECT SYS_CONTEXT('USERENV', 'CON_NAME') CON_NAME FROM dual")
def rs = stmt.executeQuery()
while(rs.next())
{
println "container name= ${rs.getString('CON_NAME')}"
}
gives
container name= CDB$ROOT
Note that if you are connected to a local DB, you get an error while trying to create a user prefixed with C##
ORA-65094: invalid local user or role name
If both conditions are fulfilled, it is possible to create / drop the common user:
String cu = "create user \"C##TEST\" identified by password123 profile \"DEFAULT\" account unlock"
con.createStatement().execute(cu)
resp.
cu = "drop user \"C##TEST\""
con.createStatement().execute(cu)
Finally should be stated, that this exercise was done for the aim of completeness only. I do not see a real use case for a JPA pool connection to be granted such privileges and connecting the root container. The database maintenance is typically done not using JPA.
Another option would be to create a stored procedure in the database and then invoke stored procedure from JPA, thus you do not need to bother about caveats and syntax.
E.g.assume that JPA provider is EclipseLink
Database stored procedure
CREATE OR REPLACE PROCEDURE p_user_creation (p_username IN VARCHAR2,
p_password IN VARCHAR2,
p_return OUT NUMBER)
IS
v_syntax VARCHAR2 (256);
BEGIN
IF (p_username IS NOT NULL)
THEN
v_syntax :=
'CREATE USER '''
|| p_username
|| ''' IDENTIFIED BY '''
|| p_password
|| '''';
EXECUTE IMMEDIATE v_syntax;
p_return := 0;
END IF;
EXCEPTION
WHEN OTHERS
THEN
raise_application_error (-20002, 'An error has occurred!');
END;
Java code snippet to invoke stored procedure
try {
Integer returnValue = null;
StoredProcedureQuery storedProcedureQuery =
getEntityManager().createStoredProcedureQuery("p_user_creation");
storedProcedureQuery.registerStoredProcedureParameter("p_username", String.class, ParameterMode.IN);
storedProcedureQuery.registerStoredProcedureParameter("p_password", String.class, ParameterMode.IN);
storedProcedureQuery.registerStoredProcedureParameter("p_return", Integer.class, ParameterMode.OUT);
storedProcedureQuery.setParameter("p_username", "SCOTT");
storedProcedureQuery.setParameter("p_password", "tiger");
storedProcedureQuery.execute();
returnValue = (Integer) storedProcedureQuery.getOutputParameterValue("p_return");
} catch (Exception e) {
log.error("Error " + e.getMessage());
}
DELIMITER //
CREATE PROCEDURE temp ( empId INT)
BEGIN
DECLARE var_etype VARCHAR(36);
SELECT
emptype = QOUTE(emptype)
FROM
dms_document
WHERE
id = empid;
SELECT
emptype,
CASE
WHEN emptype = 'P' THEN doctype
ELSE 'No Documents required'
END
FROM
dms_report
WHERE
pilot = 1;
End//
DELIMITER ;
I have created this procedure successfully but when I try to call it, I am getting error 1305 the function database.temp does not exist. I am trying to call using this statement:
SET #increment = '1';
select temp( #increment)
but I get Error, please tell me where I made mistake.
This is how you call it, use use the keyword call and then procedure's name
call procedureName(params);
in call of making an string
String sqlString = "procedureName("+?+")"; //in case of Integers
String sqlString = "procedureName('"+?+"')";//in case of Integers
bring the parameter in prepared statement.
MySQL's documentation on Using JDBC CallableStatements to Execute Stored Procedures explains the necessary steps quite well.
This is what your java code needs to look like:
CallableStatement cStmt = conn.prepareCall("{call temp(?)}");
cStmt.setInt(1, 42); //set your input parameter, empId, to 42.
If you want to work with the rows returned by your stored procedure's query in your Java code, you're also going to need to create an OUT parameter as noted in MySql's documentation page titled, CALL Syntax:
CALL can pass back values to its caller using parameters that are
declared as OUT or INOUT parameters
In order to call your stored procedure from MySQL workbench, use the CALL command. You can call stored procedure by directly setting values for each of the parameters:
SET #increment = 1;
CALL temp(#increment)
Then you simply use the SELECT statement to return the value of your output parameter
SELECT #outParameter
With help setting your output parameters, please read the article MySQL Stored Procedure - SELECT - Example.
Your stored procedure is syntactically wrong, and as mentioned in the comments, you're not using the stored procedure functionality for it's intended use. It's intended to be used for data manipulation not for querying. You should instead consider turning your procedure into a series of prepared statements.
Please let me know if you have any questions!
I am using JTDS with Java to connect to a Microsoft SQL database. I am able to connect to the database perfectly. However when I run the code below, I am getting an error "Could not find stored procedure 'get_queue_items' ". I've tried prefixing 'dbo.' to the stored procedure name, however I continue to get the error. I've also included the actual stored procedure for reference.
try {
// Prepare and call the stored procedure
CallableStatement proc = connection.prepareCall("{call get_queue_items(?) }");
// Register the ResultSet
proc.registerOutParameter(1, java.sql.Types.INTEGER);
// Register Input Parameters
proc.setInt("#last_queue_entry", 1);
// Execute the stored procedure
proc.execute();
// If we have a ResultSet
if (proc.getMoreResults()) {
ResultSet rs = proc.getResultSet();
if (rs.next()) {
// to complete...
}
}
}
catch(Exception ex)
{
System.out.println("Error: " + ex.getMessage());
}
And the stored procedure:
USE [test]
GO
/****** Object: StoredProcedure [dbo].[get_queue_items] Script Date: 11/17/2011 11:43:54 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER procedure [dbo].[get_queue_items] #qid int OUT, #last_queue_entry int as
-- select all the new records out of the main table into a temp table
-- the temp table is what we will use to process
select #qid = qid from test.[dbo].que_items
where qid > #last_queue_entry
I'm new to JTDS and Java, so its likely I am at fault, but any help would be appreciated.
Edit: Changed the Stored Procedure as per Cristian's advice, still getting the same error 'Could not find stored procedure 'get_queue_items'
Edit 2: Still not working - database connectivity seems fine also.
Today I met same problem and it seems to me that there is a bug in jTDS implementation. For me solution was procedure renaming and removing all underscore symbols (that is getQueueItems(?) in your case). Try, I think it must help to you.
you have to specify the output parameters, there are certain parameters that can not be specified as: text, ntext, and image.
ALTER procedure [dbo].[get_queue_items] #id int OUT, #last_queue_entry int as
-- select all the new records out of the main table into a temp table
-- the temp table is what we will use to process
select #id = id from test.[dbo].que_items
where qid > #last_queue_entry
this prodecure will return only id numbers