How to execute multi batch delete in JdbcTemplate? - java

I want to delete multiple database entries at once. Each entry should only be deleted if 3 fields match (here: name, email, age).
If I'd just wanted to delete by a single property, I'd go for:
String sql = "DELETE FROM persons WHERE (email) IN (?)";
JdbcTemplate template;
template.execute(sql, Arrays.asList(emails...));
But what if my condition is formed by multiple fields?
String sql = "DELETE FROM persons WHERE (name, email, age) IN (?, ?, ?)";
JdbcTemplate template;
template.execute(sql, ...); ???
The condition should always match all 3 fields (AND)!

Use the batchUpdate(sql, batchArgs, argTypes) method.
String sql = "DELETE FROM persons WHERE name = ? AND email = ? AND age = ?";
int[] argTypes = { Types.VARCHAR, Types.VARCHAR, Types.INTEGER };
List<Object[]> batchArgs = new ArrayList<>();
batchArgs.add(new Object[] { "John Doe", "john#example.com", 42 });
batchArgs.add(new Object[] { "Jane Smith", "jane#example.com", 47 });
. . .
JdbcTemplate template = ...;
int[] rowCounts = template.batchUpdate(sql, batchArgs, argTypes);

A batchUpdate is what you are looking for here. You would need to change/tweak your query a little bit though.
If you can pass a list of objects (you must match the class members with the values on the SQL query), it can be done automatically:
private final NamedParameterJdbcTemplate namedParameterJdbcTemplate;
// class People { String name; String email; Integer age; }
final String sql = "DELETE FROM persons WHERE name = :name AND email = :email AND age = :age";
final SqlParameterSource[] batchArgs = SqlParameterSourceUtils.createBatch(people.toArray()); // List<People>
final int[] results = namedParameterJdbcTemplate.batchUpdate(sql, batchArgs);
logger.debug("{} record(s) inserted successfully", results.length);
The other approach would be what #Andreas proposed.
I would also recommend to use, always, parameterized queries: DELETE FROM persons WHERE name = :name AND email = :email AND age = :age.

Related

NamedParameterJdbcTemplate substituing values with single quotes

#Autowired
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
public List<Contact> findByPhoneWithNamedParameters(String phone) {
MapSqlParameterSource namedParameters = new
MapSqlParameterSource().addValue("phone", phone);
String sqlQ = getSqlQuery(namedParameters);
namedParameters.addValue("anotherCondition", sqlQ);
String sql = "select * from Contact c where c.phone=:phone
:anotherCondition";
return namedParameterJdbcTemplate.query(sql, namedParameters, new ContactRowMapper());
}
private String getSqlQuery(MapSqlParameterSource namedParameters) {
return " and c.name='Saman'";
}
The above one is generating the query like below:
select * from Contact c where c.phone='09137390432' ' and c.name=''Saman'''
getSqlQuery() return value is embeddigng within single quotes, with that query is not working as expected.
I tried to concatenate the value directyle instead of namedParams;
But in my case, I have to avoid the SQL Injection.
How to resolve this?
You should only use named parameters with the actual values you want to pass to the query, not with whole SQL sub-parts. Otherwise it will escape also the valid SQL quotes.
So you should change your code to something like this:
private String getAdditionalSqlConditions(MapSqlParameterSource namedParameters) {
namedParameters.add("name", "Saman");
return "c.name = :name";
}
public List<Contact> findByPhoneWithNamedParameters(String phone) {
MapSqlParameterSource namedParameters = new MapSqlParameterSource().addValue("phone", phone);
String conditions = getAdditionalSqlConditions(namedParameters);
String sql = "select * from Contact c where c.phone=:phone and " + conditions);
return namedParameterJdbcTemplate.query(sql, namedParameters, new ContactRowMapper());
}

invalid relational operator after 32766 records when using NamedParameterJdbcTemplate.query with tuple

