Insert dynamic values into DB with NamedParameterJdbcTemplate - java

I have an MSSQL (very much simplified) query that looks like this:
BEGIN TRY
BEGIN TRANSACTION
IF OBJECT_ID(N\'tempdb..#temptable\') IS NOT NULL DROP TABLE #temptable;
CREATE TABLE #temptable (
id INT,
id2 INT
);
INSERT INTO #temptable (
id
id2
)
VALUES
%1$s; -- One or more (int, int) pairs
INSERT INTO anothertable (
--other stuff
) VALUES (
-- values dervied from #temptable
);
INSERT INTO anothertable2 (
--other stuff
) VALUES (
-- values dervied from #temptable
);
COMMIT TRANSACTION;
SELECT 1 AS Result;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
SELECT 0 AS Result, ERROR_MESSAGE() AS Error;
END CATCH
In the transaction, I need to be able to do a variable number of inserts, and also return the result of the transaction.
The %1$s in the query string preferably something that I can feed into a NamedParameterJdbcTemplate, which I call in the manner of:
// parameters is a Map<String, Object>
jdbcTemplate.queryForList(query, parameters)
Due to the transactional and multi-statement nature of the query, I cannot use batchupdate to generate the parameterized inserts.
Due to the specific returns that I need from the query in the case of a failure, I cannot replace the BEGIN TRANSACTION with a java-side #Transactional either.
Due to the variable nature of the number of inserts I need to make in %1$s, I cannot prepare the statement with ?s.
How can I best generate the values for %1$s in the query?

Related

Create MySQL stored procedure using JPA Hibernate

I'm trying to create a stored procedure in a MySQL database using the contents of a text file:
USE myDatabase;
DROP PROCEDURE IF EXISTS myStoredProcedure;
DELIMITER $$
CREATE PROCEDURE myStoredProcedure
(
_description VARCHAR(50),
_value INT
)
BEGIN
INSERT INTO myTable
(
description,
value
) VALUES (
_description,
_value
);
SELECT
id,
description,
value
FROM myTable
WHERE id = LAST_INSERT_ID();
END;
$$
DELIMITER ;
I execute the SQL using a native query:
Query query = entityManager.createNativeQuery(queryText);
...
query.executeUpdate();
But it gets an error on the DROP PROCEDURE
I commented out the DROP PROCEDURE and then it gets an error on the DELIMITER
Basically, it gets an error on any line after the first semicolon.
It seems as if JPA hibernate is parsing my query and telling me there's a problem with it rather than passing the unadulterated text onto MySQL.
The sql runs in MySQL without error.
I can't find anything in Google about creating a stored procedure with JPA, only calling one.
Does anyone have any insight on what I might be doing wrong? Or if this is even possible.
This can be possible if you mention the following property in the url
spring.datasource.url=jdbc:mysql://localhost:3306/test?allowMultiQueries=true
The allowMultiQueries will instruct the driver to sent delimited queries to the database.
Please note that if you are using native queries be-aware of sql injection attack.
You dont need to put the delimiter(DELIMITER) explicitly.The sql statement
The following query works
SET myDatabase;
DROP PROCEDURE IF EXISTS myStoredProcedure;
CREATE PROCEDURE myStoredProcedure ( _description VARCHAR(50), _value INT )
BEGIN
INSERT INTO
myTable ( description, value )
VALUES ( _description, _value );
SELECT id, description, value
FROM myTable
WHERE id = LAST_INSERT_ID();
END;

jOOQ and bridge tables

