Is it possible to execute stored procedure using Spring JDBC API in batch mode?
I tried to use Spring JdbcTemplate's method batchUpdate and it doesn't work.
I use Spring 3.0 and Oracle JDBC driver version 11.2.0.3
JdbcTemplate template = new JdbcTemplate(dataSource);
template.batchUpdate("BEGIN SCHEMA.PERSON_UPDATE(I_N_PERSON_ID=> ? ,I_S_NAME=> ? ,J_N_TID=> ?);END;",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
OracleCallableStatement cs = (OracleCallableStatement) ps;
cs.setString(1, persons.get(i).getName());
cs.setLong(2, persons.get(i).getPersonId());
cs.registerOutParameter(3, OracleTypes.NUMBER);
}
public int getBatchSize() {
return persons.size();
}}
);
Thanks
There is a mistake in implementation of the method setValues. You create instance of OracleCallableStatement but you have to register parameters in PreparedStatement which given in method argement like ps
JdbcTemplate template = new JdbcTemplate(dataSource);
template.batchUpdate("{call SCHEMA.PERSON_UPDATE(?, ?)}",
new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setString(1, persons.get(i).getName());
ps.setLong(2, persons.get(i).getPersonId());
}
public int getBatchSize() {
return persons.size();
}
}
);
But there is a problem. PreparedStatement doesn't work with out parameters.
Related
I am trying to run a SQL Server 2014 stored procedure from Java (Spring) code and get some xml results.
When I run this in a SQL client e.g. RazorSQL I get a bunch of xmls (which is expected because the there are multiple stored procedures within it, that returns those xml).
Here is the Exec call from my SQL client:
EXEC [dbo].[sp_GetType]
#TRAN_ID = 42
#QUAL_ID = 0
GetType does a RETURN 0 at the end (so basically if all steps succeed, it returns 0)
This opens multiple tabs in my client with the xmls.
And one example stored procedure within GetType has these lines:
SELECT TOP 1 ModifiedBy = CASE WHEN #IS_ID = 1 FROM TABLE23.dbo.TRX
TrxId WITH (NOLOCK) WHERE #DD_ID = #TRAN_ID
FOR XML AUTO, ELEMENTS
My goal is to capture all the xmls returned by GetType into a List of objects.
Here is my dao:
private final JdbcTemplate jdbcTemplate;
#Autowired
public TransactionDao(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
#Transactional(readOnly = true)
public List<Object> getTransaction(Integer tranId, Integer qualId) {
Object dt = new Object();
List<Object> resultList = (List<Object>) jdbcTemplate.execute(
new CallableStatementCreator() {
public CallableStatement createCallableStatement(Connection con) throws SQLException {
String storedProc = "{call sp_GetType(?,?)}";
CallableStatement cs = con.prepareCall(storedProc);
cs.setInt(1, tranId);
cs.setInt(2, qualId);
return cs;
}
}, new CallableStatementCallback() {
public Object doInCallableStatement(CallableStatement > cs) throws SQLException,
DataAccessException {
List<Object> results = new ArrayList<Object>();
cs.execute();
if(cs.getMoreResults())
{
ResultSet rs = cs.getResultSet();
while(rs.next()) //rs has only one record
{
InputStream in = null;
Clob clob = rs.getClob(1);
in = clob.getAsciiStream();
}
rs.close();
}
return results;
}
});
return resultList;
}
I'm using the jtds 1.3.1 driver (I tried connecting using mssql-jdbc but no luck).
Any help is much appreciated.
I work with hibernate3 and didn't use JPA
I have a procedure in oracle which return 2 out parameter
For test I execute this procedure in oracle with this query.
declare
req_type number;
req_seq number;
begin
insert_req(1111,req_type,req_seq);
dbms_output.put_line('req_type='||req_type);
dbms_output.put_line('req_seq='||req_seq);
end;
Now I want to call this procedure using hibernate
I try with native query without success using this code :
public void insertReq(String numEmp) {
int req_type ;
int req_seq;
String sql = " insert_req(1111,:in1,:in2) ";
SQLQuery query = session.createSQLQuery(sql);
query.setParameter("in1", req_type);
query.setParameter("in2", req_seq);
List results = query.list();
System.out.println(req_type);
System.out.println(req_seq);
}
when I have a function I can run it using hibernate using this code as an example :
public void insertOrder(String numEmp) {
String query = "call insert_order(" + numEmp + ",50)";
SQLQuery sqlQuery = this.getSession().createSQLQuery(query);
sqlQuery.executeUpdate();
}
but the problem is how to call procedure with 2 out parameter using hibernate.
you have to use CallableStatement and registerOutParameter.
you can get a connection from your hibernate session and create the callablestatement.
hibernate does not provide a mecanism to deal with this (at least as i know).
i hope that helps.
Try this and let me know.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Session session = em.unwrap(Session.class);
session.doWork(new Work() {
#Override
public void execute(Connection con) throws SQLException {
// do something useful
try (CallableStatement stmt = con.prepareCall("{call my_sp()}")) {
stmt.execute();
}
}
});
em.close();
Best regards.
I have 200K rows to be inserted in one single database table. I tried to use jdbcTemplate.batchUpdate in spring in order to do insertion 10,000 per batch. However, this process consumes too much time (7 mins for 200K rows). So on database side, I check the number of rows inserted by select count(*) from table_X. I found the number of rows increased slightly instead of 10K expected. Can anyone explain what's reason or is it something which should be configurated on Database side ?
PS: I am using sybase ....
There are lot of approaches available on the web.
Performance directly depends on the
Code you have written
JDBC driver you are using
database server and number of connection you are using
table indexes leads to slowness for insertion
Without looking at your code anyone can guess, but no one can find the exact solution.
Approach 1
//insert batch example
public void insertBatch(final List<Customer> customers){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
getJdbcTemplate().batchUpdate(sql, new BatchPreparedStatementSetter() {
#Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Customer customer = customers.get(i);
ps.setLong(1, customer.getCustId());
ps.setString(2, customer.getName());
ps.setInt(3, customer.getAge() );
}
#Override
public int getBatchSize() {
return customers.size();
}
});
}
reference
https://www.mkyong.com/spring/spring-jdbctemplate-batchupdate-example/
http://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch12s04.html
Approach 2.1
//insert batch example
public void insertBatch(final List<Customer> customers){
String sql = "INSERT INTO CUSTOMER " +
"(CUST_ID, NAME, AGE) VALUES (?, ?, ?)";
List<Object[]> parameters = new ArrayList<Object[]>();
for (Customer cust : customers) {
parameters.add(new Object[] {cust.getCustId(),
cust.getName(), cust.getAge()}
);
}
getSimpleJdbcTemplate().batchUpdate(sql, parameters);
}
Alternatively, you can execute the SQL directly.
//insert batch example with SQL
public void insertBatchSQL(final String sql){
getJdbcTemplate().batchUpdate(new String[]{sql});
}
reference
https://www.mkyong.com/spring/spring-simplejdbctemplate-batchupdate-example/
Approach 2.2
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
SqlParameterSource[] batch = SqlParameterSourceUtils.createBatch(actors.toArray());
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
batch);
return updateCounts;
}
// ... additional methods
}
Approach 2.3
public class JdbcActorDao implements ActorDao {
private SimpleJdbcTemplate simpleJdbcTemplate;
public void setDataSource(DataSource dataSource) {
this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
}
public int[] batchUpdate(final List<Actor> actors) {
List<Object[]> batch = new ArrayList<Object[]>();
for (Actor actor : actors) {
Object[] values = new Object[] {
actor.getFirstName(),
actor.getLastName(),
actor.getId()};
batch.add(values);
}
int[] updateCounts = simpleJdbcTemplate.batchUpdate(
"update t_actor set first_name = ?, last_name = ? where id = ?",
batch);
return updateCounts;
}
// ... additional methods
}
Approach 3 :JDBC
dbConnection.setAutoCommit(false);//commit trasaction manually
String insertTableSQL = "INSERT INTO DBUSER"
+ "(USER_ID, USERNAME, CREATED_BY, CREATED_DATE) VALUES"
+ "(?,?,?,?)";
PreparedStatement = dbConnection.prepareStatement(insertTableSQL);
preparedStatement.setInt(1, 101);
preparedStatement.setString(2, "mkyong101");
preparedStatement.setString(3, "system");
preparedStatement.setTimestamp(4, getCurrentTimeStamp());
preparedStatement.addBatch();
preparedStatement.setInt(1, 102);
preparedStatement.setString(2, "mkyong102");
preparedStatement.setString(3, "system");
preparedStatement.setTimestamp(4, getCurrentTimeStamp());
preparedStatement.addBatch();
preparedStatement.executeBatch();
dbConnection.commit();
reference
https://www.mkyong.com/jdbc/jdbc-preparedstatement-example-batch-update/
/*Happy Coding*/
Try setting below for connection string - useServerPrepStmts=false&rewriteBatchedStatements=true. Have not tried but its from my bookmarks. You can search on these lines..
Connection c = DriverManager.getConnection("jdbc:<db>://host:<port>/db?useServerPrepStmts=false&rewriteBatchedStatements=true", "username", "password");
For us moving the code to a wrapper class and annotating the batch insert method with #Transactional did solve the problem.
I have a stored procedure defined in PostgresSql as below:
CREATE OR REPLACE FUNCTION update_points(accountId bigint, points numeric(19,5)) RETURNS void AS $$
...
...
$$ LANGUAGE plpgsql;
And I call the following method to call the procedure:
public void updatePoints(final Account account, final BigDecimal points) {
SimpleJdbcCall simpleCall = new SimpleJdbcCall(coreJdbcTemplate).withFunctionName("update_points");
SqlParameterSource inputs = new MapSqlParameterSource()
.addValue("accountId", account.getId())
.addValue("points", points);
simpleCall.execute(inputs);
}
When the method is called, I get following spring error:
org.springframework.dao.DataIntegrityViolationException: CallableStatementCallback; SQL [{? = call update_points()}]; No value specified for parameter 1.; nested exception is org.postgresql.util.PSQLException: No value specified for parameter 1.
at org.springframework.jdbc.support.SQLStateSQLExceptionTranslator.doTranslate(SQLStateSQLExceptionTranslator.java:102)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:73)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:81)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:1137)
at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1173)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.executeCallInternal(AbstractJdbcCall.java:388)
at org.springframework.jdbc.core.simple.AbstractJdbcCall.doExecute(AbstractJdbcCall.java:348)
at org.springframework.jdbc.core.simple.SimpleJdbcCall.execute(SimpleJdbcCall.java:190)
at net.exchangesolutions.veo.dao.AccountTransactionDaoImpl.updateRewardsConsumedAmount(AccountTransactionDaoImpl.java:28)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
I tried another way to call the procedure with CallableStatement in which case the function is not called at all.
Do you know what is the problem with the code or have you any suggestion about how to call the procedure from SpringJPA?
Thanks!
EDIT:
This is how I call with CallableStatement:
public void updatePoints(final Account account, final BigDecimal points) {
Connection connection;
try {
connection = coreJdbcTemplate.getDataSource().getConnection();
CallableStatement callableSt = connection.prepareCall("{call update_points(?, ?)}");
callableSt.setLong(1, account.getId());
callableSt.setBigDecimal(2, points);
callableSt.executeUpdate();
} catch (SQLException e) {
}
}
I resolved this problem by using following method. I could not figure out why the two approaches above did not work. Anyway, following solution is working now.
public void updatePoints(final Account account,
final BigDecimal points) {
coreJdbcTemplate.execute(new CallableStatementCreator() {
public CallableStatement createCallableStatement(Connection con)
throws SQLException {
CallableStatement cs = con
.prepareCall("{call update_points(?, ?)}");
cs.setLong(1, account.getId());
cs.setBigDecimal(2, points);
return cs;
}
}, new CallableStatementCallback() {
public Object doInCallableStatement(CallableStatement cs)
throws SQLException {
cs.execute();
return null;
}
});
I am using Spring 3.1.2,am using PreparedStatementSetter for Select, update and insert, but how can I use for Delete query?
jdbcTemplate.update() can be used for delete's
e.g.
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("delete from my_table where value=? ", new PreparedStatementSetter() {
public void setValues(PreparedStatement ps) throws SQLException {
ps.setLong(1, 2L);//or setString or whatever
}
});
you can also execute a delete without the PreparedStatementSetter as shown here: http://www.java2s.com/Code/Java/Spring/UseJdbcTemplateToExecuteDeleteStatementWithParameter.htm
You can use PreparedStatement for deletion like this
String sql = "DELETE FROM MY_TABLE WHERE ID = ?";
PreparedStatement ps = yourDBConnection.prepareStatement(sql);
ps.setInt(1, 1001);
// execute delete SQL stetement
ps.executeUpdate();