How to get generated keys with commons dbutils? - java

I don't understand how to get auto generated keys with commons-dbutils?

You can use QueryRunner#insert(). Below is an example. Given a table called users, which has an auto generated primary key column and a varchar column called username, you can do something like this:
DataSource dataSource = ... // however your app normally gets a DataSource
QueryRunner queryRunner = new QueryRunner(dataSource);
String sql = "insert into users (username) values (?)";
long userId = queryRunner.insert(sql, new ScalarHandler<Long>(), "test");

As a matter of fact I think it cannot be done with the current version of common-dbutils. A few months ago, when I was working for another company, I extented the QueryRunner with my own implementation.
The request has been submitted to the DbUtils project, and there you can even find a viable implementation which I guess you could copy if you really need it.
https://issues.apache.org/jira/browse/DBUTILS-54

Related

jOOQ Mocking: Why does insert require me to prepare a MockResult for a subsequent select?

I have a jOOQ MockConnection/DSL set up for unit tests to be able to do insert, but in at least one instance of my testing I have to also implement a MockResult for a subsequent select statement.
My question is, why does jOOQ execute a select statement in org.jooq.impl.AbstractDMLQuery#executeReturningGeneratedKeysFetchAdditionalRows -> selectReturning for my insert?
My insert is a simple myRecord.insert(), and the mocked DSL looks something like this:
// Simplified
var connection = new MockConnection(ctx -> {
var sql = ctx.sql();
if (sql.startsWith("insert")) {
return mockResultCount(1); // Impl elsewhere
}
return null;
});
var dsl = DSL.using(connection, SQLDialect.MYSQL);
[...]
var myRecord = new MyRecord();
myRecord.setX(...).setY(...);
dsl.attach(myRecord);
// Why does this require a mocked insert result AND a mocked select result?
myRecord.insert();
And my test fails because jOOQ needs the DSL to return a result for a select on something like SELECT ID_COLUMN, UNIQUE_KEY_COLUMN WHERE UNIQUE_KEY_COLUMN = ?
The only thing I can think of is that this table has a unique key?
Anyone know why a simple record.insert(); requires a select statement to be executed?
This depends on a variety of factors.
First off, the dialect. MySQL, for example cannot fetch values other than the identity values via JDBC's Statement.getGeneratedKeys(). This is the main reasons why there might be an additional query at all.
Then, for example, your Settings.returnAllOnUpdatableRecord configuration might cause this behaviour. If you have turned this on, then a separate query is required in MySQL to fetch the non-identity values.
Or, if your identity column (in MySQL, the AUTO_INCREMENT column) does not coincide with your primary key, which seems to be the case given your logged SQL statement, where you distinguish between an ID_COLUMN and a UNIQUE_KEY_COLUMN.
The reason for this fetching is that jOOQ assumes that such values may be generated (e.g. by triggers). I guess that the special case where
The identity and the primary key do not coincide
The primary key has been supplied fully by the user
We can attempt not to fetch the possibly generated primary key value, and fetch only the identity value. I've created a feature request for this: https://github.com/jOOQ/jOOQ/issues/9125

Flyway Migration: NamedParameterJdbcTemplate

Is there anyway to create a flyway migration class utilizing the NamedParameterJdbcTemplate rather than the standard JdbcTemplate that comes across from the implementation of SpringJdbcMigration?
I have an upgrade I need to run where I need to convert a column type from text to integer (Replacing a string value with an internal id associated with that value.)
The way I'm doing this is temporarily storing the string values for a reverse lookup, deleting the column and re-adding it as the proper type, and then running an UPDATE call to add in the appropriate ID to the records. I have code similar to the following I want to execute as part of the migration:
String sql = "UPDATE my_table SET my_field = :my_field WHERE my_id IN (:my_ids)";
MapSqlParameterSource source = new MapSqlParameterSource();
source.addValue("my_field", someIntValue); // the internal id of the string I want to use.
source.addValue("my_ids", someListOfPKIds); // List of PK ids.
namedTemplate.update(sql,source); //namedTemplate is a NamedParameterJdbcTemplate
However, it seems as if I can't take advantage of the NamedParameterJdbcTemplate. Am I incorrect in this?
According to Flyway sources they create a new JdbcTemplate in SpringJdbcMigrationExecutor
However you can try creating a new NamedParameterJdbcTemplate in your migration given the classic JdbcTemplate. Check this constructor. E.g. new NamedParameterJdbcTemplate(jdbcTemplate)