I'm trying to imagine how to use jOOQ with bridge tables.
Suppose you have
CREATE TABLE TableA (
id BIGSERIAL PRIMARY KEY
)
CREATE TABLE TableB (
id BIGSERIAL PRIMARY KEY
)
CREATE TABLE TableBridge (
id BIGSERIAL,
table_a_id INTEGER NOT NULL,
table_b_id INTEGER NOT NULL,
CONSTRAINT tablea_pk_id PRIMARY KEY (table_a_id)
REFERENCES TableA (id) MATCH SIMPLE,
CONSTRAINT tableb_pk_id PRIMARY KEY (table_b_id)
REFERENCES TableB (id) MATCH SIMPLE
)
When mapping this schema using jOOQ there will be three record classes, TableARecord, TableBRecord and TableBridgeRecord.
If I want to persist through an insert a record for TableA, should I simply first create and persist the TableB records, then persit rows for TableB and then manually add the TableBridge rows? Isn't there any way to automatically save also the rows in the bridge table?
There are several ways to solve this kind of problem:
1. Do it with a "single" jOOQ statement (running three SQL statements)
The most idiomatic way to solve this kind of problem with standard jOOQ would be to write a single SQL statement that takes care of all three insertions in one go:
ctx.insertInto(TABLE_BRIDGE)
.columns(TABLE_BRIDGE.TABLE_A_ID, TABLE_BRIDGE.TABLE_B_ID)
.values(
ctx.insertInto(TABLE_A)
.columns(TABLE_A.VAL)
.values(aVal)
.returning(TABLE_A.ID)
.fetchOne()
.get(TABLE_A.ID),
ctx.insertInto(TABLE_B)
.columns(TABLE_B.VAL)
.values(bVal)
.returning(TABLE_B.ID)
.fetchOne()
.get(TABLE_B.ID)
)
.execute();
The above works with jOOQ 3.8. Quite possibly, future versions will remove some of the verbosity around returning() .. fetchOne() .. get().
2. Do it with a single SQL statement
I assume you're using PostgreSQL from your BIGSERIAL data type usage, so the following SQL statement might be an option to you as well:
WITH
new_a(id) AS (INSERT INTO table_a (val) VALUES (:aVal) RETURNING id),
new_b(id) AS (INSERT INTO table_b (val) VALUES (:bVal) RETURNING id)
INSERT INTO table_bridge (table_a_id, table_b_id)
SELECT new_a.id, new_b.id
FROM new_a, new_b
The above query is currently not supported entirely via jOOQ 3.8 API, but you can work around the jOOQ API's limitations by using some plain SQL:
ctx.execute(
"WITH "
+ " new_a(id) AS ({0}), "
+ " new_b(id) AS ({1}) "
+ "{2}",
// {0}
insertInto(TABLE_A)
.columns(TABLE_A.VAL)
.values(aVal)
.returning(TABLE_A.ID),
// {1}
insertInto(TABLE_B)
.columns(TABLE_B.VAL)
.values(bVal)
.returning(TABLE_B.ID),
// {2}
insertInto(TABLE_BRIDGE)
.columns(TABLE_BRIDGE.TABLE_A_ID, TABLE_BRIDGE.TABLE_B_ID)
.select(
select(field("new_a.id", Long.class), field("new_b.id", Long.class))
.from("new_a, new_b")
)
);
Clearly also here, there will be improvements in future jOOQ APIs.
3. Do it with UpdatableRecords
In this particular simple case, you could get away simply by calling:
TableARecord a = ctx.newRecord(TABLE_A);
a.setVal(aVal);
a.store();
TableBRecord b = ctx.newRecord(TABLE_B);
b.setVal(bVal);
b.store();
TableBridgeRecord bridge = ctx.newRecord(TABLE_BRIDGE);
bridge.setTableAId(a.getId());
bridge.setTableBId(b.getId());
bridge.store();

How to upsert(update if exists, else insert) into a table using jdbcTemplate [duplicate]

