Concurrency issues when retriveing Ids of newly inserted rows with ibatis - java

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.
Could this cause problems? Like returning the id of the wrong insert?
( See also my related question about how to get ibatis to use the INSERT .. RETURING .. statements )

This is definitely wrong. Use:
select currval('sometable_id_seq')
or better yet:
INSERT INTO sometable ( somefield ) VALUES ( #value# ) returning id
which will return you inserted id.

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);

I have another thought. ibatis invokes the insert method delegate the Class: com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate,with the code:
try {
trans = autoStartTransaction(sessionScope, autoStart, trans);
SelectKeyStatement selectKeyStatement = null;
if (ms instanceof InsertStatement) {
selectKeyStatement = ((InsertStatement) ms).getSelectKeyStatement();
}
// Here we get the old value for the key property. We'll want it later if for some reason the
// insert fails.
Object oldKeyValue = null;
String keyProperty = null;
boolean resetKeyValueOnFailure = false;
if (selectKeyStatement != null && !selectKeyStatement.isRunAfterSQL()) {
keyProperty = selectKeyStatement.getKeyProperty();
oldKeyValue = PROBE.getObject(param, keyProperty);
generatedKey = executeSelectKey(sessionScope, trans, ms, param);
resetKeyValueOnFailure = true;
}
StatementScope statementScope = beginStatementScope(sessionScope, ms);
try {
ms.executeUpdate(statementScope, trans, param);
}catch (SQLException e){
// uh-oh, the insert failed, so if we set the reset flag earlier, we'll put the old value
// back...
if(resetKeyValueOnFailure) PROBE.setObject(param, keyProperty, oldKeyValue);
// ...and still throw the exception.
throw e;
} finally {
endStatementScope(statementScope);
}
if (selectKeyStatement != null && selectKeyStatement.isRunAfterSQL()) {
generatedKey = executeSelectKey(sessionScope, trans, ms, param);
}
autoCommitTransaction(sessionScope, autoStart);
} finally {
autoEndTransaction(sessionScope, autoStart);
}
You can see that the insert and select operator are in a Transaction. So I think there is no concureency problem with the insert method.

Related

jdbcTemplate not returning data from stored procedure

