2013-05-08 36 views
0

我正在編寫一個導出器,它將從數據庫中獲取結果並將每個記錄寫入逗號分隔文件。不同的查詢將爲其創建不同的工作者,因爲他們需要編寫單獨的csv文件。首先,我將任務分解爲兩個不同的角色。 Actor1是一個JdbcWorker,用於查詢數據庫提供的查詢參數,Actor2是一個CSVWriter,它接收表示需要附加到CSV的查詢結果的大小寫類。我的第一個問題是,儘管我喜歡這兩個工作人員提供的關注點的分離,但將jdbc查詢從CSV編寫器中解耦出來的設計很好嗎?scala + jdbc + case class + actors設計混淆

所以,我寫了actor1如下:

class DataQueryWorker(csvExporterWorker: ActorRef) extends Actor with ActorLogging{ 

    private implicit def ModelConverter(rs: ResultSet): QueryModel = { 
    QueryModel(
     id = rs.getString(0), 
     name = rs.getString(1), 
     age = rs.getString(2), 
     gender = rs.getString(3)) 
} 

    private def sendModelToCsvWorker(model: QueryModel): Unit = { 
    csvExporterWorker ! model 
    } 

    private def startExport[T](queryString: String)(resultFunc: T => Unit)(implicit ModelConverter: ResultSet => T): Unit = { 
    try { 
     val connection = DriverManager.getConnection(DbConfig.connectionString, 
     DbConfig.user, 
     DbConfig.password) 
     val statement = connection.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY) 
     statement.setFetchSize(Integer.MIN_VALUE) 
     val rs = statement.executeQuery(queryString) 
     while (rs.next()) { 
     resultFunc(ModelConverter(rs)) 
     } 
    } catch { 
     case e: Exception => //What to do in case of an exception??? 
    } 
    } 

    override def receive() = { 
    case startEvent => startExport(DbConfig.ModelExtractionQuery)(sendModelToCsvWorker) 
    } 

} 

我的下一個問題是,上面寫的代碼,查詢數據庫,模型中的包,並把結果發送給正確的方法CSVWorker?我不確定我是否正確地遵循scala習語。另外,在這種情況下處理異常的正確方法是什麼?

這將是很好的一些指導。

謝謝

+1

有一點要注意的在你目前的IMPL是要打開一個連接,但不關閉它。一定要關閉它,否則最終會在數據庫服務器上累積連接。 – cmbaxter 2013-05-08 12:03:26

回答

2

我覺得你的方法是確定一個微小的改動:

對於DB演員,你可能要考慮使這些長期生活的演員,彙集Router後面。讓這個演員舉行Connection,因爲它的狀態,啓動時一旦打開它,然後關閉因故障重啓在重新啓動的情況下。我認爲這可能是一種更好的方法,因爲您並不總是需要打開連接才能調用導出數據。您只需編寫一些代碼,以便在調用連接之前檢查連接的狀態(並重新連接)。

一旦你做出了DB演員狀態和長壽命,你將無法通過構造函數傳遞CSVWorker英寸你應該通過消息傳遞給這個actor,表明你想要導出。你可以做的是通過一個案例類,像這樣:

case class ExportQuery(query:String, csvWorker:ActorRef) 

更改receive看起來像這樣:

def receive = { 
    case ExportQuery(query, csvWorker) => 
    ... 
} 

最後,刪除try/catch邏輯。除非你可以根據這個失敗做一些有意義的事情(比如調用一些替代代碼路徑),否則它沒有任何意義。讓演員失敗並重新啓動(並關閉/重新打開連接)並繼續。

+0

謝謝。這非常有幫助。 – 2013-05-09 05:48:55

-1

我覺得在這裏使用演員可能是矯枉過正。

當您想要安全地使用多線程操作可變狀態時,參與者非常有用。但是,就你而言,你說每個查詢寫入一個單獨的CSV文件(所以每個CSV文件只有一個線程)。我不認爲CSVWorker演員是必要的。它甚至可能是有害的,因爲如果DBWorker明顯比CSVWorker快得多,那麼參與者郵箱可能會增長並消耗大量內存。

個人而言,我只是直接調用CSV編寫器。

關於問題分離的問題取決於您是否期望此代碼在不相關的上下文中被重用。如果您想要將JDBC工作人員與其他作者一起使用,那麼這可能是值得的(儘管有一派思想認爲您最好等到重構之前需要出現的時候 - 您不需要它或YAGNI)。否則,你可能會更好地簡化。

如果您決定直接將JDBC代碼附加到CSV代碼,那麼您可能還想要取出案例類轉換。再一次,如果這是將在其他地方重複使用的代碼,那麼最好保留它。

異常處理取決於您的應用程序,但在Scala(與Java不同)中,如果您不知道如何處理異常,則可能不應該執行任何操作。把try..catch塊取出來,然後讓異常傳播 - 一些東西會抓住它並報告它。

Java迫使你處理異常,這在理論上是一個好主意,但實際上往往會導致錯誤處理代碼無法真正使用(重新拋出或更糟糕,吞嚥錯誤)。

哦,如果你正在編寫很多將ResultSets變成case類的代碼,反之亦然,你可能需要使用Object Relation Mapping框架,比如Slick或Squeryl。它們針對這個用例進行了優化。

+1

如果DataQueryWorker的每個實例都有它自己的CSVWriter的情況下,行被髮送過來,因爲他們正在遍歷的,那麼你不會有處理行的任何問題出故障了。演員將連續處理它的郵箱;這是最大的好處之一。 – cmbaxter 2013-05-08 12:02:30

+0

「當您想要安全地在多個線程之間共享對象時,參與者非常有用。」 < - 你是什麼意思,顯然演員模型不是關於線程之間共享對象。 – 2013-05-08 23:16:37

+0

@Viktor巴生:你說的沒錯,演員避免共用對象 - 這就是他們的魅力。我已經編輯它來指代可變狀態,這就是我最初(笨拙地)試圖說的。 – 2013-05-09 13:40:36