I am trying to execute the below query with IN clause and passing tuples of number 32767 or more using NamedParameterJdbcTemplate.query method. But, i am getting below error:
In DataAccessException:PreparedStatementCallback; bad SQL grammar [SELECT ID, STATUS FROM ID_DETAILS WHERE (ID, 1) IN ((?, ?), (?, ?), (?, ?), (?, ?)..32767 times) ORDER BY ID, EL_SEQ ASC]; nested exception is java.sql.SQLSyntaxErrorException: ORA-00920: invalid relational operator
This error is NOT thrown when the number of IDs passed is less than or equal to 32766. Can someone help in this regard
Below is the code used:
List<Object[]> ids = getToupleOf(idList); //idList has 32767 or more Long values
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("IDLIST", ids);
NamedParameterJdbcTemplate namedParameterJdbcTemplate = new
NamedParameterJdbcTemplate(
getJdbcTemplate().getDataSource());
return namedParameterJdbcTemplate.query(SQLConstants.REFRESH_QUERY,
parameters,
new ResultSetExtractor<List<ResponseVO>>() {
#Override
public List<ResponseVO> extractData(ResultSet rs)
throws SQLException, DataAccessException {
List<ResponseVO> retList = new LinkedList<ResponseVO>();
while (rs.next()) {
ResponseVO eachResponse = constructResponseVOFromRS(rs);
retList.add(eachResponse);
}
return retList;
}
});
Below is the code for getToupleOf():
private List<Object[]> getToupleOf(List<Long> idSet){
List<Object[]> list = new LinkedList<Object[]>();
idSet.forEach(l->{
Object[] touple = new Object[2];
touple[0] = l.longValue();
touple[1] = 1;
list.add(touple);
});
return list;
}
SQLConstants.REFRESH_QUERY we are passing is given below:
public static final String REFRESH_QUERY = "SELECT ID, STATUS FROM ID_DETAILS WHERE (ID, 1) IN (:IDLIST) ORDER BY ID, EL_SEQ ASC";

How can i add column names to the ResultSet. I use Java with the EntityManager

Is it possible to read out the columnnames?
My Application: Java, SpringBoot, JPA, Hibernate
My code:
Query query = entityManager.createNativeQuery(
"select name as Name, concat('Time:',time, ' AND:', event) as FOO
from XYZ
where NAME = 'BLA'");
List<Object[]> resultList = query.getResultList();
I need the columnnames (Name and FOO) in the first Object[] of the ResultSet or
somewhere else...
My unsightly solution:
private String getColumnNames(String sql) {
StringBuilder sb = new StringBuilder();
String part1 = sql.split("from")[0];
part1 = part1.replaceAll("\\(.*?\\)", "");
String[] columns = part1.split(",");
for (String column : columns) {
String alias = column;
if (column.toUpperCase().contains(" AS ")) {
alias = column.toUpperCase().split(" AS ")[1];
}
alias = alias.replace("'","");
sb.append(alias+",");
}
String columnNames = sb.toString();
if (columnNames.length() != 0) {
columnNames = columnNames.substring(0, columnNames.length()-1);
}
return columnNames;
}
Thanks
You should use other overloaded versions of createNativeQuery() :
public Query createNativeQuery(String sqlString, Class resultClass);
or
public Query createNativeQuery(String sqlString, String resultSetMapping);
In this way, you could map the result of the query to a class that contains name and foo fields.
For example with the first way :
Query query = entityManager.createNativeQuery(
"select name as Name, concat('Time:',time, ' AND:', event) as FOO
from XYZ
where NAME = 'BLA'", Xyz.class);
List<Xyz> resultList = query.getResultList();
Note that query.getResultList() will return a raw type.
What you should do here is to:
First create an entity with Name and FOO attributes, so you can map it as a result of query.getResultList().
Then map it with a List<YourEntity> instead of List<Object[].
And Last thing is to define a mapper for the request so it can map those results to the desired entity, if the query uses different attributes names other than the ones in the entity, using #SqlResultSetMapping.
You can check this Hibernate Tips: How to map native query results to entities tutorial for further details.
Edit:
If you can't create a new Entity to map the results, you can map those results in a HashMap in each element of the Object[] like this:
Map results = new HashMap();
results.put("Name", obj[0]);
results.put("Foo", obj[1]);
Yes it is possible. Lets assume your entity name is XYZ and you Repository would be XYZRepository, Lets create a DTO which only contains name and eventDetais since we want to extract only these 2 details from table.
public class XyzDTO {
String name;
String eventDetails;
public XyzDTO() {
}
public XyzDTO(String name, String eventDetails) {
this.name = name;
this.eventDetails =eventDetails;
}
}
This code would be written in XYZRepository.
#Query(nativeQuery = true)
List<Details> getList();
This code would be written in XYZ entity.
#SqlResultSetMapping(name = "getList",
classes = #ConstructorResult(targetClass = XyzDTO.class,
columns = {#ColumnResult(name = "name", type = String.class),
#ColumnResult(name = "eventDetails", type = String.class)}))
#NamedNativeQuery(name = "XYZ.getList",
query = "select name as name, concat('Time:',time, ' AND:', event) as eventDetails from XYZ where name = 'BL")
public class XYZ implements Serializable {
}

Using Spring JdbcTemplate to extract one string

Can't seem to find a way to get one string from table using JdbcTemplate query.
This is the table my sql returns:
ID | STREET_NAME
------------------------
1 | Elm street
Now how am I supposed to get the value of STREET_NAME. SQL always returns one row, so no need to worry about returning more than one row.
For some background info:
INNER JOIN and COUNT in the same query
Using Tony Stark answer to get my table.
But how can I extract "Elm street" from it using JdbcTemplate?
It would help a lot to know what your SQL query looks like, but assuming it's something like SELECT STREET_NAME FROM table WHERE ID=1;
CODE:
public String getStreetNameById(int id) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "SELECT STREET_NAME FROM table WHERE ID=?";
String streetName = (String) jdbcTemplate.queryForObject(
sql, new Object[] { id }, String.class);
return streetName;
}
The class JdbcTemplate implements JdbcOperations.
If you look at the queryForObject javadocs in JdbcOperations it states:
Deprecated. as of 5.3, in favor of queryForObject(String, Class, Object...)
Basically, they have changed the method signature to get rid of Object[] arguments in the method signatures in favor of varargs. Please, see the relevant Github issue.
You can rewrite answer of #jlewkovich with the new method signature like this:
public String getStreetNameById(int id) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "SELECT STREET_NAME FROM table WHERE ID=?";
String streetName = (String) jdbcTemplate.queryForObject(
sql, String.class, id);
return streetName;
}
If you want to get only one column "string" from your table (or any query with joins), you have to say the name of the column.
Using SELECT * FROM TABLE is a very-very bad practice by the way. I bet you did this.
#JLewkovich modified code:
public String getStreetNameById(int id) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "SELECT STREET_NAME FROM table WHERE ID=?";
String streetName = (String) jdbcTemplate.queryForObject(
sql, new Object[] { id }, String.class);
return streetName;
}
But what if there is 0 or more than one result?
Think about it!
But to getting a sequence value (in Oracle), this should work.
public Long getSequence() {
Long seq;
String sql = "select SEQ_XY.NEXTVAL from dual";
seq = jdbcTemplateObject.queryForObject(sql, new Object[] {}, Long.class);
return seq;
}
As per latest specification queryForObject with below syntax is now deprecated
<T> T queryForObject(String sql, Object[] args, Class<T> requiredType)
New method uses varargs.
<T> T queryForObject(String sql, Class<T> requiredType, Object... args)
Updated Solution: We have to replace the class type with Object args and vice-versa.
Sql Query: SELECT STREET_NAME FROM table WHERE ID=1;
public String getStreetNameById(int id) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
String sql = "SELECT STREET_NAME FROM table WHERE ID=?";
String streetName = (String) jdbcTemplate.queryForObject(
sql, String.class, new Object[] { id });
return streetName;
}
I usually do this way:
String result = DataAccessUtils.singleResult(
jdbcTemplate.queryForList(
"SELECT street_name FROM table WHERE id = :id",
Collections.singletonMap("id", id),
String.class
)
)
queryForList is used instead of queryForObject for handling empty results. queryForObject throws EmptyResultDataAccessException for empty results. Often it is not desired behavior.
DataAccessUtils.singleResult + queryForList:
returns null for empty result
returns single result if exactly 1 row found
throws exception if more then 1 rows found. Should not happen for primary key / unique index search
DataAccessUtils.singleResult

