Using Spring's KeyHolder with programmatically-generated primary keys
Asked Answered
C

5

11

I am using Spring's NamedParameterJdbcTemplate to perform an insert into a table. The table uses a NEXTVAL on a sequence to obtain the primary key. I then want this generated ID to be passed back to me. I am using Spring's KeyHolder implementation like this:

KeyHolder key = new GeneratedKeyHolder();
jdbcTemplate.update(Constants.INSERT_ORDER_STATEMENT, params, key);

However, when I run this statement, I am getting:

org.springframework.dao.DataRetrievalFailureException: The generated key is not of a supported numeric type. Unable to cast [oracle.sql.ROWID] to [java.lang.Number]
    at org.springframework.jdbc.support.GeneratedKeyHolder.getKey(GeneratedKeyHolder.java:73)

Any ideas what I am missing?

Czarra answered 12/5, 2010 at 14:5 Comment(0)
A
-4

I think you're using the wrong method on JdbcTemplate. The only one of the update methods that would seem to match your code fragment is

int update(String sql, Object... args)

If so, you're passing params and key as a two-element vargs array, and JdbcTemplate is treating key as a normal bind parameters, and mis-interpreting it.

The only public update method on JdbcTemplate that takes a KeyHolder is

int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)

So you'll need to rephrase your code to use that.

Anchoress answered 12/5, 2010 at 18:31 Comment(1)
misleading, check below answer of KonstantinInterdisciplinary
C
23

Just solved a similar issue - with Oracle you need to use another method (from NamedParameterJdbcOperations) -

int update(String sql,
           SqlParameterSource paramSource,
           KeyHolder generatedKeyHolder,
           String[] keyColumnNames)
           throws DataAccessException

with keyColumnNames containing auto-generated columns, in my case just ["Id"]. Otherwise all you get is ROWID. See Spring doc for details.

Ching answered 16/3, 2011 at 22:40 Comment(1)
Thanks this answer the question better than previous answers and also solve my problemVardar
B
5

You have to execute the JdbcTemplate.update(PreparedStatementCreator p, KeyHolder k).

The key returned from the database will be injected into the KeyHolder parameter object.

An example:

final String INSERT_ORDER_STATEMENT 
       = "insert into order (product_id, quantity) values(?, ?)";

KeyHolder keyHolder = new GeneratedKeyHolder();
    jdbcTemplate.update(new PreparedStatementCreator() {
        public PreparedStatement createPreparedStatement(
            Connection connection) throws SQLException {
                PreparedStatement ps = connection.prepareStatement(
                    INSERT_ORDER_STATEMENT, new String[] { "id" });
                ps.setInt(1, order.getProductId());
                ps.setInt(2, order.getQuantity());
                return ps;
            }
        }, keyHolder);

More information can be found here in the reference documentation.

Bamberger answered 12/5, 2010 at 15:33 Comment(2)
This is actually returning ROWID in holder and not a Number!Secondclass
no, it is not, @supernova, in connection.prepareStatement(), it is specifying the id columns, therefore you got generated id, not ROWIDInterposition
S
5

No elaborate on @konstantin answer: Here is a fully working example: Assuming Database is Oracle and column name which store generated Id is "GENERATED_ID" ( Can be any name). NOTE: I used NamedParameterJdbcTemplate.update(....) In this example NOT JdbcTemplate class of Spring.

       public Integer insertRecordReturnGeneratedId(final MyObject obj)
            {
            final String INSERT_QUERY = "INSERT INTO MY_TABLE  VALUES(GENERATED_ID_SEQ.NEXTVAL, :param1, :param2)";
            try
                {
                    MapSqlParameterSource parameters = new MapSqlParameterSource().addValue( "param1", obj.getField1() ).addValue( "param2",  obj.getField1() ) ;
                    final KeyHolder holder = new GeneratedKeyHolder();
                    this.namedParameterJdbcTemplate.update( INSERT_QUERY, parameters, holder, new String[] {"GENERATED_ID" } );
                    Number generatedId = holder.getKey();
                   // Note: USING holder.getKey("GENERATED_ID") IS ok TOO.
                    return generatedId.intValue();
                }
                catch( DataAccessException dataAccessException )
                {
        }
        }
Secondclass answered 6/8, 2016 at 19:20 Comment(0)
T
0

With MySQL

CREATE TABLE `vets` (
  `id` int(4) unsigned NOT NULL AUTO_INCREMENT,
  `first_name` varchar(30) DEFAULT NULL,
  `last_name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `last_name` (`last_name`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;


public @Data class Vet {
    private int id;
    private String firstname;
    private String lastname;
}

@Repository
public class VetDaoImpl implements VetDao {
/** Logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(VetDaoImpl.class);

private static final String INSERT_VET = "INSERT INTO vets (first_name, last_name) VALUES (:first_name, :last_name)";

@Autowired
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;

@Override
public Number insertVet(final Vet vet) {
    MapSqlParameterSource paramSource = new MapSqlParameterSource();
    paramSource.addValue("first_name", vet.getFirstname());
    paramSource.addValue("last_name", vet.getLastname());
    KeyHolder keyHolder = new GeneratedKeyHolder();
    int nbRecord = namedParameterJdbcTemplate.update(INSERT_VET, paramSource, keyHolder, new String[] {"id" });
    LOGGER.info("insertVet: id ["+keyHolder.getKey()+"]");
    return nbRecord;
}
}
Tie answered 22/1, 2019 at 16:38 Comment(0)
A
-4

I think you're using the wrong method on JdbcTemplate. The only one of the update methods that would seem to match your code fragment is

int update(String sql, Object... args)

If so, you're passing params and key as a two-element vargs array, and JdbcTemplate is treating key as a normal bind parameters, and mis-interpreting it.

The only public update method on JdbcTemplate that takes a KeyHolder is

int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)

So you'll need to rephrase your code to use that.

Anchoress answered 12/5, 2010 at 18:31 Comment(1)
misleading, check below answer of KonstantinInterdisciplinary

© 2022 - 2024 — McMap. All rights reserved.