Java DB Database, check for all table names

I've got an embedded Derby Database in my java application, and have multiple table's (that are created and deleted, so nothing is set in stone). I wanted to be able to return a list of names of all the tables currently in the database as I have to display the list in the application as well as get all the information from them.
Easiest way to do so? I don't need code just a method or methods. I'm a terrible google-fu user.
Currently my code works by grabbing a ResultSet from a specific table name entered, but it's only for testing purposes and I need to be able to display the full list of tables I have.
EDIT: My current workaround is actually different than posted. I simply have another table that holds all the table names created and updates when one is created/deleted. Obviously not the best approach but it works for me right now.
DatabaseMetaData metaData = connection.getMetaData();
ResultSet resultSet = metaData.getTables(null, "schenaName", "%" ,new String[] {"TABLE"} );
while (resultSet.next()) {
System.out.println(resultSet.getString(3));
}
Adding new answer:
Connection connection = getDBConnection();
DatabaseMetaData dbMetaData = connection.getMetaData();
//getting catalogs for mysql DB, if it is not working for your DB, try dbMetaData.getSchemas();
ResultSet catalogs = dbMetaData.getCatalogs();
while(catalogs.next()){
String catalogName = catalogs.getString(1);
//excluding table names from "mysql" schema from mysql DB.
if(!"mysql".equalsIgnoreCase(catalogName)){
ResultSet tables = dbMetaData.getTables(catalogName, null, null, null);
while(tables.next()){
System.out.println(catalogName + "::"+tables.getString(3));
}
}
}
Using metadata is the (somewhat) more portable solution. Note that you don't need the catalog stuff with Derby, as there are no catalogs. You can issue dmd.getTables(...) directly with null for the catalog. If all the tables you track are in a single schema, (and there aren't any other tables in that schema), getTables(null, "schemaName", null, null) should do the trick.
If need more fancy querying and you're not concerned about portability, you can check out
the dataBaseMetaData tool which gives you access to metadata as tables so that you can perform joins and other sophisticated queries on them.
Try this:
select tableName from sys.systables
You should get all the tables your system.

How to get Auto Generated ID from SQL Table when multiple users working simultaneously

Am creating a SQL Database for multiple users(Roughly 100 user), each records having nearly 15 fields in it.. In which the ID field is auto incremented...
Whenever a person Inserting a record to the database, it has to show "auto incremented ID" for that particular person, For this am using this code
PreparedStatement ppstmt = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
ppstmt.execute(sql,PreparedStatement.RETURN_GENERATED_KEYS);
ResultSet rs = ppstmt.getGeneratedKeys();
long key = 0;
if (rs != null && rs.next()) {
key = rs.getLong(1);
}
As of now its working fine but my doubt is when multiple users inserting the record at the same time, whether it will corresponding auto generated ID to each person..?
The statement will work correctly. The generated key returned will be the key generated by that execution of that statement for that user. These are SQL-defined semantics. Any implementation where it didn't work would be broken.
NB the result set cannot be null at the point you're testing it.
You have tagged oracle, so here is oracle's documentation on how retrieving generated keys works. A key piece of information is:
Auto-generated keys are implemented using the DML returning clause.
So it is worth looking at the documentation on how the returning clause works.
As you can see, this is guaranteed to return only data relevant to the just executed statement.
I would also like to point out that your use of a PreparedStatement is wrong. Once you have created the PreparedStatement from
PreparedStatement ppstmt = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
The next call should be to ppstmt.execute followed by ppstmt.getGeneratedKeys.

Return Timestamp With Prepared Statement

