Using Spring's KeyHolder with programmatically-generated primary keys - spring

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?

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.

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.

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 )
{
}
}

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

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.

Related

Spring Boot rest controller: how to return clean json

I've set this method to return a response from a Spring Boot rest controller:
public ResponseEntity<Map<String, Object>> get(#PathVariable("id") long id) {
try {
return new ResponseEntity<>(this.ReportDAO.read("dbuser1"), HttpStatus.OK);
} catch (Exception e) {
return new ResponseEntity<>(null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
And this is the DAO method:
#Autowired
private JdbcTemplate jdbcTemplate;
public Map<String, Object> read(String testParam) {
List<SqlParameter> parameters = Arrays.asList(new SqlParameter(Types.NVARCHAR));
CallableStatementCreator csc = new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con) throws SQLException {
CallableStatement cs = con.prepareCall("{call test (?)}");
cs.setString(1, testParam);
return cs;
}
};
return jdbcTemplate.call(csc, parameters);
}
I'm successfully having a json object as response but in this format:
#result-set-1: [ {…}, {…} ]
while I'm expecting to have:
[ {…}, {…} ]
Why is the resultset inserted into #result-set-1 key? How can I modify this behaviour?
JdbcTemplate#call returns Map<String, Object> You can alter this behaviour by specifically extracting key from map using key #result-set-1.
This is how i have done it:
sql
CREATE TABLE `sample_log` (
`id` bigint NOT NULL AUTO_INCREMENT,
`message` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
Insert statements:
insert into sample_log (message) values('West Country');
insert into sample_log (message) values('Welcome User');
Stored procedure:
CREATE PROCEDURE `fetch_sample_logs`(
in message_query varchar(30)
)
BEGIN
SELECT * FROM new_db.sample_log where message like message_query;
END
Controller
#RequestMapping("/logs")
#RestController
class SampleLogController {
private final JdbcTemplate jdbcTemplate;
#Autowired
SampleLogController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#GetMapping("/call")
public Object get() {
final Map<String, Object> call = jdbcTemplate.call(connection -> {
CallableStatement cs = connection.prepareCall("{call fetch_sample_logs (?)}");
cs.setString(1, "%wel%");
return cs;
}, Collections.singletonList(new SqlParameter(Types.VARCHAR)));
return Optional.of(call.getOrDefault("#result-set-1", Collections.emptyList()));
}
}
I am suggesting that you extract all the result-sets and concat them together. You could do as the other answer suggests and just get "#result-set-1" from the Map, but I would suggest at the very least converting the ResultSet to an application-represented object ("Thing" pojo) before returning from the dao method. I think that concatening the result-sets together is probably a more durable solution, unless someone can think of a reason as to why not.
#Autowired
private JdbcTemplate jdbcTemplate;
public List<Thing> read(String testParam) {
List<SqlParameter> parameters = Arrays.asList(new SqlParameter(Types.NVARCHAR));
CallableStatementCreator csc = new CallableStatementCreator() {
#Override
public CallableStatement createCallableStatement(Connection con) throws SQLException {
CallableStatement cs = con.prepareCall("{call test (?)}");
cs.setString(1, testParam);
return cs;
}
};
Map<String, Object> result = jdbcTemplate.call(csc, parameters);
return result.values().stream().map(o -> fromResultSet((ResultSet) o)
.flatMap(List::stream).collect(toList());
}
private List<Thing> fromResultSet(ResultSet resultSet) {
List<Thing> list = new ArrayList<>();
while (resultSet.next()) {
Thing thing = new Thing(resultSet.getString("resultCol1"), resultSet.getString("resultCol2")
list.add(user);
}
}
I modified some code from Resultset To List to actually parse the result set.

how to allow filter in cassandra with java datastax

I created a table and two indexes on it i am not able to do filter query from my spring application
first i created table
CREATE TABLE keyspace.favourite_setting (
id uuid,
user_name text,
filter_name text,
columns LIST<FROZEN<columns_def>>,
filters text,
PRIMARY KEY (id)
);
second i added two indexes
CREATE INDEX IF NOT EXISTS user_name_index ON keyspace.favourite_setting (user_name)
CREATE INDEX IF NOT EXISTS filter_name_index ON keyspace.favourite_setting (filter_name);
then i am trying to filter shows me error you must allow filter although i am doing it already...
My Accessory
#Accessor
public interface FavouriteSettingAccessor {
#Query("select * from favourite_setting where user_name =:userName allow filtering " )
Result<FavouriteSetting> getAllByUserName(#Param("userName") String userName );
}
my Service Impl
#Override
public List<FavouriteSetting> getAllFilterNames(String userName) throws Exception {
session = cassandraFactory.getDataSource();
favouriteSettingAccessor =
new MappingManager(session).createAccessor(FavouriteSettingAccessor.class);
return favouriteSettingAccessor.getAllByUserName(userName).all();
}
My Favourite Setting Model
#Table(keyspace = "keyspace", name = "favourite_setting")
public class FavouriteSetting {
#PartitionKey
#Column(name = "id")
private UUID id;
#Column(name = "user_name")
private String userName;
#Column(name = "filter_name")
private String filterName;
#Column(name = "columns")
private List<ColumnsDef> columns;
#Column(name = "filters")
private String filters;
public FavouriteSetting(UUID id, String userName, String filterName,
List<ColumnsDef> columns, String filters) {
this.id = id;
this.userName = userName;
this.filterName = filterName;
this.columns = columns;
this.filters = filters;
}
public FavouriteSetting() {
}
public UUID getId() {
return id;
}
public void setId(UUID id) {
this.id = id;
}
public String getFilterName() {
return filterName;
}
public void setFilterName(String filterName) {
this.filterName = filterName;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public List<ColumnsDef> getColumns() {
return columns;
}
public void setColumns(List<ColumnsDef> columns) {
this.columns = columns;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
}
Cannot execute this query as it might involve data filtering and thus may have unpredictable performance. If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING
For the query
#Query("select * from favourite_setting where user_name =:userName ... " )
userName is not part of the primary key, you can set it as a clustering key. For more information you can look here or here.
Please note that there is no alter statement to add a clustering key, you will need to recreate the table with:
CREATE TABLE keyspace.favourite_setting (
id uuid,
user_name text,
filter_name text,
columns LIST<FROZEN<columns_def>>,
filters text,
PRIMARY KEY (id, username)
The index created for the username column is considered one of the antipatterns for indexes, as it will be expected to be unique (and therefore, it will have high cardinality); you can find more information of why this is a problem here
allow filtering should never be used, as explained here you should resist the urge to just add ALLOW FILTERING to it. You should think about your data, your model and what you are trying to do.
It works Now By adding user_name As Cluster Key and ADD To The End of Query ALLOW FILTERING ..
Thanks :)

Mockito Test Case for Jdbc template and Spring's keyHolder

I wrote a test case but I get a null pointer exception at the return KeyHolder.getKey() line.
My test case looks like this:
#InjectMocks
private UserDAOImpl userDAO;
#Mock
private JdbcTemplate jdbcTemplate;
#Mock
private KeyHolderFactory keyHolderFactory;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
ReflectionTestUtils.setField(userDAO, "jdbcTemplate", jdbcTemplate);
}
#Test
public void testCreateUser() {
KeyHolder keyHolder = mock(GeneratedKeyHolder.class);
when(keyHolderFactory.newKeyHolder()).thenReturn(keyHolder);
User user = getUserInfo();
Map<String,Object> map = new HashMap<>();
map.put("id",1L);
when(keyHolder.getKeys()).thenReturn(map);
when(keyHolder.getKey()).thenReturn(1L);
when(jdbcTemplate.update(Mockito.any(PreparedStatementCreator.class), Mockito.any(KeyHolder.class))).thenReturn(1);
assertEquals(1L, userDAO.createUser(user));
}
And the method looks like this:
#Override
public long createUser(User user) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(connection -> {
PreparedStatement ps = connection
.prepareStatement("insert into user (address, first_name,last_name, email, password, phone_number, is_init, is_system_admin, created_at)"
+ " values( ?, ?, ?, ?, ?, ?, ?, ?,?)",
Statement.RETURN_GENERATED_KEYS);
ps.setString(1, user.getAddress());
ps.setString(2, user.getFirstName());
ps.setString(3, user.getLastName());
ps.setString(4, user.getEmail());
ps.setString(5, user.getPassword());
ps.setString(6, user.getPhone());
ps.setBoolean(7, user.isActive());
ps.setBoolean(8, user.isSystemAdmin());
ps.setDate(9, new Date(Date.from((user.getCreatedAt().toInstant())).getTime()));
return ps;
}, keyHolder);
return (long) keyHolder.getKey();
}
I have created an interface KeyHolderFactory:
public interface KeyHolderFactory {
KeyHolder newKeyHolder();
}
And the implementation of this interface is as follows:
public class GeneratedKeyHolderFactory implements KeyHolderFactory {
public KeyHolder newKeyHolder() {
return new GeneratedKeyHolder();
}
}
Can someone help me to find a solution?
Your issue is next :
In test class you have Mocked bin KeyHolderFactory and this is correct. Then you create mocked KeyHolder, describe it's behavior and make KeyHolderFactory return KeyHolder when it is asked. Everything looks OK except next :
On your code class method you create new KeyHolder using new GeneratedKeyHolder() and this new KeyHolder doesn't relate to those mocked instance that you have in your test class. It is absolutely different objects. That one which is in your test class - is a mock, and will follow your scenario. But that one, in your tested class method, is another object, which of course don't know what to do when you execute keyHolder.getKey().
Solution :
You have injected KeyHolderFactory in your test and well configured it's behavior. Just inject this into your test class as well and use it in your method to create KeyHolder using keyHolderFactory.newKeyHolder() instead of new GeneratedKeyHolder()

About spring Rowmapper , mapRow

I have a some questions about Spring rowmapper. I'm going to receive data from my DB using rowmapper.But my command object 'Table' only have List variable.
is Rowmapper automatically map each record to List ? is it posibble?
i know spring bind tag is automatically bind value to list.
right this.
Table.java
public class Table implements Serializable{
private List<String> tableNum = new ArrayList<String>();
// setter and getter
}
Dao
private class TableRowMapper implements RowMapper {
public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
Table table = new Table();
table.setTableNum(rs.getString("TABLE_LOCATION"));
return table;
}
}
The RowMapper is used to map a single row to a single domain object, not a bunch of rows results to a single domain object. Also the RowMapper isn't a Dao type object. It is to be used with some query method, like JdbcTemplate.query(sql,args,rowMapper)
But in your case, you don't want a RowMapper. You should instead just use a JdbcTemplate.queryForList. See the JdbcTemplate API for more query method. A simple example would be something like:
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select SOME_FIELD from SOME_TABLE where SOMETHING = ?";
#Override
public List<String> getSomeFieldBySomething(String something) {
return (List<String>)queryForList( SQL,
new Object[] { something },
String.class);
}
}
You use the dao for your services.
UPDATE
Because of your help, I can get a one column from my DB. but I got a problems. my db table is made of multiple columns. and i must receive all of them. and.. how can i do it? plz help me~!!!
You posted question in no points that out. In this case you need to make a List<DomainObject>. Not a List<String>. List<String> only allows for one value. If you have a List<DomainObject>, then the class DomainObject can have all your fields. Then that's when you use the RowMapper. And you can still use queryForList that uses the RowMapper variant
public class Table {
private String field1;
private String field2;
private String field3;
// getters and setters
}
public class YourDaoImpl extends JdbcTemplate implements YourDao {
private static final String SQL =
"select * from SOME_TABLE where SOMETHING = ?";
#Override
public List<Table> getTableBySomething(String something) {
return (List<Table>)queryForList( SQL,
new Object[] { something },
new RowMapper<Table>(){
#Override
public Table mapRow(ResultSet rs, int rowNumber) {
Table table = new Table();
table.setField1(rs.getString("feild1"));
// set the others
return table;
}
});
}
}
An aside, if I were you, I would forget the jdbc and go for an ORM framework like JPA. If you want entire domain objects, this is the way to go.

How to write a search class to accept any type of parameter?

I'm using spring mvc and I created the CRUD functionality. But I want to create a search function that will allow me to find a user by any parameter (variable) as 'userid' or 'username' or 'lastname' or 'social security number' or whatever.
My userid is an integer type.
How can I do that? What is the SQL query for that?
How can I check if the input is integer or string and then go through the database by the given parameter and search for the user?
If you are using Hibernate for data access you can easily create universal finder using criteria API:
Abstract DAO class:
public abstract class AbstractHibernateDAO<T> {
private static final String PARAM_VALUE_PARAMETER = "paramValue";
private final Class<T> clazz;
#Autowired
private SessionFactory sessionFactory;
public AbstractHibernateDAO(Class<T> clazz) {
this.clazz = clazz;
}
public T findOne(String paramName, Object paramValue) {
Session session = sessionFactory.getCurrentSession();
#SuppressWarnings("unchecked")
T fetchedObject = (T) session.createCriteria(clazz).add(Restrictions.eq(paramName, paramValue)).uniqueResult();
return fetchedObject;
}
// Other CRUD methods.
}
Concrete DAO class for entity:
#Repository
#Transactional
public class ProductHibernateDAO extends AbstractHibernateDAO<Product> {
public ProductHibernateDAO() {
super(Product.class);
}
}
Or if you prefer to use HQL instead of Criteria API you can rewrite search method as:
public T findOne(String paramName, Object paramValue) {
Session session = sessionFactory.getCurrentSession();
StringBuilder queryText = new StringBuilder();
queryText.append("from ");
queryText.append(clazz.getSimpleName());
queryText.append(" where ");
queryText.append(paramName);
queryText.append("=:");
queryText.append(PARAM_VALUE_PARAMETER);
#SuppressWarnings("unchecked")
T fetchedObject = (T) session.createQuery(queryText.toString()).setParameter(PARAM_VALUE_PARAMETER, paramValue).uniqueResult();
return fetchedObject;
}
In this article you can find very good description how to create generic DAO with hibernate (Or if you prefer JPA there are also described how to do this with JPA).
Or if you prefer to use JDBC for data access I recommend you to look at Spring's JdbcTemplate. It simplifies development a lot. Here how you can implement universal finder using JdbcTemplate:
#Repository
#Transactional
public class ProductJDBCDAO implements DAO<Product> {
private static final String TABLE_NAME = "product";
#Autowired
private JdbcTemplate jdbcTemplate;
public Product findOne(String paramName, Object paramValue) {
RowMapper<Product> rowMapper = new RowMapper<Product>(){
public Product mapRow(ResultSet rs, int rowNum) throws SQLException {
long productId = rs.getLong("product_id");
// Other properties
Product product = new Product(...);
return product;
}
};
StringBuilder queryText = new StringBuilder();
queryText.append("select * from ");
queryText.append(TABLE_NAME);
queryText.append(" where ");
queryText.append(paramName);
queryText.append("=?");
Product fetchedObject = jdbcTemplate.queryForObject(queryText.toString(), rowMapper, paramValue);
return fetchedObject;
}
// Other CRUD methods
}
Ass you can see in all examples you don't need explicitly specify parameter type, you just add it as Object parameter.
If you will work with direct JDBC in such case I recommend you to use PreparedStatement and it's setObject(..) method. Query text will be similar to shown in the example with JdbcTemplate.

Resources