查询的设计方法介绍
大家知道,基本上大部分的业务系统或产品的业务逻辑实现,都可以总结为数据库的CRUD和查询。对于数据库的CRUD操作,这些都在webber.core.frame.db中有体现,这里就不再赘述。下面我想说一下对于数据库查询的设计思路。
举例子仍然使用用户系统,我要实现一个根据用户姓名,账号,年龄段来查询用户信息的查询:
按照平时的做法,我们会有这么一个Command:
public class UserCommand extends AbstractCommand
{
Adapter adapter = AdapterFactory.createAdapter( “dbName”,true );
public String processSearch()
{
//
//获取查询条件
String name = request.getParameter( “name” );
String account = request.getParameter( “account” );
String creationDate = request.getParameter( “creationDate” );
//
//拼查询sql语句,对获取的参数进行判断
String sql = “select * from user where 1=1 “;
if( StringHelper.isEmpty(name) )
sql += “and name like ‘%” + name + “%’ ”;
if( StringHelper.isEmpty(account) )
sql += “and account like ‘%” + account + “%’ ”;
if( StringHelper.isEmpty(creationDate) )
{
sql += “and creationdate=” + creationDate + “ “;
}
//
//查询数据库,把得到的结果发给页面显示
List result = adapter.find( User.class,sql,null );
request.setAttribute( “QUERY_RESULT”,result );
return “userList.jsp”;
}
}
这个例子我们可以看到,虽然使用Command模式把jsp页面的代码减少了,可是我们很容易把jsp的代码写到command中,实际上MagicJSP的问题没有解决,只是转移到MagicCommand了!
如果我们增加一个业务层来处理查询的问题,这样Command会瘦很多:
public class UserManager
{
public List find( String name,String creationDate,String account );
}
这个时候,只需要在Command中调用这个方法既可:
UserManager um = new UserManager();
List result = um.find(
request.getParameter(“name”),
request.getParameter(“creationDate”),
request.getParameter(“account”) );
这样的解决方法很简单,但有一个缺点,参数太多,不好记,更不好用。为了解决这个问题,我使用一个单独的类来包含这些参数,在提交查询条件的时候构造这个参数类,然后把这个参数类提交给方法:
public interface ResultFilter
{
public String getFilteredStatement();
}
其中getFilteredStatement()就是专门根据查询条件来生成sql语句的,然后上面的find方法就可以写成:
public List find( ResultFilter filter );
多简单,在实现时,我只需要调用filter的setName(),setAccount(),setCreationDate()方法就可以完成参数的赋值:
public class UserFilter implements ResultFilter
{
private String name = “”;
private String creationDate = “”;
private String account = “”;
public void setAccount( String acc ){ account = acc; }
public void getAccount() { return account; }
//
//省略其他两个属性的setters和getters
//
//刚才写的拼串的代码可以放到这里
//注意:这个方法中并没有select语句,from语句,因为我们封装的
//是查询条件而不是查询内容,如果加了select语句,
//那么这个filter的重用的可能就会降低,甚至不能重用
public String getFilteredStatement()
{
String sql = “”;
if( StringHelper.isEmpty(name) )
sql += “and name like ‘%” + name + “%’ ”;
if( StringHelper.isEmpty(account) )
sql += “and account like ‘%” + account + “%’ ”;
if( StringHelper.isEmpty(creationDate) )
{
sql += “and creationdate=” + creationDate + “ “;
}
return sql;
}
}
方法UserManager.find可以用下面的方式来实现,请注意方法的参数定义:
List find( UserFilter filter )
{
List result = adapter.find(
User.class,
“select * from user
where 1=1 “ + filter.getFilteredStatement(),null );
return result;
}
然后对Command就可以这样
public String processSearch()
{
UserFilter uf = new UserFilter();
uf.setAccount( request.getParameter(“account”) );
uf.setName( request.getParameter(“name”) );
uf.setCreationDate( request.getParameter(“creationDate”) );
UserManager um = new UserManager();
request.setAttribute( “QUERY_RESULT”,um.find(uf) );
return “userList.jsp”;
}
我们还可以在UserFilter中增加排序、部分选择的功能:
class UserFilter
{
public int getStart(){}
public int getOffset(){}
public boolean isDesc(){}
public void setOrderBy( String orderBy ){}
public String getOrderBy(){}
}
考虑到所有的查询基本上都会有这些功能,因此,把这些功能抽象出来,形成一个独立的类,
webber.core.frame.db.list.AbstractFilter,如果要实现你自定义的查询条件,可以继承这个类来实现。
ResultFilter的重用机制:
大家都知道:如果在查询条件中包含日期,那么日期条件的格式会随数据库的不同有不同的变化,我们还是考虑刚才的UserFilter,我们要查询某个时间段内注册的用户:
public abstract UserFilter extends AbstractFilter //我继承了AbstractFilter
{
private String name = “”;
privateDateRange[mike1] range = null;
public void setDateRange( Date dateStart,Date dateEnd )
{
range = new DateRange( dateStart,dateEnd );
}
//
//这个方法我们让他抽象,继承我的类必须实现它来满足不同数据库查询语句的要求
public abstract String getDateRangeStatement( DateRange range );
public String getFilteredStatement()
{
StringBuffer sb = new StringBuffer();
sb.append( range == null
? “” : getDateRangeStatement( range ) + “ “ );
sb.append( StringHelper.isEmpty(name)
? “” : “and username like ‘%” + name + “%’ “);
//
// super.getFilteredStatement()返回按照某个字段进行排序的sql语句
return sb.append( super.getFilteredStatement() ).toString();
}
}
对于MySql的查询条件,我们可以这么写:
public class MySqlUserFilter extends UserFilter
{
public String getDateRangeStatement( DateRange range ){
return “and creationdate between ‘” +
DateHelper.parseString( range.getStart() ) + “’ and ‘“ +
DateHelper.parseString( range.getEnd() ) + “’ “;
}
}
对于Access数据库,我们就可以这么写
public class AccessUserFilter extends UserFilter
{
public String getDateRangeStatement( DateRange range ){
return “and creationdate between #” +
DateHelper.parseString( range.getStart() ) + “# and #“ +
DateHelper.parseString( range.getEnd() ) + “# “;
}
}
大家可能看到了,由于从UserManager.find()方法返回的是个List(或Iterator),这样的方式和当前的BaseList翻页机制不兼容,也就是说没有办法使用BaseList的翻页机制,因此我提供了webber.core.frame.db.list.Title这个类来专门生成翻页信息:
Title title = new Title( request,”formName”,total,rowOfPage );
Ø 第一个参数大家都知道,request
Ø 第二个是页面表单的名称,和BaseList.setFormName()一样的
Ø total是指Title显示的数据总量是多少
Ø rowOfPage是指每页显示多少条,会根据total来计算当前页面和页面数量。
需要显示的时候,可以使用Title的方法:
Title.show( out,””,”” );既可
Ø out:javax.servlet.jsp.JspWriter就是jsp页面中的out
Ø 后两个参数是显示翻页时按钮的样式和文字的样式
要获取当前页面的记录开始编号和记录数量可以使用
Title.getStart()
Title.getOffset(),获取每页的记录条数
象上面的用户查询的例子我们可以这样:
class UserManager
{
//
//为了使用翻页机制,我们需要在find方法中根据
// UserFilter的start和offset来选择特定的记录返回,
//而不是上面例子的不加限制的返回
//这个时候就需要使用webber.core.frame.util.list.IteratorProvider
publicIterator[mike2] find( UserFilter filter )
{
//和上面相同,返回包含User.class的对象
List result = adapter.find( ... );
result,filter.getStart(),filter.getOffset() ){
public Object fetch( Object key ){
return ( User ) key;
}
}
[mike3] }
//
//返回某个查询结果的数量
//看到这个,我们就会明白为什么不能在UserFilter中包含select语句了
public int total( UserFilter filter )
{
List result = adapter.find(
“select count(1) as c from user “ +
“where 1=1 “ + filter.getFilteredStatement(),null );
return StringHelper.parseInt(
String.valueOf(
((HashMap)result.get(0)).get(“c”)) );
}
}
对于Command可以这样来写
class UserCommand extends AbstractCommand
{
public String processSearch()
{
UserManager um = new UserManager();
//
// create filter
//先查总数,根据总数和每个页面的记录数量来计算页数,
UserFilter uf = ...;
int total = um.total( uf );
//
//分页开始
Title t = new Title( request,”users”,total,10 );
//
//根据分页来选择当前页面的数据:
uf.setOffset( t.getOffset() );
uf.setStart( t.getStart );
Iterator it = um.find( uf );
request.setAttribute( “QUERY_RESULT”,it );
return “users.jsp”;
}
}
对于jsp页面来说,直接获取迭代显示就可以了
<%
int i = 0;
Iterator users = ( Iterator ) request.getAttribute( “QUERY_RESULT” );
while( users!=null && users.hasNext() ){
User user = ( User ) users.next();
out.println( “<tr “ +
(i%2==0?”listContentDark”:”listContentBright”) + “>” +
“<td>” + user.getName() + “</td>” +
....
);
i++;
}
%>
代码分散的比较均匀,而且一个类只关心一块,就这样把复杂问题简单化!享受编程。。。享受生活!!