What is proper way to use PreparedStatementCreator of Spring JDBC? - spring

As per my understanding the use of PreparedStatement in Java is we can use it multiple times.
But I have some confusion using PreparedStatementCreator of Spring JDBC.
For example consider following code,
public class SpringTest {
JdbcTemplate jdbcTemplate;
PreparedStatementCreator preparedStatementCreator;
ResultSetExtractor<String> resultSetExtractor;
public SpringTest() throws SQLException {
jdbcTemplate = new JdbcTemplate(OracleUtil.getDataSource());
preparedStatementCreator = new PreparedStatementCreator() {
String query = "select NAME from TABLE1 where ID=?";
public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
return connection.prepareStatement(query);
}
};
resultSetExtractor = new ResultSetExtractor<String>() {
public String extractData(ResultSet resultSet) throws SQLException,
DataAccessException {
if (resultSet.next()) {
return resultSet.getString(1);
}
return null;
}
};
}
public String getNameFromId(int id){
return jdbcTemplate.query(preparedStatementCreator, new Table1Setter(id), resultSetExtractor);
}
private static class Table1Setter implements PreparedStatementSetter{
private int id;
public Table1Setter(int id) {
this.id =id;
}
#Override
public void setValues(PreparedStatement preparedStatement) throws SQLException {
preparedStatement.setInt(1, id);
}
}
public static void main(String[] args) {
try {
SpringTest springTest = new SpringTest();
for(int i=0;i<10;i++){
System.out.println(springTest.getNameFromId(i));
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
As per this code when I called springTest.getNameFromId(int id) method, it returns name from given id, Here I've used PreparedStatementCreator for creating PreparedStatement and PreparedStatementSetter for setting input parameters and I got result from ResultSetExtractor.
But performance is very slow.
After debugging and looking into what happens inside PreparedStatementCreator and JdbcTemplate I got to know that PreparedStatementCreator creates each and every time new PreparedStatement...!!!
Each and every time when I am calls method jdbcTemplate.query(preparedStatementCreator, preparedStatementSetter, resultSetExtractor), it creates new PreparedStatement and this slow downs performance.
Is this right way to use PreparedStatementCreator? Because in this code I unable to reuse PreparedStatement. And if this is right way to use PreparedStatementCreator than how to get benefit of re-usability of PreparedStatement?

Prepared Statements are usually cached by underlying connection pool, so you don't need to worry about creating a new one every time or not.
So I think that your actually usage is correct.
JdbcTemplate closes the statement after executing it, so if you really want to reuse the same prepared statement you could proxy the statement and intercept the close method in the statement creator
For example (not tested, only as example):
public abstract class ReusablePreparedStatementCreator implements PreparedStatementCreator {
private PreparedStatement statement;
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
if (statement != null)
return statement;
PreparedStatement ps = doPreparedStatement(conn);
ProxyFactory pf = new ProxyFactory(ps);
MethodInterceptor closeMethodInterceptor = new MethodInterceptor() {
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
return null; // don't close statement
}
};
NameMatchMethodPointcutAdvisor closeAdvisor = new NameMatchMethodPointcutAdvisor();
closeAdvisor.setMappedName("close");
closeAdvisor.setAdvice(closeMethodInterceptor);
pf.addAdvisor(closeAdvisor);
statement = (PreparedStatement) pf.getProxy();
return statement;
}
public abstract PreparedStatement doPreparedStatement(Connection conn) throws SQLException;
public void close() {
try {
PreparedStatement ps = (PreparedStatement) ((Advised) statement).getTargetSource().getTarget();
ps.close();
} catch (Exception e) {
// handle exception
}
}
}

You are on the right way to use PreparedStatementCreator.
In each new transaction, you should create brand new PreparedStatement instance, it's definitely correct. PreparedStatementCreator is mainly designed to wrap the code block to create PreparedStatement instance easily, not saying that you should resue the new instance each itme.
PreparedStatement is mainly designed to send the templated and pre-compiled SQL statement DBMS which will save some pre-compiled time for SQL execution.
To summarize, what you did is correct. use PreparedStatement will have better performance than Statement.

After debugging and looking into what happens inside PreparedStatementCreator and JdbcTemplate I got to know that PreparedStatementCreator creates each and every time new PreparedStatement...!!!
I'm not sure why that's so shocking since it's your own code that creates a new PreparedStatement each time by calling connection.prepareStatement(query);. If you want to reuse the same one, then you shouldn't create a new one.

Related

Manage I/O and POI exception in a customReaderItem

I'm using a customReader that implements ItemReader. My reader take information from xls and treat it row by row. My constructor take Iterator value that will be read on each read() iteration. I'm trying to find a suitable way to manage exception. I look through SkipListener and ReaderListener onReadError. But Ican't use either because my exception will be thrown in the constructor before attempting read() method.
Is there any way to do that in order to allow me to manage properly action1/2/3 respectively to exceptions ?
#Component
public class CustomReaderFile implements ItemReader<Row> {
private final Iterator<Row> data;
private static final String FILE_NAME = "";
public CustomReaderFile() throws Exception {
this.data = iteratorFromXls();
}
#Override
public Row read() throws Exception {
if (this.data.hasNext()) {
return this.data.next();
} else {
return null;
}
}
public static Iterator<Row> iteratorFromXls() throws Exception {
Iterator<Row> iterator = null;
try {
FileInputStream excelFile = new FileInputStream(new File(FILE_NAME));
Workbook workbook = new XSSFWorkbook(excelFile);
Sheet dataTypeSheet = workbook.getSheetAt(0);
iterator = dataTypeSheet.iterator();
} catch (FileNotFoundException e) {
//action1
} catch (IOException e) {
//action2
} catch (NotOfficeXmlFileException e){
//action3
}
return iterator;
}
This is actually the reader's initialization code. The reading part is nothing more than calling .next on the iterator.
So I would make the reader implement ItemStreamReader and put the initialization code in the open method, in which you can throw an exception to signal to Spring Batch that the reader's initialization has failed.

SpringBoot+JDBCTemplate+Parent Key not found error

Unable to insert into child table in a single transaction using springboot and jdbctemplate.
Controller:
#RequestMapping(value = "/addUser", method = RequestMethod.POST)
public Response addUser(#RequestBody UserVo userVo) throws Exception
{
service.addUserRecord(userVo);
}
Service Layer:
#Autowired
private Dao dao;
#Transactional(readOnly = false)
public void addUserRecord(#RequestBody UserVo userVo) throws Exception
{
int parentSeqNo = dao.addUser();
int result = dao.addUserDetails(parentSeqNo, list);
}
DAOImple class:
#Autowired
private JdbcTemplate jdbcTemplate;
public int addUser()
{
try
{
String sql = "INSERT INTO user_table() VALUES (?,?,?,?,?) ";
jdbcTemplate.update(sql,....);
return seqNo;
}
catch(Exception e)
{
e.printStackTrace();
throw e;
}
}
public void addUserDetails(String seqNo, List<String> list){
String sql="insert into user_details_table values(?,?)";
jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
public void setValues(PreparedStatement ps, int i) throws SQLException {
String img = list.get(i);
ps.setString(1, seqNo);
ps.setString(2, img);
}
public int getBatchSize() {
return list.size();
}
});
return;
}
Issue/Error:
ORA-02291: integrity constraint (FK3) violated - parent key not found; nested exception is java.sql.SQLIntegrityConstraintViolationException:
Database tables and constraints are looks good and tried to insert using sql editor, just working fine. While testing from postman tool, getting parent key not found exception. Greatly appreciated for any suggestions on this issue.

Get Statement from JDBCTemplate

I have this code,
SimpleJdbcCall sql = new SimpleJdbcCall(dataSource).withProcedureName(procName);
sql.execute(parameters);
And I believe that under the hood this uses a JDBC Statement. How can I get to that object from here? (I need to call the .getWarnings() method on the statement).
In other words how can I get SQLWarnings AND named parameters?
It took a lot of digging, but here is how you can get SQLWarnings (or Print statements) AND named parameters. I extended JdbcTemplate and overrode the handleWarnings() method, and then passed that into my SimpleJdbcCall.
public class JdbcTemplateLoggable extends JdbcTemplate{
List<String> warnings;
public JdbcTemplateLoggable(DataSource dataSource){
super(dataSource);
warnings = new ArrayList<String>();
}
protected void handleWarnings(Statement stmt){
try {
SQLWarning warning = stmt.getWarnings();
while(warning != null){
warnings.add(warning.getMessage());
warning = warning.getNextWarning();
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public List<String> getWarnings(){
return warnings;
}
}
Then in my main program
JdbcTemplateLoggable template = new JdbcTemplateLoggable(dataSource);
SimpleJdbcCall sql = new SimpleJdbcCall(template).withProcedureName(procName);
sql.execute(parameters);
for(String s : template.getWarnings()){
log.info(s);
}
You should perhaps use JdbcTemplate directly, or subclass it for usage with your SimpleJdbcCall (instead of a DataSource). JdbcTemplate has a method execute(CallableStatementCreator, CallableStatementCallback), where a callback can be passed which gets the used Statement object.
You could override that method and wrap the passed callback with an own which stores the statement for later use.
public class CustomJdbcTemplate extends JdbcTemplate {
private CallableStatement lastStatement;
public CustomJdbcTemplate(DataSource dataSource) {
super(dataSource);
}
public CallableStatement getLastStatement() {
return lastStatement;
}
#Override
public <T> T execute(CallableStatementCreator csc, CallableStatementCallback<T> action) throws DataAccessException {
StoringCallableStatementCallback<T> callback = new StoringCallableStatementCallback<T>(action);
try {
return super.execute(csc, callback);
}
finally {
this.lastStatement = callback.statement;
}
}
private static class StoringCallableStatementCallback<T> implements CallableStatementCallback<T> {
private CallableStatementCallback<T> delegate;
private CallableStatement statement;
private StoringCallableStatementCallback(CallableStatementCallback<T> delegate) {
this.delegate = delegate;
}
#Override
public T doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
this.statement = cs;
return delegate.doInCallableStatement(cs);
}
}
}
Note that the statement will most probably be closed when you retrieve it later, so getWarnings() may cause errors, depending on used JDBC driver. So maybe you should store the warnings instead of the statement itself.

How to temporarily connect to MySQL in MapReduce

I want to connect to MySQL in MapReduce temporarily. In other words, I want to bring table in MySQL to Map function temporarily to change InputData(Text) but, the Result was empty.
Below is my code:
public class Map extends Mapper{
private Text outputKey=new Text();
private final static IntWritable outputValue=new IntWritable(1);
public void map(LongWritable key,Text value,Context context)
throws IOException,InterruptedException
{
int i=0;
try{
Connection con=DriverManager.getConnection("jdbc:sqlserver://locallhost:3306/test_db",
"user","user");
PreparedStatement ps=con.prepareStatement("select * from studentinfo");
ResultSet rs=ps.executeQuery();
while(rs.next())
i++;
AirlinePerformanceParser parser=new AirlinePerformanceParser(value);
outputKey.set(parser.getMonth_day()+","+i);
context.write(outputKey, outputValue);
}catch(SQLException e)
{
e.printStackTrace();
}
}
}
Map and reduce work well (there are no exceptions), but Result was empty.
I don't want to use DBInputFormat. MySQL table must not be used by DBInputFormat.
What is the problem?
If your query is static, i.e.it does not depend on map input (as is your current query), you should move your sql code into the setup function, to query your DB only once.
// set global the data buffer from your sql results (here a counter)
int nbResults;
#Override
protected void setup(Context context) throws IOException, InterruptedException {
// query your DB
Connection con [...]
}
public void map(LongWritable key,Text value,Context context)throws IOException,InterruptedException {
AirlinePerformanceParser parser=new AirlinePerformanceParser(value);
outputKey.set(parser.getMonth_day()+","+nbResults);
context.write(outputKey, outputValue);
}
As pointed by #Ramzy, check the connexion status.
In addition, do not perform a SELECT * if what you need is a COUNT(*).

Freemarker removeIntrospectionInfo does not work with DCEVM after model hotswap

I am using Freemarker and DCEVM+HotSwapManager agent. This basically allows me to hotswap classes even when adding/removing methods.
Everything works like charm until Freemarker uses hotswapped class as model. It's throwing freemarker.ext.beans.InvalidPropertyException: No such bean property on me even though reflection shows that the method is there (checked during debug session).
I am using
final Method clearInfoMethod = beanWrapper.getClass().getDeclaredMethod("removeIntrospectionInfo", Class.class);
clearInfoMethod.setAccessible(true);
clearInfoMethod.invoke(clazz);
to clear the cache, but it does not work. I even tried to obtain classCache member field and clear it using reflection but it does not work too.
What am I doing wrong?
I just need to force freemarker to throw away any introspection on model class/classes he has already obtained.
Is there any way?
UPDATE
Example code
Application.java
// Application.java
public class Application
{
public static final String TEMPLATE_PATH = "TemplatePath";
public static final String DEFAULT_TEMPLATE_PATH = "./";
private static Application INSTANCE;
private Configuration freemarkerConfiguration;
private BeansWrapper beanWrapper;
public static void main(String[] args)
{
final Application application = new Application();
INSTANCE = application;
try
{
application.run(args);
}
catch (InterruptedException e)
{
System.out.println("Exiting");
}
catch (IOException e)
{
System.out.println("IO Error");
e.printStackTrace();
}
}
public Configuration getFreemarkerConfiguration()
{
return freemarkerConfiguration;
}
public static Application getInstance()
{
return INSTANCE;
}
private void run(String[] args) throws InterruptedException, IOException
{
final String templatePath = System.getProperty(TEMPLATE_PATH) != null
? System.getProperty(TEMPLATE_PATH)
: DEFAULT_TEMPLATE_PATH;
final Configuration configuration = new Configuration();
freemarkerConfiguration = configuration;
beanWrapper = new BeansWrapper();
beanWrapper.setUseCache(false);
configuration.setObjectWrapper(beanWrapper);
try
{
final File templateDir = new File(templatePath);
configuration.setTemplateLoader(new FileTemplateLoader(templateDir));
}
catch (IOException e)
{
throw new RuntimeException(e);
}
final RunnerImpl runner = new RunnerImpl();
try
{
runner.run(args);
}
catch (RuntimeException e)
{
e.printStackTrace();
}
}
public BeansWrapper getBeanWrapper()
{
return beanWrapper;
}
}
RunnerImpl.java
// RunnerImpl.java
public class RunnerImpl implements Runner
{
#Override
public void run(String[] args) throws InterruptedException
{
long counter = 0;
while(true)
{
++counter;
System.out.printf("Run %d\n", counter);
// Application.getInstance().getFreemarkerConfiguration().setObjectWrapper(new BeansWrapper());
Application.getInstance().getBeanWrapper().clearClassIntrospecitonCache();
final Worker worker = new Worker();
worker.doWork();
Thread.sleep(1000);
}
}
Worker.java
// Worker.java
public class Worker
{
void doWork()
{
final Application application = Application.getInstance();
final Configuration freemarkerConfiguration = application.getFreemarkerConfiguration();
try
{
final Template template = freemarkerConfiguration.getTemplate("test.ftl");
final Model model = new Model();
final PrintWriter printWriter = new PrintWriter(System.out);
printObjectInto(model);
System.out.println("-----TEMPLATE MACRO PROCESSING-----");
template.process(model, printWriter);
System.out.println();
System.out.println("-----END OF PROCESSING------");
System.out.println();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (TemplateException e)
{
e.printStackTrace();
}
}
private void printObjectInto(Object o)
{
final Class<?> aClass = o.getClass();
final Method[] methods = aClass.getDeclaredMethods();
for (final Method method : methods)
{
System.out.println(String.format("Method name: %s, public: %s", method.getName(), Modifier.isPublic(method.getModifiers())));
}
}
}
Model.java
// Model.java
public class Model
{
public String getMessage()
{
return "Hello";
}
public String getAnotherMessage()
{
return "Hello World!";
}
}
This example does not work at all. Even changing BeansWrapper during runtime won't have any effect.
BeansWrapper (and DefaultObjectWrapper's, etc.) introspection cache relies on java.beans.Introspector.getBeanInfo(aClass), not on reflection. (That's because it treats objects as JavaBeans.) java.beans.Introspector has its own internal cache, so it can return stale information, and in that case BeansWrapper will just recreate its own class introspection data based on that stale information. As of java.beans.Introspector's caching, it's in fact correct, as it builds on the assumption that classes in Java are immutable. If something breaks that basic rule, it should ensure that java.beans.Introspector's cache is cleared (and many other caches...), or else it's not just FreeMarker that will break. At JRebel for example they made a lot of effort to clear all kind of caches. I guess DCEVM doesn't have the resources for that. So then, it seems you have to call Introspector.flushCaches() yourself.
Update: For a while (Java 7, maybe 6) java.beans.Introspector has one cache per thread group, so you have call flushCaches() from all thread groups. And this all is actually implementation detail that, in principle, can change any time. And sadly, the JavaDoc of Introspector.flushCaches() doesn't warn you...

Resources