james.estes
Aug 28th, 2004, 10:15 PM
I'm going to post an issue in JIRA, but it appears down. Thought I'd post here to get some feelers.
When I first started using the SqlQuery objects, I noticed that the
SqlParameter object has a name property. This made me think that
maybe spring supports named bind variables. So I tried it:
select *
from employees
where (firstName=:name
OR lastname=:name)
and :refDate between startdate and enddate
...no dice. So, since the support was not there, I added it (I will post the diff to JIRA).
Following is a description of the changes I made.
To start out with, I added a SqlParameterValue class that basically
wraps an object and gives it a name. This is the name of the declared
parameter that the object corresponds to. I then modified
PreparedStatementCreatorFactory to check for values of this type and
upon seeing them, re-orders the input parameter list to match the
order their names appear in the declared parameter list. At this
point, I could create a sql query that declares parameters with names
and then calls execute with SqlParameterValues with names:
super(ds, "select * "+
"from employees "+
"where (firstname=? "+
" or lastname=?) "+
" and ? between startdate and enddate"
);
declareParameter(new SqlParameter("firstname", SqlTypes.VARCHAR));
declareParameter(new SqlParameter("lastname", SqlTypes.VARCHAR));
declareParameter(new SqlParameter("refdate", SqlTypes.DATE));
compile();
and execute it with:
query.execute(new Object[]{
new SqlParameterValue("lastname", commandBean.getLastName()),
new SqlParameterValue("firstname", commandBean.getFirstName()),
new SqlParameterValue("refdate", commandBean.getRefDate())
);
...notice I can pass the parameters in any order...they will be
reorganized by the PreparedStatementCreatorFactory.
Then, stepping back and looking at the result, I thought it would be
really nice if i could just pass my bean to the execute method and
have spring pull out the parameters from the properties of the bean.
I added this support by adding a method to the RdbmsOperation class
that pulls the properties from the bean, then added "*fromBean"
methods (updateFromBean, executeFromBean, and findObjectFromBean) to
the SqlUpdate and SqlQuery classes. Now I can execute my query with:
query.executeFromBean( commandBean );
I still wanted to be able to have named
bind variables within the actual sql string and not have to be sure
and declare the parameters in the order they appear in the sql. I
also wanted to be able to reference a declared parameter multiple
times in the same sql (in my query the firstname and lastname on the
command bean are actually the same value....the query is finding an
employee who was known to be employed on a given date and first or
last name was the given value).
So I modified PreparedStatementCreatorFactory to have a
usingNamedBinds property that would be set to true when using a sql
query that contained named bind variables. When this 'mode' is on,
the factory will pull the placeholders from the sql and reorder
the declared parameters and the input parameters (when called) to
match the order they appear in the sql. I then modified SqlOperation
to also have a usingNamedBinds property that would be passed through
to the PreparedStatementCreatorFactory. Once finished, this allowed
me to declare and execute my query as:
super(ds, "select * "+
"from employees "+
"where (firstname=:name "+
" or lastname=:name) "+
" and :refDate between startdate and enddate"
);
setUsingNamedBinds(true);
declareParameter(new SqlParameter("refDate", SqlTypes.DATE));
declareParameter(new SqlParameter("name", SqlTypes.VARCHAR));
compile();
and execute it with:
//order MUST match declared order
query.execute(new Object[]{
commandBean.getRefDate(),
commandBean.getName()
);
OR
//order need not match declared order
query.execute(new Object[]{
new SqlParameterValue("name", commandBean.getName()),
new SqlParameterValue("refDate", commandBean.getRefDate())
);
OR
query.executeFromBean(commandBean);
...and we have what I originally wanted.
So, I hope you'll find this useful enough to include into spring. I
know it has greatly simplified my daos. My next logical step is to
externalize the queries entirely and introduce a NamedSqlFactory that
creates SqlOperations by reading some external source. The implementation
I'm thinking about would have a .sql file that it reads from a file similar
to:
--findEmployeesByNameAndDate(String name, Date refDate):com.foo.Employee{
select *
from employees
where (firstName=:name
OR lastname=:name)
and :refDate between startdate and enddate
--}
My dao could then create and call it by:
List employees = namedSqlFactory.executeFromBean(
"findEmployeesByNameAndDate", commandBean );
When I first started using the SqlQuery objects, I noticed that the
SqlParameter object has a name property. This made me think that
maybe spring supports named bind variables. So I tried it:
select *
from employees
where (firstName=:name
OR lastname=:name)
and :refDate between startdate and enddate
...no dice. So, since the support was not there, I added it (I will post the diff to JIRA).
Following is a description of the changes I made.
To start out with, I added a SqlParameterValue class that basically
wraps an object and gives it a name. This is the name of the declared
parameter that the object corresponds to. I then modified
PreparedStatementCreatorFactory to check for values of this type and
upon seeing them, re-orders the input parameter list to match the
order their names appear in the declared parameter list. At this
point, I could create a sql query that declares parameters with names
and then calls execute with SqlParameterValues with names:
super(ds, "select * "+
"from employees "+
"where (firstname=? "+
" or lastname=?) "+
" and ? between startdate and enddate"
);
declareParameter(new SqlParameter("firstname", SqlTypes.VARCHAR));
declareParameter(new SqlParameter("lastname", SqlTypes.VARCHAR));
declareParameter(new SqlParameter("refdate", SqlTypes.DATE));
compile();
and execute it with:
query.execute(new Object[]{
new SqlParameterValue("lastname", commandBean.getLastName()),
new SqlParameterValue("firstname", commandBean.getFirstName()),
new SqlParameterValue("refdate", commandBean.getRefDate())
);
...notice I can pass the parameters in any order...they will be
reorganized by the PreparedStatementCreatorFactory.
Then, stepping back and looking at the result, I thought it would be
really nice if i could just pass my bean to the execute method and
have spring pull out the parameters from the properties of the bean.
I added this support by adding a method to the RdbmsOperation class
that pulls the properties from the bean, then added "*fromBean"
methods (updateFromBean, executeFromBean, and findObjectFromBean) to
the SqlUpdate and SqlQuery classes. Now I can execute my query with:
query.executeFromBean( commandBean );
I still wanted to be able to have named
bind variables within the actual sql string and not have to be sure
and declare the parameters in the order they appear in the sql. I
also wanted to be able to reference a declared parameter multiple
times in the same sql (in my query the firstname and lastname on the
command bean are actually the same value....the query is finding an
employee who was known to be employed on a given date and first or
last name was the given value).
So I modified PreparedStatementCreatorFactory to have a
usingNamedBinds property that would be set to true when using a sql
query that contained named bind variables. When this 'mode' is on,
the factory will pull the placeholders from the sql and reorder
the declared parameters and the input parameters (when called) to
match the order they appear in the sql. I then modified SqlOperation
to also have a usingNamedBinds property that would be passed through
to the PreparedStatementCreatorFactory. Once finished, this allowed
me to declare and execute my query as:
super(ds, "select * "+
"from employees "+
"where (firstname=:name "+
" or lastname=:name) "+
" and :refDate between startdate and enddate"
);
setUsingNamedBinds(true);
declareParameter(new SqlParameter("refDate", SqlTypes.DATE));
declareParameter(new SqlParameter("name", SqlTypes.VARCHAR));
compile();
and execute it with:
//order MUST match declared order
query.execute(new Object[]{
commandBean.getRefDate(),
commandBean.getName()
);
OR
//order need not match declared order
query.execute(new Object[]{
new SqlParameterValue("name", commandBean.getName()),
new SqlParameterValue("refDate", commandBean.getRefDate())
);
OR
query.executeFromBean(commandBean);
...and we have what I originally wanted.
So, I hope you'll find this useful enough to include into spring. I
know it has greatly simplified my daos. My next logical step is to
externalize the queries entirely and introduce a NamedSqlFactory that
creates SqlOperations by reading some external source. The implementation
I'm thinking about would have a .sql file that it reads from a file similar
to:
--findEmployeesByNameAndDate(String name, Date refDate):com.foo.Employee{
select *
from employees
where (firstName=:name
OR lastname=:name)
and :refDate between startdate and enddate
--}
My dao could then create and call it by:
List employees = namedSqlFactory.executeFromBean(
"findEmployeesByNameAndDate", commandBean );