The UPSERT operation either updates or inserts a row in a table, depending if the table already has a row that matches the data:
if table t has a row exists that has key X:
update t set mystuff... where mykey=X
else
insert into t mystuff...
Since Oracle doesn't have a specific UPSERT statement, what's the best way to do this?
The MERGE statement merges data between two tables. Using DUAL
allows us to use this command. Note that this is not protected against concurrent access.
create or replace
procedure ups(xa number)
as
begin
merge into mergetest m using dual on (a = xa)
when not matched then insert (a,b) values (xa,1)
when matched then update set b = b+1;
end ups;
/
drop table mergetest;
create table mergetest(a number, b number);
call ups(10);
call ups(10);
call ups(20);
select * from mergetest;
A B
---------------------- ----------------------
10 2
20 1
The dual example above which is in PL/SQL was great becuase I wanted to do something similar, but I wanted it client side...so here is the SQL I used to send a similar statement direct from some C#
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" )
However from a C# perspective this provide to be slower than doing the update and seeing if the rows affected was 0 and doing the insert if it was.
An alternative to MERGE (the "old fashioned way"):
begin
insert into t (mykey, mystuff)
values ('X', 123);
exception
when dup_val_on_index then
update t
set mystuff = 123
where mykey = 'X';
end;
Another alternative without the exception check:
UPDATE tablename
SET val1 = in_val1,
val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%rowcount = 0 )
THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
insert if not exists
update:
INSERT INTO mytable (id1, t1)
SELECT 11, 'x1' FROM DUAL
WHERE NOT EXISTS (SELECT id1 FROM mytble WHERE id1 = 11);
UPDATE mytable SET t1 = 'x1' WHERE id1 = 11;
None of the answers given so far is safe in the face of concurrent accesses, as pointed out in Tim Sylvester's comment, and will raise exceptions in case of races. To fix that, the insert/update combo must be wrapped in some kind of loop statement, so that in case of an exception the whole thing is retried.
As an example, here's how Grommit's code can be wrapped in a loop to make it safe when run concurrently:
PROCEDURE MyProc (
...
) IS
BEGIN
LOOP
BEGIN
MERGE INTO Employee USING dual ON ( "id"=2097153 )
WHEN MATCHED THEN UPDATE SET "last"="smith" , "name"="john"
WHEN NOT MATCHED THEN INSERT ("id","last","name")
VALUES ( 2097153,"smith", "john" );
EXIT; -- success? -> exit loop
EXCEPTION
WHEN NO_DATA_FOUND THEN -- the entry was concurrently deleted
NULL; -- exception? -> no op, i.e. continue looping
WHEN DUP_VAL_ON_INDEX THEN -- an entry was concurrently inserted
NULL; -- exception? -> no op, i.e. continue looping
END;
END LOOP;
END;
N.B. In transaction mode SERIALIZABLE, which I don't recommend btw, you might run into
ORA-08177: can't serialize access for this transaction exceptions instead.
I'd like Grommit answer, except it require dupe values. I found solution where it may appear once: http://forums.devshed.com/showpost.php?p=1182653&postcount=2
MERGE INTO KBS.NUFUS_MUHTARLIK B
USING (
SELECT '028-01' CILT, '25' SAYFA, '6' KUTUK, '46603404838' MERNIS_NO
FROM DUAL
) E
ON (B.MERNIS_NO = E.MERNIS_NO)
WHEN MATCHED THEN
UPDATE SET B.CILT = E.CILT, B.SAYFA = E.SAYFA, B.KUTUK = E.KUTUK
WHEN NOT MATCHED THEN
INSERT ( CILT, SAYFA, KUTUK, MERNIS_NO)
VALUES (E.CILT, E.SAYFA, E.KUTUK, E.MERNIS_NO);
I've been using the first code sample for years. Notice notfound rather than count.
UPDATE tablename SET val1 = in_val1, val2 = in_val2
WHERE val3 = in_val3;
IF ( sql%notfound ) THEN
INSERT INTO tablename
VALUES (in_val1, in_val2, in_val3);
END IF;
The code below is the possibly new and improved code
MERGE INTO tablename USING dual ON ( val3 = in_val3 )
WHEN MATCHED THEN UPDATE SET val1 = in_val1, val2 = in_val2
WHEN NOT MATCHED THEN INSERT
VALUES (in_val1, in_val2, in_val3)
In the first example the update does an index lookup. It has to, in order to update the right row. Oracle opens an implicit cursor, and we use it to wrap a corresponding insert so we know that the insert will only happen when the key does not exist. But the insert is an independent command and it has to do a second lookup. I don't know the inner workings of the merge command but since the command is a single unit, Oracle could execute the correct insert or update with a single index lookup.
I think merge is better when you do have some processing to be done that means taking data from some tables and updating a table, possibly inserting or deleting rows. But for the single row case, you may consider the first case since the syntax is more common.
A note regarding the two solutions that suggest:
1) Insert, if exception then update,
or
2) Update, if sql%rowcount = 0 then insert
The question of whether to insert or update first is also application dependent. Are you expecting more inserts or more updates? The one that is most likely to succeed should go first.
If you pick the wrong one you will get a bunch of unnecessary index reads. Not a huge deal but still something to consider.
Try this,
insert into b_building_property (
select
'AREA_IN_COMMON_USE_DOUBLE','Area in Common Use','DOUBLE', null, 9000, 9
from dual
)
minus
(
select * from b_building_property where id = 9
)
;
From http://www.praetoriate.com/oracle_tips_upserts.htm:
"In Oracle9i, an UPSERT can accomplish this task in a single statement:"
INSERT
FIRST WHEN
credit_limit >=100000
THEN INTO
rich_customers
VALUES(cust_id,cust_credit_limit)
INTO customers
ELSE
INTO customers SELECT * FROM new_customers;