I have an auto generated timestamp that is created each time a record is inserted or updated in a mysql table. Is there a way to return this timestamp in a way similar to how I would use a keyholder to return a newly created id?
KeyHolder keyHolder = new GeneratedKeyHolder();
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
//Insert Contact
jdbcTemplate.update(new PreparedStatementCreator() {
#Override
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(SQL_ADD, Statement.RETURN_GENERATED_KEYS);
preparedStatement.setString(1, contact.getFirstName());
preparedStatement.setString(2, contact.getLastName());
preparedStatement.setInt(3, contact.getOrganizationId());
preparedStatement.setString(4, contact.getType());
preparedStatement.setInt(5, contact.getUserId());
return preparedStatement;
}
}, keyHolder);
//Use keyholder to obtain newly created id
contact.setId(keyHolder.getKey().intValue());
Is there some way to also return the new timestamp without having to requery the table? I have been looking for ways to return it along with the id as a key in the keyholder, but it doesn't seem to be returned as a key?
Not very satisfying, but I think "no" is the answer to your question. I don't know any of the Spring stuff, but I think this is due to the basic JDBC that it's wrapping. See http://docs.oracle.com/javase/6/docs/api/java/sql/Statement.html#getGeneratedKeys%28%29
You only option would be to create a stored procedure on MySQL that has an out parameter and call that. See http://dev.mysql.com/doc/refman/5.0/en/call.html.
There are few options for solving this issue on the MySQL database server side. You could start with creating a TRIGGER on the table. As TRIGGER has a restriction and cannot return data, you can set the TIMESTAMP value to a variable:
DEMILITER //
CREATE TRIGGER ai_tbl_name AFTER INSERT ON tbl_name
FOR EACH ROW
BEGIN
SET #TimeStamp = NEW.timestamp_column;
END;//
DELIMITER ;
To retrieve this timestamp value, run the following command:
SELECT #TimeStamp;
Since the variables are stored in the memory, there will be no need to open any tables again.
You go even further. You could create a STORED PROCEDURE in MySQL to automate all the above (sample code, as I do not know your table's details):
DELIMITER //
DROP PROCEDURE IF EXISTS sp_procedure_name //
CREATE PROCEDURE sp_procedure_name (IN col1_val VARCHAR(25),
IN col2_val VARCHAR(25),
IN col3_val INT)
BEGIN
INSERT INTO tbl_name (col1, col2, col3)
VALUES (col1_val, col2_val, col3_val);
SELECT #TimeStamp;
END; //
DELIMITER ;
You can run this procedure with the following code:
CALL sp_procedure_name(col1_val, col2_val, col3_val);
As I'm not familiar with the Java, you'll need to finish it up with your side of code.
It seems that the variable contact is an instance for the newly inserted record. As it contains the newly generated id (primary key) field value, you can execute a new query to return the required timestamp field value for this new id.
The query may look like this:
select timestamp_field from my_table where id=?
Use PreparedStatement to input new id value and execute it to fetch required timestamp field value.
GeneratedKeyHolder also has two methods: getKeyList() that returns Map<String,Object> of generated fields; and getKeyList() that produces a list of generated keys for all affected rows.
See java doc of GeneratedKeyHolder and Spring tutorial of auto generated keys
In addition Spring's SimpleJdbcInsert has methods for generated key retrieval. See also method SimpleJdbcInsert#usingGeneratedKeyColumns
There are 2 methods in java.sql.Connection class causing PreparedStatement execution to return selected key columns :
PreparedStatement prepareStatement(String sql,
int[] columnIndexes)
throws SQLException
PreparedStatement prepareStatement(String sql,
String[] columnNames)
throws SQLException
You don't need to use Spring KeyHolder & JDBCTemplate to do this.
The give hope you could number/name your timestamp column. But the javadoc doesn't require or suggest that any JDBC implementation can return non-key columns, so your out of luck with this approach:
Creates a default PreparedStatement object capable of returning the auto-generated keys
designated by the given array. This array contains the names of the columns in the target
table that contain the auto-generated keys that should be returned.
As suggested in another answer, can switch to a stored procedure that does exactly what you want (CallableStatement is actually a PreparedStatement that executes storedprocedures - i.e. a subclass).
Can populate the timestamp column within the prepared statement via new Timestamp(new Date()) - but you should have in place a mechanism to sync times across your various servers (which is often used in windows and *nix environments). Your trigger could set the timestamp only if a value wasn't already provided.
As part of your app & DB design, you need to commit to a philosophy of where certain operations occur. If the DB derives needed data, the app needs to refresh data - you must pay the price of separate query executions or a combined stored proc that inserts & retrieves.

Categories

Resources