在Grails使用Sql獲取資料
前因
Grails預設情況使用Hibernate作為資料存取的框架。不過Hibernate的缺點是眾所周知的。所以我們在一些複雜的場合需要通過 groovy.sql.Sql 直接使用sql來獲取資料。這樣就會存在如下的問題:
- 如何使用Grails配置的資料庫連線?
- 如何執行sql,進行資料庫相關操作?
- 如何將查詢的資料轉換成Domain Class?
下面就從上面這3個問題來說明如何在grails環境中直接使用sql來對資料庫進行操作。
連線資料庫
連線資料庫相對來說比較簡單,通過如下程式碼就可以完成。
class DoSomethingServices { def dataSource def queryDataWithSql(){ Sql sql = new Sql(dataSource) sql.each("select * from sometable"){ it -> println it } } }
程式碼詳細說明:
- 第3行 注入dataSource, 這個dataSource 就是Grails中在DataSource.groovy中配置的 資料來源。 預設的hibernate也是在使用這個資料來源。
- 第5 行 使用通過注入的dataSource物件 建立sql物件。 關於Sql物件的使用可以參考 http://groovy.codehaus.org/api/groovy/sql/Sql.html
- 第6行 使用each方法執行一個sql語句。然後逐行回撥。
連線資料庫和查詢資料就這麼簡單。
到這裡肯定有人會問,如果需要往sql語句中加入引數怎麼辦。如何避免 Sql注入。 這個Sql在設計的過程中已經考慮到了。而且使用及其簡單。只要使用如下程式碼即可。
def queryDataWithSql(){ Sql sql = new Sql(dataSource) def paramValue = .. def paramValue2 = .. sql.each("select * from sometable where field = ${paramValue} and field2 = ${paramValue2}"){ it -> println it } }
第5行直接使用GString傳入引數。最終執行的時候其實是講GString中得引數獲取出來。通過PreparedStatement傳入引數的方式。這樣可以避免sql的注入的攻擊。你不相信?那就看看Sql.java這個類中得eachRow方法吧。這個方法位於groovy-all-2.1.9-source.jar/groovy/sql/Sql.java 的第1236行。
資料如何轉換成Domain Class物件
這個問題是一個大問題。不過不是沒有辦法。最笨的辦法就是寫成如下的樣子:
sql.each("select field1 , field2 from sometable where field = ${paramValue} and field2 = ${paramValue2}"){ it -> def someDomain = new SomeDomin() someDomain.field1 = it["field1"] someDomain.field2 = it["field2"] }
這個方法最大的缺點是程式碼量多,並且會有大量重複的程式碼。給人感覺很噁心。 在Groovy中又如下的辦法可以對物件的欄位賦值:
def key = "field1" someDomain.getProperties()[key] = "someValue"
getProperties這個方法將該物件的所有值放到一個Map中返回。具體可參考http://groovy.codehaus.org/groovy-jdk/java/lang/Object.html#getProperties%28%29 對這個map進行賦值,就等於對這個物件進行賦值。 所以下面我只要有一個欄位和變數名對應的map,什麼就會搞定了。 於是有了如下的程式碼:
class DomainClassInfoService { def sessionFactory def grailsApplication def getDomainClass(clazzName) { return grailsApplication.domainClasses.find { it.name == clazzName } } def getFieldColumnMap(clazz) { def fieldColumnMap = [:] def hibernateMetaClass = sessionFactory.getClassMetadata(clazz) def grailsDomainClass = getDomainClass(clazz.getSimpleName()) def domainProps = grailsDomainClass.getProperties() domainProps.each { prop -> //get the property's name def propName = prop.getName() //please refer to the hibernate javadoc //http://www.hibernate.org/hib_docs/v3/api/org/hibernate/persister/entity/AbstractEntityPersister.html def columnProps = hibernateMetaClass.getPropertyColumnNames(propName) if (columnProps && columnProps.length > 0) { //get the columnname, which is stored into the first array def columnName = columnProps[0] fieldColumnMap[propName] = columnName } } return fieldColumnMap } }
- 以上程式碼說明如下:
-
- 5 ~ 6 行注入將要使用的兩個服務,一個是hibernate的sessionFactory, 另外一個是grailsApplication 上下文
- 7 ~ 9 這個方法是根據給定的段類名。比如有一個Domain Class的全名為 org.gunn.domain.Book 這裡的clazzName 就是Book。 * 第 8 行是從grailsApplication中獲取所有Domain Class的DefaultGrailsDomainClass這個類的物件。這裡牽涉到一個Artefact的概念,請參考 https://grails.org/Developer+-+Artefact+API
- 12 ~ 28 行就是 根據Domain Class中的變數來獲取資料庫對應的的欄位名。 有程式碼在這裡就不多解釋了。
結合我們上面的那個properties的小技巧,我們就使用如下程式碼來完成使用Sql查詢資料,轉換成Domain Class的物件。
String querySql = ''' select * from table where field1 = ? ''' def tripSegmentFieldColumnMap = domainClassInfoService.getFieldColumnMap(SomeDomain) Sql sql = new Sql(dataSource) sql.eachRow(querySql, field1Value){ SomeDomain someObject = new SomeDomain() tripSegmentFieldColumnMap.each { key, value -> someObject.getProperties()[key] = it[value] } }
這個方法對於非關係的,沒有太大問題。如果有類似於一對多這樣的關係的話,會引起hibernate中著名的n+1的問題。例如SomeDomain 中有一個變數是SomeParent, 並且SomeDomain belong to 這個SomeParent的話。那麼像上面那樣直接賦值就會引起去發起資料庫查詢請求查詢SomeParent的。所以可以使用如下的方式進行避免:
sql.eachRow(querySql, field1Value){ SomeDomain someObject = new SomeDomain() SomeParent someParent = new SomeParent() someParent.id = it.parentId tripSegmentFieldColumnMap.each { key, value -> if(key != "parentId") someObject.getProperties()[key] = it[value] } someObject.parent = someParent
這個辦法很土,如果你有更好的。歡迎分享!謝謝!
相關文章
- 在SQL隱碼攻擊中使用DNS獲取資料SQLDNS
- 在 Laravel 中使用 GraphQL 一 [獲取資料]Laravel
- Grails通過sessionId獲取session物件AISession物件
- 在Oracle資料庫中使用XML資料獲取業務資訊XHOracle資料庫XML
- 獲取oracle 系統資料的sqlOracleSQL
- datatables使用ajax獲取資料
- 使用SQL語句從資料庫一個表中隨機獲取資料SQL資料庫隨機
- SQL Server 怎麼在分頁獲取資料的同時獲取到總記錄數SQLServer
- 獲取資料庫空閒空間的SQL資料庫SQL
- 教你如何使用API介面獲取資料!API
- 怎麼在ajax外邊使用ajax裡面在後端獲取的資料後端
- SQL Server在分頁獲取資料的同時獲取到總記錄數的兩種方法SQLServer
- 在ActionForm中如何獲取session中的資料?ORMSession
- 使用RxJava從多個資料來源獲取資料RxJava
- ckeditor獲取資料
- Grails 使用MySQL例子AIMySql
- 如何使用API介面獲取淘寶商品資料API
- 如何獲取sql server資料庫版本初識之一SQLServer資料庫
- Mysql在資料插入後立即獲取插入的IdMySql
- 在登入資料庫的使用!sql資料庫SQL
- modbustcp封裝使用獲取裝置資料示例TCP封裝
- 使用**迭代器**獲取Cifar等常用資料集
- 如何教會小白使用API介面獲取商品資料API
- 如何使用js獲取USB掃碼槍資料JS
- 使用商品詳情API介面獲取商品資料API
- 使用Paging Library獲取網路資料
- 轉:使用基本認證從WebServer獲取資料WebServer
- Scrapy爬蟲 - 獲取知乎使用者資料爬蟲
- 使用Python獲取HTTP請求頭資料PythonHTTP
- 使用 useLazyFetch 進行非同步資料獲取非同步
- 獲取Wireshark資料流
- 1.獲取資料
- Modbus ASCII 獲取資料ASCII
- 【原】獲取SQLServer的最完整資料字典的SQL語句SQLServer
- 【轉】通過sql語句獲取資料庫的基本資訊SQL資料庫
- API介面在電商商品資料獲取中的應用API
- 在RFT中如何獲取JTable中的所有資料?
- 在安卓 4.4.4 的機器上,獲取 app 冷熱啟動的資料無法獲取 WaitTime 資料安卓APPAI