Wie bereits in einem vorherigen Beitrag erwähnt, handhabt Hibernate HQL Queries unter Berücksichtigung der Gross/Kleinschreibung. Der Nachteil dieses Fakts bekahm ich gestern zu spüren als ich einige Funktionen programmierte, die Blogeinträge auslesen sollten.
Die Funktionalität die umgesetzt werden sollte, war ein guter alter Pageturner: Es werden maximal 10 Eintäge auf der Startseite angezeigt werden und durch Links kann man vor- und zurückblättern.
<cfif local.offset>
<p><a href="#buildURL('blog.entries#local.queryString#&offset=#local.offsetNewer#')#" title="neuere">neuere</a></p>
</cfif>
<cfif local.entryCount GT local.offsetOlder>
<p><a href="#buildURL('blog.entries#local.queryString#&offset=#local.offsetOlder#')#" title="ältere">ältere</a></p>
</cfif>
Um die Links korrekt ein- und ausblenden zu können, soll die komplette Anzahl Links (der local.entryCount) anhand der übergebenen Parameter ermittelt werden. Es gibt also zwei Funktionen im Service: Eine list und eine count Funktion - beide machen eigentlich dasselbe:
<cffunction name="count" returntype="numeric" access="public" output="false" hint="Zählt die Anzahl Einträge">
<cfset local.ormResult = executeQuery(executionMode='count',argumentCollection=arguments) />
<cfset local.result = 0 />
<cfif arrayLen(local.ormResult)>
<cfset local.result = local.ormResult[1] />
</cfif>
<cfreturn local.result />
</cffunction>
<cffunction name="list" returntype="array" access="public" output="false" hint="Liest Einträge aus">
<cfreturn executeQuery(executionMode='default',argumentCollection=arguments) />
</cffunction>
Die Magie geschieht in den executeQuery und prepareHQL Methoden. Diese parsen als erstes die argumentCollection und führen das HQL - Query aus.
<cffunction name="executeQuery" returntype="array" access="private" output="false" hint="Führt ein HQL Query aufgrund der übergebenen Argumente aus">
<cfargument name="executionMode" type="string" required="false" default="default" hint="Welche Art von Query ausgeführt werden soll: count, default, search" />
<cfset local.hql = prepareHQL(argumentCollection=arguments) />
<cfreturn ormExecuteQuery(local.hql.statement,local.hql.args,false,local.hql.options) />
</cffunction>
<cffunction name="prepareHQL" returntype="struct" access="private" output="false" hint="Generiert das HQL Statement">
<cfargument name="executionMode" type="string" required="false" default="default" hint="Welche Art von Query ausgeführt werden soll: count, default" />
<cfargument name="search" type="string" required="false" default="" hint="Freitext nach dem gesucht werden soll" />
<cfargument name="ident" type="guid" required="false" hint="Schlüssel des Eintrags" />
<cfargument name="isActive" type="boolean" required="false" hint="Ob der Eintrag aktiv ist" />
<cfargument name="Title" type="string" required="false" hint="Titel des Eintrags" />
<cfargument name="urlTitle" type="string" required="false" hint="Url sicherer Titel" />
<cfargument name="Teaser" type="string" required="false" hint="Einleitung des Eintrags" />
<cfargument name="Body" type="string" required="false" hint="Inhalt des Eintrags" />
<cfargument name="Download" type="string" required="false" hint="Downloadlink" />
<cfargument name="Website" type="string" required="false" hint="URL zu einer Webseite" />
<cfargument name="GeoLocation" type="string" required="false" hint="Karten URL" />
<cfargument name="postedAt" type="any" required="false" hint="Veröffentlichungs-Zeitpunkt" />
<cfargument name="postedFrom" type="any" required="false" hint="Ab Veröffentlichungs-Zeitpunkt" />
<cfargument name="postedTo" type="any" required="false" hint="Bis Veröffentlichungs-Zeitpunkt" />
<cfargument name="postedBy" type="any" required="false" hint="Verfasser" />
<cfargument name="joinCategoryArgs" type="struct" required="false" default="#structNew()#" hint="Bedingungen die auf die verknüpften Kategorien zutreffen müssen" />
<cfargument name="orderBy" type="string" required="false" default="postedAt DESC" hint="Sortierreihenfolge der Resultate" />
<cfargument name="offset" type="numeric" required="false" default="0" hint="Offset ab welchem Einträge geliefert werden sollen" />
<cfargument name="maxResults" type="numeric" required="false" hint="Maximale Anzahl an Einträgen die ausgelesen werden sollen" />
<cfset local.hql.select = arrayNew(1) />
<cfset local.hql.joins = arrayNew(1) />
<cfset local.hql.group = arrayNew(1) />
<cfset local.hql.conditions = arrayNew(1) />
<cfset local.hql.sort = arrayNew(1) />
<cfset local.hql.args = structNew() />
<cfset local.hql.options = structNew() />
<cfset arrayAppend(local.hql.select,'SELECT') />
<cfset arrayAppend(local.hql.conditions,'WHERE') />
<cfset arrayAppend(local.hql.group,'GROUP BY') />
<cfset arrayAppend(local.hql.sort,'ORDER BY') />
<cfif arguments.executionMode EQ 'default'>
<cfloop list="offset,maxResults" index="local.option">
<cfif structKeyExists(arguments,local.option)>
<cfset local.hql.options[local.option] = arguments[local.option] />
</cfif>
</cfloop>
</cfif>
<cfset arrayAppend(local.hql.group,'main') />
<cfloop list="#arguments.orderBy#" index="local.sort">
<cfset arrayAppend(local.hql.sort,lCase('main.#local.sort#')) />
</cfloop>
<cfloop collection="#arguments#" item="local.arg">
<cfif isNull(arguments[local.arg])>
<cfset structDelete(arguments,local.arg) />
</cfif>
</cfloop>
<cfif arguments.executionMode EQ 'count'>
<cfset arrayAppend(local.hql.select,'COUNT(main)') />
<cfelse>
<cfset arrayAppend(local.hql.select,'main') />
</cfif>
<cfset arrayAppend(local.hql.select,'FROM blog_main AS main') />
<cfif len(arguments.search)>
<cfset arrayAppend(local.hql.conditions,'1=1') />
<cfif structKeyExists(arguments,'isActive')>
<cfset arrayAppend(local.hql.conditions,'AND main.isactive = :isactive') />
<cfset local.hql.args.isactive = arguments.isActive />
</cfif>
<cfset arrayAppend(local.hql.conditions,'AND ( 1=2') />
<cfset local.hql.args.search = '%#arguments.search#%' />
<cfloop list="Title,urlTitle,Teaser,Body,Download,Website,GeoLocation" index="local.local.property">
<cfset arrayAppend(local.hql.conditions,'OR main.#lCase(local.property)# LIKE :search') />
</cfloop>
<cfset arrayAppend(local.hql.conditions,')') />
<cfelse>
<cfset arrayAppend(local.hql.conditions,'1=1') />
<cfloop collection="#arguments#" item="local.arg">
<cfset local.arg = lCase(local.arg) />
<cfswitch expression="#local.arg#">
<cfcase value="joincategoryargs">
<cfif structCount(arguments.joinCategoryArgs)>
<cfset arrayAppend(local.hql.joins,'LEFT JOIN main.categories AS cat') />
<cfloop collection="#arguments.joinCategoryArgs#" item="local.joinArg">
<cfset local.joinArg = lCase(local.joinArg) />
<cfswitch expression="#local.joinArg#">
<cfcase value="isactive">
<cfset arrayAppend(local.hql.conditions,'AND cat.#local.joinArg# = :cat#local.joinArg#') />
<cfset local.hql.args['cat#local.joinArg#'] = arguments.joinCategoryArgs[local.joinArg] />
</cfcase>
<cfcase value="urlboard">
<cfset arrayAppend(local.hql.conditions,'AND cat.#local.joinArg# LIKE :cat#local.joinArg#') />
<cfset local.hql.args['cat#local.joinArg#'] = '%#arguments.joinCategoryArgs[local.joinArg]#%' />
</cfcase>
</cfswitch>
</cfloop>
</cfif>
</cfcase>
<cfcase value="postedat">
<cfif isDate(arguments.postedAt)>
<cfset local.hql.args.postedat = arguments.postedAt />
<cfelse>
<cfset local.hql.args.postedat = lsParseDateTime(arguments.postedAt) />
</cfif>
<cfset arrayAppend(local.hql.conditions,'AND main.postedat = :postedat') />
</cfcase>
<cfcase value="postedfrom">
<cfif isDate(arguments.postedFrom)>
<cfset local.hql.args.postedfrom = arguments.postedFrom />
<cfelse>
<cfset local.hql.args.postedfrom = lsParseDateTime(arguments.postedFrom) />
</cfif>
<cfset arrayAppend(local.hql.conditions,'AND main.postedat >= :postedfrom') />
</cfcase>
<cfcase value="postedto">
<cfif isDate(arguments.postedTo)>
<cfset local.hql.args.postedto = arguments.postedTo />
<cfelse>
<cfset local.hql.args.postedto = lsParseDateTime(arguments.postedTo) />
</cfif>
<cfset arrayAppend(local.hql.conditions,'AND main.postedat <= :postedto') />
</cfcase>
<cfcase value="postedby">
<cfset local.hql.args.postedby = arguments.postedBy />
<cfif isSimpleValue(arguments.postedBy)>
<cfset local.hql.args.postedby = entityLoadByPk('sys_user',arguments.postedBy) />
</cfif>
<cfset arrayAppend(local.hql.conditions,'AND main.postedby = :postedby') />
</cfcase>
<cfdefaultcase>
<cfif listFindNoCase('Ident,isActive,Title,urlTitle,Teaser,Body,Download,Website,GeoLocation',local.arg)>
<cfset arrayAppend(local.hql.conditions,'AND main.#local.arg# = :#local.arg#') />
<cfset local.hql.args[local.arg] = arguments[local.arg] />
</cfif>
</cfdefaultcase>
</cfswitch>
</cfloop>
</cfif>
<cfset local.hql.statement = ' />
<cfloop list="select,joins,conditions,group,sort" index="local.part">
<cfset local.hql.statement = listAppend(local.hql.statement,arrayToList(local.hql[local.part],' '),' ') />
</cfloop>
<cfreturn local.hql />
</cffunction>
Das Problem welches sich eingestellt hat, ist diese vermaledeite Gross/Kleinschreibung. ColdFusion übergibt Strukturen teilweise Uppercase - da ist natürlich mit camelCase Properties in den persistenten Komponenten Essig.
Die Moral von der Geschichte: lowercase für alle Properties - ausser jemand kennt da ne elegantere Lösung?!