2016-02-01 28 views
15

Pracuję nad aplikacją Scala z Postgres 9.3 i Slick 3.1.1. Otrzymuję Null Pointer Exception w śliskim sterowniku, gdy wiele kwerend wykonuje się w tym samym czasie.Wyjątek NullPointerException podczas wykonywania równoczesnych zapytań przy użyciu aplikacji Slick

Oto mój uproszczony kod. Tworzę wielu aktorów, którzy będą wywoływać tę samą metodę, aby zapytać z bazy danych.

package com.app.repo 

import java.sql.Timestamp 

import akka.actor.{Actor, ActorSystem, Props} 
import slick.driver.PostgresDriver 
import slick.driver.PostgresDriver.api._ 
import scala.concurrent.ExecutionContext.Implicits.global 
import scala.concurrent.duration.FiniteDuration 
import scala.util.{Failure, Success} 

case class SampleData(id: Long, name: String, createDate: java.sql.Timestamp) 

object Tables extends { 
    val profile = PostgresDriver 
} with Tables 

trait Tables { 
    val profile: PostgresDriver 

    import profile.api._ 

    class SampleDataTable(_tableTag: Tag) extends Table[SampleData](_tableTag, Some("processing"), "SampleData") { 
    def * = (id, name, createDate) <>(SampleData.tupled, SampleData.unapply) 

    def ? = (Rep.Some(id), Rep.Some(name), Rep.Some(createDate)).shaped.<>({ r => import r._; _1.map(_ => SampleData.tupled((_1.get, _2.get, _3.get))) }, (_: Any) => throw new Exception("Inserting into ? projection not supported.")) 

    val id: Rep[Long] = column[Long]("SampleId", O.AutoInc, O.PrimaryKey) 
    val name: Rep[String] = column[String]("Name") 
    val createDate: Rep[java.sql.Timestamp] = column[java.sql.Timestamp]("CreateDate") 
    } 

    lazy val sampleDataTable = new TableQuery(tag => new SampleDataTable(tag)) 
} 

class SampleQueryingActor(delay: FiniteDuration, duration: FiniteDuration) extends Actor { 

    import scala.concurrent.duration._ 

    override def preStart() = { 
    context.system.scheduler.schedule(0.second, duration, self, "tick") 
    } 

    override def receive: Receive = { 
    case "tick" => { 
     println("tick received.. ") 
     //val range = 1 until 1000 
     RepositoryImpl.reader.onComplete({ 
     case Success(r) => println(s"got sum as ${r.getOrElse(0)}") 
     case Failure(ex) => ex.printStackTrace() 
     }) 

    } 
    } 
} 

object DriverHelper { 
    val user = "postgres" 
    val url = "jdbc:postgresql://192.168.1.50:5432/MyDatabase" 
    val password = "password" 
    val jdbcDriver = "org.postgresql.Driver" 
    val db: PostgresDriver.backend.DatabaseDef = Database.forURL(url, user = user, password = password, driver = jdbcDriver) 
} 

object RepositoryImpl { 
    val db: PostgresDriver.backend.DatabaseDef = DriverHelper.db 

    val now = new Timestamp(System.currentTimeMillis()) 

    def reader = { 
    db.run(Tables.sampleDataTable.filter(_.createDate > now).map(_.id).sum.result) 
    } 

    def insertBatchRecords(list: List[SampleData]) = { 
    db.run(Tables.sampleDataTable ++= list) 
    } 

} 

object PGConnectionTester extends App { 

    import scala.concurrent.duration._ 

    val sys = ActorSystem("sys") 
    sys.actorOf(Props(classOf[SampleQueryingActor], 1.seconds, 10.seconds)) 
    sys.actorOf(Props(classOf[SampleQueryingActor], 1.seconds, 10.seconds)) 
    sys.actorOf(Props(classOf[SampleQueryingActor], 1.seconds, 10.seconds)) 
} 

Kiedy wykonać powyższy kod, pojawia się błąd jak poniżej:

java.lang.NullPointerException 
    at slick.jdbc.DriverDataSource.getConnection(DriverDataSource.scala:98) 
    at slick.jdbc.DataSourceJdbcDataSource.createConnection(JdbcDataSource.scala:64) 
    at slick.jdbc.JdbcBackend$BaseSession.conn$lzycompute(JdbcBackend.scala:415) 
    at slick.jdbc.JdbcBackend$BaseSession.conn(JdbcBackend.scala:414) 
    at slick.jdbc.JdbcBackend$SessionDef$class.prepareStatement(JdbcBackend.scala:297) 
    at slick.jdbc.JdbcBackend$BaseSession.prepareStatement(JdbcBackend.scala:407) 
    at slick.jdbc.StatementInvoker.results(StatementInvoker.scala:33) 
    at slick.jdbc.StatementInvoker.iteratorTo(StatementInvoker.scala:22) 
    at slick.jdbc.Invoker$class.first(Invoker.scala:31) 
    at slick.jdbc.StatementInvoker.first(StatementInvoker.scala:16) 
    at slick.driver.JdbcActionComponent$QueryActionExtensionMethodsImpl$$anon$3.run(JdbcActionComponent.scala:228) 
    at slick.driver.JdbcActionComponent$SimpleJdbcDriverAction.run(JdbcActionComponent.scala:32) 
    at slick.driver.JdbcActionComponent$SimpleJdbcDriverAction.run(JdbcActionComponent.scala:29) 
    at slick.backend.DatabaseComponent$DatabaseDef$$anon$2.liftedTree1$1(DatabaseComponent.scala:237) 
    at slick.backend.DatabaseComponent$DatabaseDef$$anon$2.run(DatabaseComponent.scala:237) 
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
    at java.lang.Thread.run(Thread.java:745) 

Aktor będzie wywołać tę samą metodę co 10 sekund. Jednakże, Otrzymuję ten błąd tylko po raz pierwszy. Następnie kwerendy są wykonywane poprawnie. Nie jestem w stanie zrozumieć, dlaczego tak się dzieje. W tym przykładowym przypadku są tylko niektóre proste operacje odczytu. Ale w moim przypadku, ponieważ zapytanie nie działa, niektóre dane są tracone bez prawidłowego przetwarzania. Czy ten błąd ma związek z pulą połączeń?

+2

Spójrz tutaj: https://github.com/slick/slick/issues/1400 – Dima

+2

Dzięki @Dima. Zastosowałem łączenie HikariCP, a na razie problem został rozwiązany. –

+0

Nie .. Ponownie otrzymuję ten sam błąd nawet po użyciu Hikari. :( –

Odpowiedz

0

Wystarczy udostępnienia informacji dla nikogo innego w obliczu tego problemu.

Wystąpił błąd dotyczący samego Slicka. Zostało zgłoszone here. Git user, mustajavi naprawił to i został połączony z najnowszym oddziałem Slick. W najnowszej aktualizacji 3.1.1 problem został rozwiązany.

Powiązane linki w GitHub:

https://github.com/slick/slick/pull/1401

https://github.com/slick/slick/pull/1445

+0

Nadal dostaję błąd w wersji 3.1.1 –

3

Wydaje mi się, że znalazłeś numer this. Spróbuj użyć leniwy val dla db więc tylko inicjuje raz:

object DriverHelper { 
    val user = "postgres" 
    val url = "jdbc:postgresql://192.168.1.50:5432/MyDatabase" 
    val password = "password" 
    val jdbcDriver = "org.postgresql.Driver" 
    lazy val db: PostgresDriver.backend.DatabaseDef = Database.forURL(url, user = user, password = password, driver = jdbcDriver) 
} 
+0

hmm, ponieważ uzyskuje dostęp do 'db' z wielu wątków, tworzenie bazy danych prawdopodobnie powinno być również zsynchronizowane? –

+0

dobrze, slick sam zarządza synchronizacją, szczególnie poprzez db.run() konstrukt – vitalii

+0

Wystąpił błąd z samym Slick. Zostało to naprawione i połączone z wersją 3.1.1 dwa dni temu. Oto link: https://github.com/slick/slick/pull/1401 –