JDBC: multicolumn IN query

I have a following query:
SELECT
date, userId, value
FROM
tbl_table
WHERE
date = to_date(:date, 'YYYY-MM-DD')
AND
userId = :userId
It allows to request for a single value like this:
MapSqlParameterSource args = new MapSqlParameterSource();
args.addValue("date", date, Types.VARCHAR);
args.addValue("userId", userId, Types.VARCHAR);
SqlRowSet rowSet = jdbcTemplate.queryForRowSet(SQL_SELECT, args);
jdbcTemplate.queryForRowSet(SQL_SELECT_MARKET_VALUE, args);
This is totally ok, but extremelly slow in case you have to query value for many date/userId pairs.
I would like to optimize it using multicolumn IN clause, but how do I handle multicolumn list via JDBC (or better question: is it possible using JDBC)?
Oracle supports multiple columns in "in" predicate:
SELECT
date, userId, value
FROM
tbl_table
WHERE
(date, userId) IN ((to_date(:date1, 'YYYY-MM-DD'), :userId1), (to_date(:date2, 'YYYY-MM-DD'), :userId2))
However JDBC doesn't provide a decent support of in-statement parameters - you will have to build the query using StringBuilder or use some of workarounds described here
It depends of details. If user/date filter is quite persistent (should be user more than once) temporary table will be the best decision. You can fill it once, you can edit it, and you can use it several times without reloading.
If you need of quite large number of pairs, I'd recommend you to use a table type. It would be something like this:
create type DateUserPair as object (dt date, userid integer);
create type DateUserPairs as table of DateUserPair;
....
SELECT
date, userId, value
FROM
tbl_table src,
table(cast :filter as DateUserPairs) flt
WHERE
src.date = flt.dt and
src.userId = flt.userId;
If filter would be small, filtering by (date, userId) in ((?,?), (?,?), ...) would be simple and clever.
Btw, your approach
date = to_date(:date, 'YYYY-MM-DD')
isn't good practise. Such conversions should be done by client, not by server. Use
date = :date
and assign it as date instead.
If what you want is to pass JDBC a list of date/userId pairs, or a list of dates and a list of userIds, I think it will not work.
A possible workaround in Oracle would be using a global temporary table with ON COMMIT DELETE ROWS. Your would have:
-- DDL for the workaround
CREATE GLOBAL TEMPORARY TABLE admin_work_area
(d DATE,
userId VARCHAR2(10))
ON COMMIT DELETE ROWS;
...
-- Start of query method pseudo-code
...
-- You should be able to JDBC-batch these for better performance
INSERT INTO temp_multicolumn_filter (d, userId) VALUES (date1, userId1);
INSERT INTO temp_multicolumn_filter (d, userId) VALUES (date2, userId2);
...
-- Query using temp_multicolumn_filter
SELECT date, userId, value
FROM tbl_table
WHERE
(date, userId) in (select d, userId from temp_multicolumn_filter);
...
-- End of query method pseudo-code
As the temporary table has the ON COMMIT DELETE ROWS, each transaction will only see its own date/userId pairs. Just remember that if you use the temporary table more than once in the same transaction, you might need to clear it before using it.
UPDATE:
Another option would be using a PIPELINED PL/SQL function to "build" your table inside the query:
-- DDL for this workaround
CREATE TYPE date_userid_pair AS OBJECT (
d DATE,
userId VARCHAR2(10));
CREATE TYPE date_userid_dataset IS TABLE OF date_userid_pair;
CREATE FUNCTION decode_date_userid_pairs(dates in varchar2, userIds in varchar2)
RETURN date_userid_dataset PIPELINED IS
result_row date_userid_pair;
BEGIN
WHILE there are more "rows" in the parameters LOOP
result_row.d := -- Decode next date from dates
result_row.userId := -- Decode next userId from userIds
PIPE ROW(result_row);
END LOOP;
END;
// Start of query method pseudo-code
...
// This is Java code: encodeList encodes a List of elements into a String.
encodedDates = encodeList(listOfDates);
encodedUserIds = encodeList(listOfUserIds);
...
// Query using temp_multicolumn_filter
SELECT date, userId, value
FROM tbl_table
WHERE
(date, userId) in (
select date, userId
from TABLE(decode_date_userid_pair(:encodedDates, :encodedUserIds));
...
// End of query method pseudo-code
But this is more hacky, and if you don't have privileges to create a temporary table, then you probably won't have CREATE TYPE either (you might not even have CREATE FUNCTION privilege).

Howto return ids on Inserts with Ibatis ( with RETURNING keyword )

I'm using iBatis/Java and Postgres 8.3.
When I do an insert in ibatis i need the id returned.
I use the following table for describing my question:
CREATE TABLE sometable ( id serial NOT NULL, somefield VARCHAR(10) );
The Sequence sometable_id_seq gets autogenerated by running the create statement.
At the moment i use the following sql map:
<insert id="insertValue" parameterClass="string" >
INSERT INTO sometable ( somefield ) VALUES ( #value# );
<selectKey keyProperty="id" resultClass="int">
SELECT last_value AS id FROM sometable_id_seq
</selectKey>
</insert>
It seems this is the ibatis way of retrieving the newly inserted id. Ibatis first runs a INSERT statement and afterwards it asks the sequence for the last id.
I have doubts that this will work with many concurrent inserts. ( discussed in this question )
I'd like to use the following statement with ibatis:
INSERT INTO sometable ( somefield ) VALUES ( #value# ) RETURNING id;
But when i try to use it within a <insert> sqlMap ibatis does not return the id. It seems to need the <selectKey> tag.
So here comes the question:
How can i use the above statement with ibatis?
The <selectKey> element is a child of the <insert> element and its content is executed before the main INSERT statement. You can use two approaches.
Fetch the key after you have inserted the record
This approach works depending on your driver. Threading can be a problem with this.
Fetching the key before inserting the record
This approach avoids threading problems but is more work. Example:
<insert id="insert">
<selectKey keyProperty="myId"
resultClass="int">
SELECT nextVal('my_id_seq')
</selectKey>
INSERT INTO my
(myId, foo, bar)
VALUES
(#myId#, #foo#, #bar#)
</insert>
On the Java side you can then do
Integer insertedId = (Integer) sqlMap.insert("insert", params)
This should give you the key selected from the my_id_seq sequence.
Here is simple example:
<statement id="addObject"
parameterClass="test.Object"
resultClass="int">
INSERT INTO objects(expression, meta, title,
usersid)
VALUES (#expression#, #meta#, #title#, #usersId#)
RETURNING id
</statement>
And in Java code:
Integer id = (Integer) executor.queryForObject("addObject", object);
object.setId(id);
This way more better than use :
It's simpler;
It have not requested to know sequence name (what usually hidden from postgresql developers).

Categories

Resources