scala: czytanie tabel

scala: czytanie tabel
Julian_
  • Rejestracja:około 8 lat
  • Ostatnio:ponad 4 lata
  • Postów:1703
0

Jak scalą pobieracie tabele z bazy?
Jak to zrobić bardziej scalowo?

Kopiuj

  classOf[org.postgresql.Driver]
  val table = "dbo.countries"

  override def findAll: Seq[CountriesDto] = {
    var rows = ListBuffer[CountriesDto]()
    try {
      val sql = "SELECT * FROM ?"
      val stmt = conn.prepareStatement(sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY)
      stmt.setString(1, table)
      val rs = stmt.executeQuery()
      while (rs.next) {
        rows += CountriesDto(rs.getString(0), rs.getString(1), rs.getBoolean(2))
      }
    }
    finally {
      conn.close()
    }
    rows.toSeq
  }

Podoba mi się rozwiązanie sparkownikowe, ale nie chcę sparka zaciągać tylko do tego.

Kopiuj
spark.read
  .jdbc("jdbc:postgresql:dbserver", table, conn)
edytowany 1x, ostatnio: Julian_
DQ
  • Rejestracja:prawie 10 lat
  • Ostatnio:7 miesięcy
  • Postów:141
2

Użyj jakiejkolwiek z powszechnie używanych bibliotek:

Czego nie wybierzesz to będzie "bardziej scalowe" i po prostu ładniejsze. Ręczne rzeźbienie tego co ktoś już zrobił nie ma sensu.

Julian_
no tylko nie wiem którą wybrać właśnie... a może da się ten zwykły jdbc co napisałem jakoś upiększyć
DQ
Mimo wszystko polecałbym zapoznanie się z jakąś biblioteką skoro chcesz sobie klepać w Scali, zwróci się w przyszłości. Zapewne najłatwiej Ci będzie z scalikejdbc bo jego API jest dość proste i powinno być w miarę podobne do tego co aktualnie już znasz. Jeśli chcesz troszkę bardziej "scalowo" ale nadal dość prosto to Quill. Slick może być troszkę bardziej wymagający, ale ma książkę dość dobrze tłumaczącą co i jak. Doobie/Skunk są trudniejszymi bibliotekami, wymagającymi znajomości FP i ogólnie ekosystemu Scali.
semicolon
  • Rejestracja:ponad 5 lat
  • Ostatnio:prawie 5 lat
  • Postów:114
2

JDBC sam w sobie nie jest zły. Do prostych rzeczy wystarczy, a do złożonych jest najlepszy.

W Twoim przypadku próbowałbym rozdzielić kod budujący stmt od kodu, który skanuje wynik.

Nie mniej sam JDBC będzie sprawiał Ci większe kłopoty, gdy będziesz chciał robić coraz więcej zadań:

  • budować zapytania z dynamicznie zmieniającymi się kryteriami
  • gdy będziesz robić powtarzalne złączenia, warunki, unie
  • gdy w projekcie będziesz miał dużo tabel i gdy często będziesz je zmieniać

Dobry mapper takie rzeczy wychwyci dość sprawnie, ale sam w sobie utrudni Ci budowanie złożonych zapytań, czasem będziesz musiał obchodzić na około to co normalnie mógłbyś wyciągnąć używając jdbc.

Nie znam najlepszej odpowiedzi na to pytanie, ale wiem, że docelowo możesz:

  • łączyć oba podejścia
  • uprzeć się jak osioł i iść w samo jdbc o ile pokryjesz zapytania testami
  • można zepchnąć zapytania z bazą na język, który ma najlepiej rozwinięty ORM
  • rozważyć inną bazę lub nawet brak bazy :D
Zobacz pozostałe 30 komentarzy
Wibowit
Po prostu cały czas zastanawiam się co miał znaczyć komentarz jest nonblocking i nie korzysta z JDBC i to jest ten rok 2020? :-) - semicolon dziś, 10:52 ? W Project Loom zwykły blokujący kod JDBC staje się nieblokujący, gdy odpali się go na Virtual Thread, tzn będzie blokował lekki wirtualny wątek, zamiast tego ciężkiego rzeczywistego, a takie blokowanie nie jest już problemem (wirtualnych wątków można napykać miliony, rzeczywiste trzeba trzymać w małej puli).
semicolon
Byłem ciekaw czemu użycie ORM w 2020 to zły pomysł.
Wibowit
Bo jest niewypałem? Pomiędzy JDBC, a ORM są właśnie takie wynalazki jakie wymienił @DisQ w poście powyżej (mam na myśli biblioteki Slick i Quill). Masz otypowane mechanizmy do tworzenia zapytań do bazy (wprost kontrolujesz jakie to mają być zapytania i jaka ma być ich liczba), ale nie ma żadnych skomplikowanych persistence managerów czy wytrychów typu JPQL.
semicolon
A z czego to wynika? Inaczej, z czego wynika to, że ludzie przestawiają się na prostsze narzędzia. Akurat to mnie ciekawi, bo korpo-świat to jednak takie tłuściejsze rozwiązania preferuje typu springi, angulary. Jak to się stało teraz inaczej piszecie? Ja nie pracuje za często z ORM, bo nie leży mi przyjmowanie wyniku jako klasy, ale trochę rozumiem świat railsów, django, php i to całe bazowanie na frameworkach i ormach.
Wibowit
Nie przypominam sobie by dla Scali był jakiś typowy ORM. Od zawsze używało się jakichś scalowych bibliotek typu właśnie Slick. ORMy są w zasadzie oparte na mutowalności, a Scala ma ambicje łączyć OOP z FP, więc z mutowalnością ma nie po drodze.
semicolon
  • Rejestracja:ponad 5 lat
  • Ostatnio:prawie 5 lat
  • Postów:114
