scala: czytanie tabel

0

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


  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.

spark.read
  .jdbc("jdbc:postgresql:dbserver", table, conn)
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.

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
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.

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:

   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ć:

  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).

1 użytkowników online, w tym zalogowanych: 0, gości: 1