I am trying to call my Teradata stored procedure using simpleJDBCcall but I always get ZERO result though data is available in my table. I tried to debug but no luck. here is my stored procedure. is there any thing I am doing wrong here?
REPLACE PROCEDURE MYPROC
(
IN ID VARCHAR(50)
)
DYNAMIC RESULT SETS 1
MAIN : BEGIN
DECLARE ltype VARCHAR(256);
DECLARE mainSql VARCHAR (5000);
DECLARE emptySql VARCHAR(5000);
DECLARE mainCur CURSOR WITH RETURN ONLY FOR mainStmt;
DECLARE emptyCur CURSOR WITH RETURN ONLY FOR emptyStmt;
SET mainSql = 'LOCKING ROW FOR ACCESS SELECT * FROM MYTABLE where ID = ''' || ID || ''';';
SET emptySQL = 'SELECT NULL AS ID;';
PREPARE mainStmt FROM mainSql;
PREPARE emptyStmt FROM emptySql;
BEGIN TRANSACTION;
SET statementNo = 100;
OPEN mainCur;
END TRANSACTION;
END MAIN;
and here is my simpleJDBCCall
simpleJdbcCall.withProcedureName("MYPROC").returningResultSet("mainCur",
BeanPropertyRowMapper.newInstance(MYCLASS.class) ).declareParameters(new SqlParameter(ID, Types.VARCHAR) );
SqlParameterSource namedParameters = new MapSqlParameterSource("ID", transId);
try {
Map<String, Object> out = simpleJdbcCall.execute(namedParameters);
System.out.println(out);
} catch (Exception e) {
System.err.println(e);
}

PreparedStatement.executeUpdate() is not working for Oracle

I am using PreparedStatement for executing the update query.
The following is the query:
String callersUpdateQuery = "update W67U999S a set pcrdattim= ? where exists (select b.CRDATTIM, b.RECORDCD, b.CRNODE, b.UNITCD, b.WRKTYPE from W03U999S b where a.PCRDATTIM = ? and a.CCRDATTIM = b.CRDATTIM and a.CRECORDCD = b.RECORDCD and a.CCRNODE = b.CRNODE and a.PRECORDCD = 'F' and a.PCRNODE = '01' and b.WRKTYPE = 'CALLER' and b.UNITCD=? and a.crecordcd='T')";
The below is the java code that should update the records:
preparedStatement = dbConnection.prepareStatement(callersUpdateQuery);
preparedStatement.setString(1,newFolderCrdattim);
preparedStatement.setString(2,crdattim);
preparedStatement.setString(3,businessAreaName.trim());
int j = preparedStatement.executeUpdate();
But preparedStatement.executeUpdate() is not updating the required rows and returning the updated rows count as zero. Weirdly, the same sql query when I execute at the database end, the records are getting updated.
My database is Oracle and the schema of the table that should be updated is below:
Name Null Type
----------- -------- ----------
PCRDATTIM NOT NULL CHAR(26)
PRECORDCD NOT NULL CHAR(1)
PCRNODE NOT NULL CHAR(2)
RECORDTYPE NOT NULL NUMBER(3)
CCRDATTIM NOT NULL CHAR(26)
CRECORDCD NOT NULL CHAR(1)
CCRNODE NOT NULL CHAR(2)
CRDATTIM NOT NULL CHAR(26)
LINKRULE_ID NOT NULL NUMBER(14)
Can anyone guess what's wrong with the code or query?
First, did you check for existence of tuples on the select you're using as condition in where clause?
If there are rows being returned. The issue may be related to the transaction in which you're executing your update statement. Double check for your transaction mode and if it is really being committed.
As a query optimization suggestion I'd change the statement to:
String callersUpdateQuery =
"update W67U999S a
set pcrdattim= ?
where
a.PCRDATTIM = ?
and a.PRECORDCD = 'F'
and a.PCRNODE = '01'
and a.CRECORDCD ='T'
and exists (
select
b.CRDATTIM,
b.RECORDCD,
b.CRNODE,
b.WRKTYPE
from W03U999S b
where
b.CCRDATTIM = a.CRDATTIM
and b.CRECORDCD = a.RECORDCD
and b.CCRNODE = a.CRNODE
and b.WRKTYPE = 'CALLER'
and b.UNITCD=?
)";
That way you will be first reducing the tuples from a then use it to narrow the b tuples only to those that match.
Oracle CHAR type is the culprit here. The columns that I want to update are of type CHAR. That's causing the issue. This link helped me in figuring out the solution: Oracle JDBC and Oracle CHAR data type

How to getString(Table.Column) in ResultSet JayBird

I need to use the database Firebird and for this I use the Jaybird 2.2.9.
When I used the MySQL driver, to converter of ResultSet to Object this way:
empresa.setBairro(rs.getString("empresa.bairro")); // (Table.Column)
empresa.setCep(rs.getString("empresa.cep")); // (Table.Column)
empresa.setCidade(rs.getString("empresa.cidade")); // (Table.Column)
But with Jaybird the resultSet don't return rs.getString("Table.Column")
I need this way when I have inner join in SQL.
Anyone help me?
This is my full code
public ContaLivros converterContaLivros(ResultSet rs, Integer linha) throws Exception {
if (rs.first()) {
rs.absolute(linha);
ContaLivros obj = new ContaLivros();
obj.setId(rs.getLong("cad_conta.auto_id"));
obj.setNome(rs.getString("cad_conta.nome"));
if (contain("cad_banco.auto_id", rs)) {
obj.setBancoLivros(converterBancoLivros(rs, linha));
} else {
obj.setBancoLivros(new BancoLivros(rs.getLong("cad_conta.banco"), null, null, null));
}
obj.setAgencia(rs.getInt("cad_conta.agencia"));
obj.setAgenciaDigito(rs.getInt("cad_conta.agencia_digito"));
obj.setConta(rs.getInt("cad_conta.conta"));
obj.setContaDigito(rs.getInt("cad_conta.conta_digito"));
obj.setLimite(rs.getDouble("cad_conta.limite"));
obj.setAtivo(rs.getString("cad_conta.ativo"));
return obj;
} else {
return null;
}
}
You can't. Jaybird retrieves the columns by its label as specified in JDBC 4.2, section 15.2.3. In Firebird the column label is either the original column name, or the AS alias, the table name isn't part of this. The extension of MySQL that you can prefix the table name for disambiguation is non-standard.
Your options are to specify aliases in the query and retrieve by this aliasname, or to process the result set metadata to find the right indexes for each column and retrieve by index instead.
However note that in certain queries (for example UNION), the ResultSetMetaData.getTableName cannot return the table name, as Firebird doesn't "know" it (as you could be applying a UNION to selects from different tables).
The name in jdbc will not have the table in it.
You can either
work with positional parameters ( getString (1); and so on )
Or
define column name alias in your select (select a.name namefroma from tableone a )
Or
simply do rs.getString ("column"); without the table prefix if name is unambigous

Deciding addition or updation

I need to add a record if already a record with the primary key doesn't exist; otherwise existing record is to be updated. For this I am querying the db with the primary key. If no record exist, I am adding; otherwise updating. I am coding this in java using raw JDBC.
Is there a better way to do this?
insert … select … where not exist
INSERT INTO ... VALUES ... ON duplicate KEY UPDATE id = id
REPLACE INTO ... SELECT ... FROM ...
The most soft way to do this is to use special query INSERT ... ON DUPLICATE KEY UPDATE query in My Sql. It is much more effective than check is conflict exist on the application side.
Code snippet for example:
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(
"INSERT INTO table (a,b,c) VALUES (?,?,?) ON DUPLICATE KEY UPDATE c=?;"
);
int paramIndex = 1;
statement.setInt(parameterIndex++, primaryKeyValue);
statement.setInt(parameterIndex++, secondValue);
statement.setInt(parameterIndex++, thirdValue);
statement.setInt(parameterIndex++, thirdValue);
int updatedCount = statement.executeUpdate();
} finally {
statement.close();
}
Another way would be REPLACE INTO which takes the same syntax as INSERT but removes the old entry when the primary key already exists before inserting.
The simplest and the most general way is to count the record before insert/update.
Pseudo Code:
SELECT COUNT(*) as recordCount FROM mytable WHERE keyField = ?
if (recordCount > 0) {
UPDATE mytable SET value1=? WHERE keyField = ?
} else {
INSERT INTO mytable (keyField, value1) VALUES (?, ?)
}

Problems with JDBCTemplate retrieving value set by LAST_INSERT_ID() mySQL trick

I'm having some trouble using mySQL and Spring JDBCTemplate. I have an INSERT ... ON DUPLICATE KEY UPDATE which increments a counter, but uses the LAST_INSERT_ID() trick to return the new value in the same query through the last generated id. I'm using JDCTemplate.update() with the PreparedStatementCreator and GeneratedKeyHolder, but I'm getting multiple entries in the KeyHolder.getKeyList(). When I run it manually in the mySQL client, I'm getting 2 rows affected (when it hits the DUPLICATE KEY UPDATE), and then the SELECT LAST_INSERT_ID(); gives me the correct value.
UPDATE: It looks like the 3 entries in the keyList all have 1 entry each, and they are all incrementing values. So keyList.get(0).get(0) has the value that should be returned, and then keyList.get(0).get(1) is the previous value +1, and then keyList.get(0).get(2) is the previous value +1. So for example, if 4 is the value that should be returned, it gives me 4, 5, 6 in that order.
The relevant code is:
CREATE TABLE IF NOT EXISTS `ClickChargeCheck` (
uuid VARCHAR(50),
count INTEGER Default '0',
CreatedOn Timestamp DEFAULT '0000-00-00 00:00:00',
UpdatedOn Timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`uuid`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
String CLICK_CHARGE_CHECK_QUERY = "INSERT INTO ClickChargeCheck (uuid,count,CreatedOn) VALUES(?,LAST_INSERT_ID(1),NOW()) ON DUPLICATE KEY UPDATE count = LAST_INSERT_ID(count+1)";
...
...
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
centralStatsJdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement(CLICK_CHARGE_CHECK_QUERY, Statement.RETURN_GENERATED_KEYS);
ps.setString(1, uuid);
return ps;
}}, keyHolder);
int count = keyHolder.getKey().intValue();
For me, I ran into the same problem. For INSERT...ON DUPLICATE KEY UPDATE, GeneratedKeyHolder is throwing an exception because it expects there to only be one returned key, which is not the case with MySQL. I basically had to implement a new implementation of KeyHolder which can handle multiple returned key.
public class DuplicateKeyHolder implements KeyHolder {
private final List<Map<String, Object>> keyList;
/* Constructors */
public Number getKey() throws InvalidDataAccessApiUsageException, DataRetrievalFailureException {
if (this.keyList.isEmpty()) {
return null;
}
Iterator<Object> keyIter = this.keyList.get(0).values().iterator();
if (keyIter.hasNext()) {
Object key = keyIter.next();
if (!(key instanceof Number)) {
throw new DataRetrievalFailureException(
"The generated key is not of a supported numeric type. " +
"Unable to cast [" + (key != null ? key.getClass().getName() : null) +
"] to [" + Number.class.getName() + "]");
}
return (Number) key;
} else {
throw new DataRetrievalFailureException("Unable to retrieve the generated key. " +
"Check that the table has an identity column enabled.");
}
}
public Map<String, Object> getKeys() throws InvalidDataAccessApiUsageException {
if (this.keyList.isEmpty()) {
return null;
}
return this.keyList.get(0);
}
public List<Map<String, Object>> getKeyList() {
return this.keyList;
}
}
If you just use this class where you would otherwise use GeneratedKeyHolder, everything works. And I found that you don't even need to add ... ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id) ... in your insert statement.
Turns out that mySQL's PreparedStatement runs that query, and says it returns 3 rows. So the getGeneratedKeys() call (delegated to StatementImpl.getGeneratedKeysInternal()) which, since it sees 3 rows returned, grabs the first LAST_INSERT_ID() value, and then increments from there up to the number of rows. So, I'll have to grab the GeneratedKeyHolder.getKeyList() and grab the first entry, and get it's first value and that'll be the value I need.

Categories

Resources