0

@Wibowit jakbyś napisał taką unię w slicku?

Masz tabelkę główną i 3 tabelki podtypy (jako podtyp rozumiem tabelkę, której pk to fk do tabelki głównej). Chcę wykonać na tym union, chcę uzyskać w wyniku kolumny z tabelki głównej, ale też chcę wyprowadzić z pozostałych tabelek dodatkową kolumnę pod wspólnym aliasem. Jak taka rzecz zrobiłbyś w slicku?

Dołączam w załączniku opis tego co rozumiem za podtyp.

W razie potrzeb daj znać to napiszę cały przykład w SQL.

edytowany 4x, ostatnio: semicolon
Wibowit
  • Rejestracja:około 20 lat
  • Ostatnio:około 2 godziny
4

@semicolon:

chcę wyprowadzić z pozostałych tabelek dodatkową kolumnę pod wspólnym aliasem

Aliasów na potrzeby zapytania nie ma sensu definiować w Slicku, bo operuje się np na krotkach (i Slick sam tworzy swoje aliasy przy tworzeniu zapytania). Są sobie oficjalne testy https://github.com/slick/slick/blob/6e20f96bba0d84e9bb755b489b0bd73d422ca039/slick-testkit/src/main/scala/com/typesafe/slick/testkit/tests/UnionTest.scala gdzie pokazane jest kilka scenariuszy, np union między dwiema tabelami o różnych kolumnach:

Kopiuj
   class Managers(tag: Tag) extends Table[(Int, String, String)](tag, "managers") {
    def id = column[Int]("id")
    def name = column[String]("name")
    def department = column[String]("department")
    def * = (id, name, department)
  }
  lazy val managers = TableQuery[Managers]

  class Employees(tag: Tag) extends Table[(Int, String, Int)](tag, "employees") {
    def id = column[Int]("id")
    def name = column[String]("name2")
    def manager = column[Int]("manager")
    def * = (id, name, manager)

    // A convenience method for selecting employees by department
    def departmentIs(dept: String) = manager in managers.filter(_.department === dept).map(_.id)
  }
  lazy val employees = TableQuery[Employees]

  def managersQuery = for(m <- managers filter { _.department === "IT" }) yield (m.id, m.name)

  def employeesQuery = for(e <- employees filter { _.departmentIs("IT") }) yield (e.id, e.name)

  val managersData = Seq(
    (1, "Peter", "HR"),
    (2, "Amy", "IT"),
    (3, "Steve", "IT")
  )

  val employeesData = Seq(
    (4, "Jennifer", 1),
    (5, "Tom", 1),
    (6, "Leonard", 2),
    (7, "Ben", 2),
    (8, "Greg", 3)
  )

  def testBasicUnions = {

    val q1 = managersQuery
    val q2 = employeesQuery
    val q3 = (q1 union q2).sortBy(_._2.asc)
    val q4 = managers.map(_.id)
    val q4b = q4 union q4
    val q4c = q4 union q4 union q4
    val q5 = managers.map(m => (m.id, 0)) union employees.map(e => (e.id, e.id))

    (for {
      _ <- (managers.schema ++ employees.schema).create
      _ <- managers ++= managersData
      _ <- employees ++= employeesData
      _ <- mark("q1", q1.result).map(r => r.toSet shouldBe Set((2,"Amy"), (3,"Steve")))
      _ <- mark("q2", q2.result).map(r => r.toSet shouldBe Set((7,"Ben"), (8,"Greg"), (6,"Leonard")))
      _ <- mark("q3", q3.result).map(_ shouldBe List((2,"Amy"), (7,"Ben"), (8,"Greg"), (6,"Leonard"), (3,"Steve")))
      _ <- mark("q4b", q4b.result).map(r => r.toSet shouldBe Set(1, 2, 3))
      _ <- mark("q4c", q4c.result).map(r => r.toSet shouldBe Set(1, 2, 3))
      _ <- mark("q5", q5.result).map(r => r.toSet shouldBe Set((7,7), (6,6), (2,0), (4,4), (3,0), (8,8), (5,5), (1,0)))
    } yield ()) andFinally (managers.schema ++ employees.schema).drop
  }

Jak widać można sobie filtrować, mapować, robić unię i sortować:

Kopiuj
  def managersQuery = for(m <- managers filter { _.department === "IT" }) yield (m.id, m.name)
  def employeesQuery = for(e <- employees filter { _.departmentIs("IT") }) yield (e.id, e.name)
  val q1 = managersQuery
  val q2 = employeesQuery
  val q3 = (q1 union q2).sortBy(_._2.asc)

PS: gdybym miał pisać coś od zera to pewnie wypróbowałbym Quill. Slick miejscami wygląda na przekombinowanego i trochę walczyłem z typami jak chcialem tworzyć abstrakcje (np podmiana JdbcProfiles, abstrakcje na tworzenie, czyszczenie, usuwanie tabel, itd już nawet nie pamiętam, bo dawno nie kombinowałem nic w Slicku).


"Programs must be written for people to read, and only incidentally for machines to execute." - Abelson & Sussman, SICP, preface to the first edition
"Ci, co najbardziej pragną planować życie społeczne, gdyby im na to pozwolić, staliby się w najwyższym stopniu niebezpieczni i nietolerancyjni wobec planów życiowych innych ludzi. Często, tchnącego dobrocią i oddanego jakiejś sprawie idealistę, dzieli od fanatyka tylko mały krok."
Demokracja jest fajna, dopóki wygrywa twoja ulubiona partia.
edytowany 1x, ostatnio: Wibowit
semicolon
Bardzo dziękuję, to jest super przykład. Powinien wisieć w dokumentacji tej biblioteki.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.