数据库查询的设计

查询的设计方法介绍

大家知道,基本上大部分的业务系统或产品的业务逻辑实现,都可以总结为数据库的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 );

多简单,在实现时,我只需要调用filtersetName(),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; }

       //

       //省略其他两个属性的settersgetters

       

       //

       //刚才写的拼串的代码可以放到这里

       //注意:这个方法中并没有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方法中根据

// UserFilterstartoffset来选择特定的记录返回,

//而不是上面例子的不加限制的返回

//这个时候就需要使用webber.core.frame.util.list.IteratorProvider

publicIterator[mike2] find( UserFilter filter )

{

     //和上面相同,返回包含User.class的对象

     List result = adapter.find( ... );

     return new IteratorProvider(

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++;

}

%>

代码分散的比较均匀,而且一个类只关心一块,就这样把复杂问题简单化!享受编程。。。享受生活!!


 [mike1]DateRange:时间段对象,包含一个开始日期和结束日期,DateRange.getStart();DateRange.getEnd()

详细信息请参见webber.core.frame.util中的定义

 [mike2]我使用迭代

 [mike3]会产生一个根据result的子序列为内容的迭代,开始于filter.getStart()的位置结束于filter.getStart()+filter.getOffset()的位置


版权声明:本文为MikeDogSong原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。