Mysql Stored procedure with java String Array

I need a stored procedure to compare mobile number list in my database table.
Example my table contain 100 rows,I pass java String array to stored procedure to get matching row values return.
The array contain multiple mobile numbers.
If any mobile numbers matched in my table mobile number column value it return row values.
Any possibilities available ?
If available please help me..
I have other solution it's working in spring Jdbc with mysql.
private static final String GET_MATCHING_MOBILE_NUMBERS = "SELECT USER_ID, USER_NAME, REGISTRATION_ID, IMEI_CODE, MOBILE_NUMBER FROM USER WHERE MOBILE_NUMBER IN (";
#Override
#Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public List<UserDO> getMatchingExistingUsers(final String[] mobileNumbers) throws UserDataException {
JdbcTemplate jd = this.getJdbctemplate();
Object[] params = mobileNumbers;
List<UserDO> userDOs = jd.query(getSqlQuery(GET_MATCHING_MOBILE_NUMBERS, mobileNumbers), params, new BeanPropertyRowMapper<UserDO>(UserDO.class));
return userDOs;
}
private String getSqlQuery(String query_string, String[] array_values) {
StringBuilder query_builder = new StringBuilder();
query_builder.append(query_string);
for (#SuppressWarnings("unused")
String values : array_values) {
query_builder.append("?,");
}
query_builder.append("*");
String formated_query = query_builder.toString().replace(",*", ")");
return formated_query;
}
I want Mysql stored procedure if possible for above code..
Advance thanks..
Select query is
private static final String GET_MATCHING_MOBILE_NUMBERS = "SELECT USER_ID, USER_NAME, REGISTRATION_ID, IMEI_CODE, MOBILE_NUMBER FROM USER WHERE FIND_IN_SET(MOBILE_NUMBER,?);";
Jdbc code is
List<UserDO> userDOs = jd.query(GET_MATCHING_MOBILE_NUMBERS, new Object[] { formatedSqlText }, new BeanPropertyRowMapper<UserDO>(UserDO.class));
It's working fine without using String Builder and Stored procedure.

Categories