Query Object
编程语言可以包含sql语句,但许多开发者对此并不太熟悉。而且,你需要了解数据库设计方案以便形成查询。可以通过创建特殊的,隐藏的SQL内部参数化方法来避免这一点。但是这样难以构造更多的查询。而且,如果数据库设计方案改变,就会需要复制到SQL语句。
查询对象是一个解析器(interpreter)【Gang of Four】,也就是说,它是由多个对象组成的结构,该结构可以自身转化为一个sql查询。你可以通过引用类和域而不是表和列来创建这个查询。采用这种方式,那些编写查询的人就能够设计独立数据库方案的查询,并且数据库方案的变化也可以被封装到局部
《企业应用架构模式》 - Martin Fower
看到上面,我第一时间是想到Hibernate中的Criteria查询,他是Query Object很直观和跟经典的实现。但是就我个人使用来说,Criteria查询比较少,而且要记住几个英文很长的类,写出来巨繁琐(静态导入会好一些)。目前做项目都基于JPA的接口来开发。(但大多复杂查询还是使用原生sql查出来,然后自己转换对象!也有干脆使用mybatis)。就平时开发而言,简单快速好用的封装Query Object,然后配合链式编程(该实践中,良好的测试可以避免火车事故),使用起来是非常方便的。
查询对象可以很好的简单的完美的:^)跟 查询通道(QueryChannel)或者直接活动记录(ActiveRecord)+资源库(Repository)这些模式结合,形成简单好用的查询接口。
以下是我参考以前一些前辈,跟自己进行改进的一个Query Object实践,基本JPQL。 最终于我Repository 或DAO的查询接口可能是类似于这样的风格。
<T> T findEntity(Query q);
PageVo findPage(Query q);
List<T> findEntites(Query q);
然后这个Query的代码跟单元测试:
Query.java
package com.xz.marksix.repository;
import java.util.ArrayList;
import java.util.List;
import com.xz.marksix.infrastructure.AppContants;
/**
* <pre>
*
* JPA 查询对象
*
* @author XzhiF
* Sep 25, 2013
* </pre>
*/
public class Query
{
private static final String SPACE = " ";
private static final String SELECT = "SELECT";
private static final String COUNT = "COUNT";
private static final String FROM = "FROM";
private static final String WHERE = "WHERE";
private static final String AND = "AND";
private static final String OR = "OR";
private static final String ORDER_BY = "ORDER BY";
private static final String ASC = "ASC";
private static final String DESC = "DESC";
private static final String JOIN = "JOIN";
private static final String INNER_JOIN = "INNER JOIN";
private static final String LEFT_JOIN = "LEFT JOIN";
private static final String RIGHT_JOIN = "RIGHT JOIN";
private static final String LEFT_OUTER_JOIN = "LEFT OUTER JOIN";
private static final String RIGHT_OUT_JOIN = "RIGHT OUTER JOIN";
private static final String LEFT_JOIN_FETCH = "LEFT JOIN FETCH";
private static final String RIGHT_JOIN_FETCH = "RIGHT JOIN FETCH";
private static final boolean ADD_PARAM = true;
private static final boolean NOT_ADD_PARAM = false;
private Class<?> entityClass;
private String alias;
private Integer pageNum;
private Integer pageSize;
private List<Object> paramters = new ArrayList<>();
private StringBuilder selectClause = new StringBuilder();
private StringBuilder fromClause = new StringBuilder();
private StringBuilder joinClause = new StringBuilder();
private StringBuilder whereClause = new StringBuilder();
private StringBuilder orderByClause = new StringBuilder();
private boolean fetchAllRecords = true;
public Query( Class<?> entityClass, String alias )
{
this.entityClass = entityClass;
this.alias = alias;
/**
* FROM abc.Def alais
*/
fromClause//
.append( SPACE )//
.append( FROM )//
.append( SPACE )//
.append( entityClass.getName() )//
.append( SPACE )//
.append( this.alias );
}
/**
* evaluate 生成最后的JPQL语句
*/
public String evaluate()
{
StringBuilder result = new StringBuilder();
/**
* 以下构造要保持顺序,若重构时请注意
*/
evlauteAppendSelectClause( result );
evlauteAppendFromClause( result );
evlauteAppendJoinClause( result );
evlauteAppendWhereClause( result );
evlauteAppendOrderByClause( result );
return result.toString().trim();
}
/**
* evaluateCount 生成最后的查询总数的JPQL语句
*/
public String evaluateCount()
{
StringBuilder result = new StringBuilder();
/**
* 以下构造要保持顺序,若重构时请注意
*/
evlauteAppendSelectCountClause( result );
evlauteAppendFromClause( result );
evlauteAppendWhereClause( result );
return result.toString().trim();
}
private void evlauteAppendSelectClause( StringBuilder result )
{
if ( isNotBlank( selectClause ) )
{
result.append( selectClause );
}
}
private void evlauteAppendSelectCountClause( StringBuilder result )
{
result.append( SELECT )//
.append( SPACE )//
.append( COUNT )//
.append( "(" )//
.append( alias )//
.append( ")" );
}
private void evlauteAppendFromClause( StringBuilder result )
{
result.append( fromClause );
}
private void evlauteAppendJoinClause( StringBuilder result )
{
if ( isNotBlank( joinClause ) )
{
result.append( joinClause );
}
}
private void evlauteAppendWhereClause( StringBuilder result )
{
if ( isNotBlank( whereClause ) )
{
result.append( whereClause );
}
}
private void evlauteAppendOrderByClause( StringBuilder result )
{
if ( isNotBlank( orderByClause ) )
{
result.append( orderByClause );
}
}
/**
* <pre>
* 例如返回一个实体
* addSelectClause("e") "e" 跟new Query() 第二个别名参数要一至。
*
* 例如返回一个查询的VO对象
* addSelectClause("new VO(e.id,e.name)") "e" 跟new Query() 第二个别名参数要一至。
*
* 注意些方法只能调用一次,同一实例调用多次会抛出RepositoryException异常
* </pre>
*
* @param expression JPQL片段
* @return Query
*/
public Query addSelectClause( String expression )
{
checkSelectClauseIsBinded();
selectClause.append( SELECT ).append( SPACE );
if ( isBlank( expression ) )
{
selectClause.append( this.alias );
}
else
{
selectClause.append( expression );
}
return this;
}
private void checkSelectClauseIsBinded()
{
if ( isNotBlank( selectClause ) )
{
throw new RepositoryException( "selectClause已经绑定过不能再定绑定." );
}
}
/**
* <pre>
* 结果将添加如 WHERE e.id=? AND e.name=? 的 JPQL片段
* 例子:
* addWhereAndClause( id!=null, "e.id=?", id )
*
* </pre>
*
* @param canAdd 条件,如果成立 ,JPQL片段将被添加
* @param expression JPQL片段
* @param value 设置的值
* @return Query
*/
public Query addWhereAndClause( boolean canAdd, String expression, Object value )
{
if ( canAdd )
{
return addWhereClause( AND, expression, value, ADD_PARAM );
}
return this;
}
public Query addWhereAndClause( boolean canAdd, String expression )
{
if ( canAdd )
{
return addWhereClause( AND, expression, null, NOT_ADD_PARAM );
}
return this;
}
/**
* 直接添加JPQL片段
*
* @see Query#addWhereOrClause(boolean,
* String, Object)
*/
public Query addWhereAndClause( String expression, Object value )
{
return addWhereClause( AND, expression, value, ADD_PARAM );
}
/**
* 直接添加JPQL片段不带有占位参数
*
* @see Query#addWhereOrClause(boolean,
* String, Object)
*/
public Query addWhereAndClause( String expression )
{
return addWhereClause( AND, expression, null, NOT_ADD_PARAM );
}
/**
* <pre>
* 结果将添加如 WHERE e.id=? OR e.name=? 的 JPQL片段
* 例子:
* addWhereAndClause( id!=null, "e.id=?", id )
*
* </pre>
*
* @param canAdd 条件,如果成立 ,JPQL片段将被添加
* @param expression JPQL片段
* @param value 设置的值
* @return Query
*/
public Query addWhereOrClause( boolean canAdd, String expression, Object value )
{
if ( canAdd )
{
return addWhereClause( OR, expression, value, ADD_PARAM );
}
return this;
}
public Query addWhereOrClause( boolean canAdd, String expression )
{
if ( canAdd )
{
return addWhereClause( OR, expression, null, NOT_ADD_PARAM );
}
return this;
}
/**
* 直接添加JPQL片段
*
* @see Query#addWhereOrClause(boolean,
* String, Object)
*/
public Query addWhereOrClause( String expression, Object value )
{
return addWhereClause( OR, expression, value, ADD_PARAM );
}
/**
* 直接添加JPQL片段, 不添加占位符号查询参数
*
* @see Query#addWhereOrClause(boolean,
* String, Object)
*/
public Query addWhereOrClause( String expression )
{
return addWhereClause( OR, expression, null, NOT_ADD_PARAM );
}
/**
* addWhereClause
*/
private Query addWhereClause( String operation, String expression, Object value, boolean addParam )
{
prepareWhereClause( operation );
whereClause.append( SPACE )//
.append( expression.trim() );//
if ( addParam )
{
paramters.add( value );
}
return this;
}
/**
* <pre>
*
* 结果将添加如 ORDER BY e.id ASC, e.name ASC 的 JPQL片段
* 例子:
* addOrderByAscClause( true, "e.id" )
* addOrderByAscClause( true, "e.name" )
* </pre>
*
* @param canAdd 条件,如果成立 ,JPQL片段将被添加
* @param expression JPQL片段
* @return {@link Query}
*/
public Query addOrderByAscClause( boolean canAdd, String expression )
{
if ( canAdd )
{
return addOrderByAscClause( expression );
}
return this;
}
/**
* 直接添加JPQL语句
*
* @see Query#addOrderByAscClause(boolean,
* String)
*/
public Query addOrderByAscClause( String expression )
{
return addOrderByClause( ASC, expression );
}
/**
* <pre>
*
* 结果将添加如 ORDER BY e.id DESC, e.name DESC 的 JPQL片段
* 例子:
* addOrderByAscClause( true, "e.id" )
* addOrderByAscClause( true, "e.name" )
* </pre>
*
* @param canAdd 条件,如果成立 ,JPQL片段将被添加
* @param expression JPQL片段
* @return {@link Query}
*/
public Query addOrderByDescClause( boolean canAdd, String expression )
{
if ( canAdd )
{
return addOrderByClause( DESC, expression );
}
return this;
}
/**
* 直接添加JPQL语句
*
* @see Query#addOrderByDescClause(boolean,
* String)
*/
public Query addOrderByDescClause( String expression )
{
return addOrderByClause( DESC, expression );
}
private Query addOrderByClause( String opration, String expression )
{
prepareOrderByClause();
orderByClause.append( SPACE ).append( expression )//
.append( SPACE )//
.append( opration );
return this;
}
/**
* <pre>
*
* 添加JOIN语句,例如:
* .addJoinClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addJoinClause( String expression )
{
return addCaseedJoinClause( JOIN, expression );
}
/**
* <pre>
*
* 添加INNER JOIN语句,例如:
* .addInnerJoinClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addInnerJoinClause( String expression )
{
return addCaseedJoinClause( INNER_JOIN, expression );
}
/**
* <pre>
*
* 添加LEFT JOIN语句,例如:
* .addLeftJoinClause("d.sub s") s为join对象后别名
*
* </pre>
*/
public Query addLeftJoinClause( String expression )
{
return addCaseedJoinClause( LEFT_JOIN, expression );
}
/**
* <pre>
*
* 添加RIGHT JOIN语句,例如:
* .addRightJoinClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addRightJoinClause( String expression )
{
return addCaseedJoinClause( RIGHT_JOIN, expression );
}
/**
* <pre>
*
* 添加LEFT OUTER JOIN语句,例如:
* .addLeftOuterJoinClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addLeftOuterJoinClause( String expression )
{
return addCaseedJoinClause( LEFT_OUTER_JOIN, expression );
}
/**
* <pre>
*
* 添加RIGHT OUTER JOIN语句,例如:
* .addRightOuterJoinClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addRightOuterJoinClause( String expression )
{
return addCaseedJoinClause( RIGHT_OUT_JOIN, expression );
}
/**
* <pre>
* 注意fetch也不应该与setMaxResults() 或 setFirstResult()共用,
* 这是因为这些操作是基于结果集的,而在预先抓取集合类时可能包含重复的数据,也就是说无法预先知道精确的行数。
*
* 添加LEFT JOIN FETCH 语句,例如:
* .addLeftJoinFetchClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addLeftJoinFetchClause( String expression )
{
return addCaseedJoinClause( LEFT_JOIN_FETCH, expression );
}
/**
* <pre>
* 注意fetch也不应该与setMaxResults() 或 setFirstResult()共用,
* 这是因为这些操作是基于结果集的,而在预先抓取集合类时可能包含重复的数据,也就是说无法预先知道精确的行数。
*
* 添加LEFT JOIN FETCH 语句,例如:
* .addRightJoinFetchClause("d.sub s") s为join的对象别名
*
* </pre>
*/
public Query addRightJoinFetchClause( String expression )
{
return addCaseedJoinClause( RIGHT_JOIN_FETCH, expression );
}
private Query addCaseedJoinClause( String whatJoin, String expression )
{
joinClause.append( SPACE ).append( whatJoin ).append( SPACE ).append( expression );
return this;
}
private void prepareOrderByClause()
{
if ( isBlank( orderByClause ) )
{
orderByClause.append( SPACE ).append( ORDER_BY );
}
prepareOrderByClauseIfHasCondition();
}
private void prepareOrderByClauseIfHasCondition()
{
if ( orderByClause.length() != new String( SPACE + ORDER_BY ).length() )
{
orderByClause.append( "," );
}
}
private void prepareWhereClause( String operation )
{
if ( isBlank( whereClause ) )
{
whereClause.append( SPACE ).append( WHERE );
}
prepareWhereClauseIfHasCondition( operation );
}
private void prepareWhereClauseIfHasCondition( String operation )
{
if ( whereClause.length() != new String( SPACE + WHERE ).length() )
{
whereClause.append( SPACE ).append( operation );
}
}
private boolean isBlank( CharSequence c )
{
if ( c == null || c.length() == 0 )
{
return true;
}
return false;
}
private boolean isNotBlank( CharSequence c )
{
return !isBlank( c );
}
public Class<?> getEntityClass()
{
return entityClass;
}
public List<Object> getParameters()
{
return paramters;
}
/**
* <pre>
*
* 设置该参数抓取数据库所有数据
* 设置的分页数将无效,改方法不能与setPageNum 跟 setPageSize 同时使用
*
* 设置为true为全部抓取
* 默认为false
*
* </pre>
*
* @param fetch 是否抓取全部数据
* @return
*/
public Query setFetchAllRecords( boolean fetch )
{
this.fetchAllRecords = fetch;
return this;
}
public boolean isFetchAllRecords()
{
return fetchAllRecords;
}
/**
* - page
*/
public Query setPageNum( Integer pageNum )
{
this.fetchAllRecords = false;
this.pageNum = pageNum;
return this;
}
public Query setPageSize( int pageSize )
{
this.fetchAllRecords = false;
this.pageSize = pageSize;
return this;
}
public Integer getPageNum()
{
return ( pageNum == null || pageNum < 1 ) ? 1 : pageNum;
}
public Integer getPageSize()
{
return ( pageSize == null || pageSize < 1 ) ? AppContants.DEFAULT_PAGE_SIZE : pageSize;
}
public Integer getFirstResult()
{
return ( getPageNum() - 1 ) * getPageSize();
}
public Integer getMaxResults()
{
return getPageSize();
}
}
对应的单元测试代码:TestQuery.java
package com.xz.marksix.repository;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.util.List;
import org.junit.Test;
/**
* <pre>
*
* 测试Query生成jpql查询语子
*
* @author XzhiF
* Sep 25, 2013
* </pre>
*/
public class TestQuery
{
private static final String ENTITY_NAME = Object.class.getName();
@Test
public void testMultiEvaluate() throws Exception
{
String expected = "FROM " + ENTITY_NAME + " d WHERE d.name=?";
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( "d.name=?", "abc" );
String result = query.evaluate();
assertEquals( expected, result );
/*
* second evaluate
*/
assertEquals( result, query.evaluate() );
}
@Test
public void testAndOrWhereClause() throws Exception
{
String expected = "FROM " + ENTITY_NAME + " d WHERE d.name=? AND d.id=? OR d.id=? AND d.parent IS NULL";
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( "d.name=?", "abc" )//
.addWhereAndClause( "d.id=?", "abc" )//
.addWhereOrClause( "d.id=?", "aa" )//
.addWhereAndClause( "d.parent IS NULL" );
String result = query.evaluate();
assertEquals( expected, result );
assertEquals( result, query.evaluate() );
assertEquals( new Integer(3), new Integer(query.getParameters().size()) );
}
@Test
public void testOrderByClause() throws Exception
{
String expected = "FROM " + ENTITY_NAME + " d WHERE d.name=? ORDER BY d.id DESC, d.name DESC, d.age ASC";
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( "d.name=?", "abc" )//
.addOrderByDescClause( "d.id" )//
.addOrderByDescClause( "d.name" ).addOrderByAscClause( "d.age" );
String result = query.evaluate();
// System.out.println(result);
assertEquals( expected, result );
}
@Test
public void testJoinsClause() throws Exception
{
String expected = "FROM " + ENTITY_NAME + " d " + //
"JOIN d.sub s" + //
" INNER JOIN d.sub s" + //
" LEFT JOIN d.sub s" + //
" RIGHT JOIN d.sub s" + //
" LEFT OUTER JOIN d.sub s" + //
" RIGHT OUTER JOIN d.sub s" + //
" LEFT JOIN FETCH d.sub s" + //
" RIGHT JOIN FETCH d.sub s";
Query query = new Query( Object.class, "d" )//
.addJoinClause( "d.sub s" )//
.addInnerJoinClause( "d.sub s" )//
.addLeftJoinClause( "d.sub s" )//
.addRightJoinClause( "d.sub s" )//
.addLeftOuterJoinClause( "d.sub s" )//
.addRightOuterJoinClause( "d.sub s" )//
.addLeftJoinFetchClause( "d.sub s" )//
.addRightJoinFetchClause( "d.sub s" );
String result = query.evaluate();
// System.out.println(result);
assertEquals( expected, result );
}
@Test
public void testClauseHasCondition() throws Exception
{
String expected = "FROM " + ENTITY_NAME + " d INNER JOIN d.sub s WHERE d.id=? OR d.name=? ORDER BY o.id ASC, o.name DESC";
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( true, "d.id=?", "1" )//
.addWhereAndClause( false, "d.id=?", "1" )//
.addWhereOrClause( true, "d.name=?", "a" )//
.addWhereOrClause( false, "d.name=?", "b" )//
.addOrderByAscClause( true, "o.id" )//
.addOrderByAscClause( false, "o.id" )//
.addOrderByDescClause( true, "o.name" )//
.addOrderByDescClause( false, "o.name" )//
.addInnerJoinClause( "d.sub s" );
String result = query.evaluate();
// System.out.println(result);
assertEquals( expected, result );
}
@Test
public void testSelectClause() throws Exception
{
String expected = "SELECT d FROM " + ENTITY_NAME + " d";
Query query = new Query( Object.class, "d" )//
.addSelectClause( "d" );//
String result = query.evaluate();
// System.out.println(result);
assertEquals( expected, result );
expected = "SELECT new EntityVO(id,name) FROM " + ENTITY_NAME + " d";
query = new Query( Object.class, "d" )//
.addSelectClause( "new EntityVO(id,name)" );//
result = query.evaluate();
assertEquals( expected, result );
}
@Test
public void testMultiAddSelectClause() throws Exception
{
Query query = new Query( Object.class, "d" )//
.addSelectClause( "d" );
try
{
query.addSelectClause( "a" );
fail( "retry addSelectCluase should throw a exception" );
}
catch ( RepositoryException e )
{
}
}
@Test
public void testWithinParams() throws Exception
{
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( "d.id=?", "1" )//
.addWhereAndClause( "d.id=?", "2" )//
.addWhereOrClause( "d.name=?", "a" )//
.addWhereOrClause( "d.name=?", "b" );//
List<Object> paramters = query.getParameters();
assertEquals( "1", paramters.get( 0 ) );
assertEquals( "2", paramters.get( 1 ) );
assertEquals( "a", paramters.get( 2 ) );
assertEquals( "b", paramters.get( 3 ) );
}
@Test
public void testEvaluateCount() throws Exception
{
String expected = "SELECT COUNT(d) FROM " + ENTITY_NAME + " d WHERE d.id=? AND d.name=?";
Query query = new Query( Object.class, "d" )//
.addWhereAndClause( "d.id=?", "1" )//
.addWhereAndClause( "d.name=?", "a" )//
.addOrderByAscClause( "d.id" )//
.addInnerJoinClause( "d.sub c" )//
.addSelectClause( "d" );
String result = query.evaluateCount();
// System.out.println(result);
assertEquals( expected, result );
}
}