I have the RAW(16) PK column in Oracle, and trying to insert into it using JDBC:
PreparedStatement stmt = connection.prepareStatement("insert into COUNTRY (id, state, version, code, name, nationality, issuing_entity, country) values (?, ?, ?, ?, ?, ?, ?, ?)");
UUID id = UUID.randomUUID();
stmt.setObject(1, id, Types.BINARY);
However, I am getting an exception:
java.sql.SQLException: Invalid column type
at oracle.jdbc.driver.OraclePreparedStatement.setObjectCritical(OraclePreparedStatement.java:8494)
at oracle.jdbc.driver.OraclePreparedStatement.setObjectInternal(OraclePreparedStatement.java:7995)
at oracle.jdbc.driver.OraclePreparedStatement.setObject(OraclePreparedStatement.java:8559)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.setObject(OraclePreparedStatementWrapper.java:225)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.setObject(HikariProxyPreparedStatement.java)
at rw.gov.dgie.framework.test.AbstractTestCaseWithDB.tryToInsertCountry(AbstractTestCaseWithDB.java:78)
at rw.gov.dgie.framework.test.AbstractTestCaseWithDB.dbSetup(AbstractTestCaseWithDB.java:62)
at test.rw.gov.dgie.bms.terr.service.TestCountryService.init(TestCountryService.java:37)
I am getting the same exception when trying to use DbSetup for inserting test data.
Is there a way to make JDBC insert UUIDs into RAW(16) column?
I am using Oracle JDBC 12.2.0.1.0.
You must convert the UUID to a byte array. See the method asBytes how to do it.
After it the binding is a s simple as using setBytes.
Example
def stmt = con.prepareStatement("insert into TAB_UUID (id, uuid) values (?,?)")
// bind
stmt.setInt(1,1)
def uuid = UUID.randomUUID()
stmt.setBytes(2,asBytes(uuid))
def rowCount = stmt.executeUpdate()
Here just for case the link doesn't work the conversion method UUID to byte array
public static byte[] asBytes(UUID uuid) {
ByteBuffer bb = ByteBuffer.wrap(new byte[16]);
bb.putLong(uuid.getMostSignificantBits());
bb.putLong(uuid.getLeastSignificantBits());
return bb.array();
}
Oracle has no real UUID datatype and dealing with RAW(16) is really a PITA.
What we do, is to pass the UUID as a string to a SQL statement that uses hextoraw():
String sql = "insert into foo (id) values (hextoraw(?))";
PreparedStatement pstmt = connection.prepareStatement(sql);
UUID uid = UUID.randomUUID();
pstmt.setString(1, uid.toString().replaceAll("-", ""));
getJdbcTemplate().update("INSERT INTO abc(abc_id, abc_uuid, "
+ "VALUES (?, ?)",
abcId, uuidToBytes(abcUuid))
Here's a helper method to converet UUID type to bytes.
private byte[] uuidToBytes(final UUID uuid) {
if (Objects.isNull(uuid)) {
return null;
}
final byte[] uuidAsBytes = new byte[16];
ByteBuffer.wrap(uuidAsBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return uuidAsBytes;
}
JdbcTemplate provides different methods for performing a DML operations like insert. Please, consider for instance update.
#MarmiteBomber provides in his/her answer all the necessary information for performing what you need, please, only wrap the code appropriately in the different artifacts defined by Spring.
For example, you can use PreparedStatementCreator, something like:
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement ps = con.prepareStatement("insert into TAB_UUID (id, uuid) values (?,?)");
ps.setInt(1,1);
UUID uuid = UUID.randomUUID();
ps.setBytes(2,asBytes(uuid));
return ps;
}
});
The code can be simplified using lambdas to:
jdbcTemplate.update(con -> {
PreparedStatement ps = con.prepareStatement("insert into TAB_UUID (id, uuid) values (?,?)");
ps.setInt(1,1);
UUID uuid = UUID.randomUUID();
ps.setBytes(2,asBytes(uuid));
return ps;
});
If you prefer, you can use PreparedStatementSetter instead:
jdbcTemplate.update("insert into TAB_UUID (id, uuid) values (?,?)", new PreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setInt(1,1);
UUID uuid = UUID.randomUUID();
ps.setBytes(2, asBytes(uuid));
}
});
Again, the code can be simplified with lambdas:
jdbcTemplate.update("insert into TAB_UUID (id, uuid) values (?,?)", ps -> {
ps.setInt(1,1);
UUID uuid = UUID.randomUUID();
ps.setBytes(2, asBytes(uuid));
});
In both examples you explicitly invoke setBytes in the underlying prepared statement and use the asBytes method from the Marmite answer.
Related
Is there any possibility to specify the sequence generator as a value for a column in SimpleJdbcInsert, JdbcTemplate, NamedParameterJdbcTemplate or any other class for batch execution?
e.g., I want to achieve the SQL to be generated by any of the above classes as:
INSERT INTO SOME_TABLE_NAME (ID, COLUMN_A,...) VALUES (SOME_SEQUENCE.NEXTVAL, 'value for column A', ...);
A sample code snippet is like:
import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
List<SomeTableEntity> listOfEntities; // received from method parameter
SimpleJdbcInsert sql = new SimpleJdbcInsert(dataSource).withTableName("SOME_TABLE_NAME");
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(listOfEntities.toArray());
sql.executeBatch(batch);
I tried to trick the SimpleJdbcInsert as:
SqlParameterSource id = new MapSqlParameterSource("ID", "SOME_SEQUENCE.nextval"));
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(listOfEntities.toArray());
List<SqlParameterSource> params = new ArrayList<>(batch.length + 1);
params.add(id);
for (SqlParameterSource param : batch)
{
params.add(param);
}
sql.executeBatch(params.toArray(new SqlParameterSource[] {}));
But not to a surprise, that didn't worked since the ID column is of type numeric and it tried to fill the value as "SOME_SEQUENCE.nextval" instead of evaluating the result of SOME_SEQUENCE.nextval.
PS: There are too many columns in the table and due to that I do not want to use a prepared statement solution
String sql = "INSERT INTO USER
(USER_PK, ACCOUNTNUMBER, FIRSTNAME, LASTNAME, EMAIL )
VALUES
(user.nextval, ?, ?, ?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, accountNumber);
ps.setString(2, firstName);
ps.setString(3, lastName);
ps.setString(4, email);
I have to access the automatically generated data (id, created, last_modified ...) when inserting data lists. Because the lists may be large, I use statement.executeBatch() to add everything in a package. However, this way I lose the opportunity to take advantage of the returning statement.
I am currently doing the following to get the data:
public boolean store(Connection connection, List<WorkPlace> list) throws SQLException {
String query =
"insert into work_places (merchant_id, name, description) values (?, ?, ?)";
try(PreparedStatement statement = connection.prepareStatement(query, Statement.RETURN_GENERATED_KEYS)) {
for(WorkPlace workPlace: list) {
statement.setLong(1, workPlace.getMerchantId());
statement.setString(2, workPlace.getName());
statement.setString(3, workPlace.getDescription());
statement.addBatch();
}
statement.executeBatch();
try(ResultSet rs = statement.getGeneratedKeys()) {
List<Long> ids = new ArrayList<>();
while (rs.next()) {
ids.add(rs.getLong(1));
}
query =
"select * from work_places where id = any (?)";
try(PreparedStatement statement1 = connection.prepareStatement(query)) {
statement1.setArray(1, connection.createArrayOf("integer", ids.toArray()));
try(ResultSet rs1 = statement1.executeQuery()) {
list.clear();
while (rs1.next()) {
list.add(getWorkPlace(rs1));
}
}
}
}
}
return true;
}
Are you interested, is there a better way to achieve what I need?
The generated keys implementation in the PostgreSQL JDBC driver uses RETURNING *, which will return all columns from the table. So, if you can retrieve the generated id this way after executing a batch, then you should also be able to retrieve the other columns from the same getGeneratedKeys result set.
Why code from "Spring in action 5" don't work (keyHolder.getKey() return null, but entity is saved in DB)?
private long savePizzaInfo(Pizza pizza) {
pizza.setCreatedAt(new Date());
PreparedStatementCreator psc =
new PreparedStatementCreatorFactory(
"insert into PIZZA (name, createdAt) values (?, ?)",
Types.VARCHAR, Types.TIMESTAMP
).newPreparedStatementCreator(
Arrays.asList(
pizza.getName(),
new Timestamp(pizza.getCreatedAt().getTime())));
KeyHolder keyHolder = new GeneratedKeyHolder();
template.update(psc, keyHolder);
return keyHolder.getKey().longValue();
}
My DB Table:
CREATE TABLE PIZZA
(
ID bigint DEFAULT (NEXT VALUE FOR
PUBLIC.SYSTEM_SEQUENCE_12CA966F_4FFD_469C_BA69_80BB93916EF3) AUTO_INCREMENT
PRIMARY KEY NOT NULL,
NAME varchar(50) NOT NULL,
CREATEDAT timestamp NOT NULL
);
CREATE UNIQUE INDEX PRIMARY_KEY_4 ON PIZZA (ID);
You have to instruct PreparedStatementCreatorFactory instance to return the generated keys:
PreparedStatementCreatorFactory preparedStatementCreatorFactory = new PreparedStatementCreatorFactory(
"insert into PIZZA (name, createdAt) values (?, ?)",
Types.VARCHAR, Types.TIMESTAMP
);
// By default, returnGeneratedKeys = false so change it to true
preparedStatementCreatorFactory.setReturnGeneratedKeys(true);
PreparedStatementCreator psc =
preparedStatementCreatorFactory.newPreparedStatementCreator(
Arrays.asList(
pizza.getName(),
new Timestamp(pizza.getCreatedAt().getTime())));
You need to specify your prepared statement
notice the Statement.RETURN_GENERATED_KEYS in the code bellow
you need something like this
final PreparedStatementCreator psc = new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(final Connection connection) throws SQLException {
final PreparedStatement ps = connection.prepareStatement("INSERT INTO `names` (`name`) VALUES (?)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, name);
return ps;
}
};
Why not use SimpleJdbcInsert instead?
dbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("TABLE_NAME").usingGeneratedKeyColumns(
"Primary_key");
Map<String, Object> parameters = new HashMap<>();
parameters.put("Column_NAME1", bean.getval1());
parameters.put("Column_NAME2", bean.getval2());
// execute insert
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(
parameters));
// convert Number to Int using ((Number) key).intValue()
return ((Number) key).intValue();
I have an EMPLOYEE table that has 4 fields; ID, NAME, AGE, SALARY. ID is unique and auto-increment.
Below is the code to insert a row in the table, using Spring's JDBCTemplate. Please suggest, how can I auto increment ID field.
String sql = "insert into employee values (?,?,?,?)"
jdbcTemplate.update( sql, ID, bean.getName(), bean.getAge(), bean.getSalary())
I see, you tag your question Oracle, use Oracle sequence then.
String sql = "insert into Employee values (id_seq.nextval, ?, ?, ?)";
jdbcTemplate.update(sql, bean.getName(), bean.getAge(), bean.getSalary());
Ref: How to create Sequence in Oracle.
Just add following code to your domain:
Ref: http://docs.spring.io/spring/docs/2.5.x/reference/jdbc.html#jdbc-auto-genereted-keys
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
final String INSERT_SQL = "insert into my_test (name) values(?)";
final String name = "Rob";
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(
new PreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement ps =
connection.prepareStatement(INSERT_SQL, new String[] {"id"});
ps.setString(1, name);
return ps;
}
},
keyHolder);
I am trying to write a method to have an UPSERT functionality with a prepared statement in java. The code looks as follows;
public boolean addUserDeviceToken(String userid, String password, String deviceToken, Connection connection) {
String addDeviceToken = "INSERT INTO swiped.Users (userid, password, deviceToken) VALUES( ?, ?, ?) ON DUPLICATE KEY UPDATE devicetoken = ?";
boolean result = false;
ResultSet rs = null;
PreparedStatement st = null;
try {
st = connection.prepareStatement(addDeviceToken);
st.setString(1, userid);
st.setString(2, password);
st.setString(3, deviceToken);
st.setString(4, deviceToken);
What I am unsure of is whether i use st.executeQuery(); or st.executeUpdate(); as surely it depends on the condition of the duplicate key?
What is the correct approach
thanks
You don't want to get a resultSet, there's no result apart the number of insertions or updates, simply use executeUpdate.
Extract from the javadoc :
Executes the given SQL statement, which may be an INSERT, UPDATE, or DELETE statement