Использование класса case для анализа данных, содержащих перечисление, в ScalikeJdbc и ScalaJson

Мне нужно прочитать определенные значения перечисления (кроме других обычных вещей, включая некоторые необязательные нулевые значения) из базы данных MySQL в Scala. Я использую ScalikeJdbc для запроса MySQL и анализа набора результатов. Я не могу найти упоминания об анализе типов перечислений в это документация (возможно, мне нужно копать Глубже).

Но мое целевое приложение должно иметь оба способа загрузки данных: базу данных и файлы JSON. Поэтому я также использую ScalaJson (Play Framework) для анализа файлов Json для чтения точно такие же данные, как и из базы данных MySQL.

Я знаю, что и ScalikeJdbc, и ScalaJson поддерживают синтаксический анализ данных с использованием классов case в Scala для автоматического преобразования.


Мои вопросы:

  • Поддерживает ли ScalikeJdbc синтаксический анализ значений Enum нравится ScalaJson?
  • Can the same case class be used for parsing data in both ScalikeJdbc and ScalaJson or do they need to be different? My case class in question would contain parameters of custom types, which in turn are case classes themselves. These case classes accept parameters of following types apart from Enum values (basically, they don't contain parameter of exactly same types, but the level of complexity would be identical):
    • Int, String
    • Option[Int], Option[String]
    • Option[Seq[ (Int, String, Option[Int], Option[String]) ]]
  • От перечислений можно полностью отказаться, добавив дополнительную ручную проверку, хотя конечный результат может быть далеко не таким аккуратным. Тем не менее, в целом, полезно ли вообще использовать Enums (особенно при чтении данных из базы данных или файлов JSON / XML) или накладные расходы на добавление перечислений слишком велики. много, чтобы оправдать их преимущества?

person y2k-shubham    schedule 01.02.2018    source источник
comment
Хотя я отказался от Enums в своих case classes, мне все равно нужно использовать одни и те же case classes для ScalikeJdbc и ScalaJson; companion object этих case classes должны расширять SQLSyntaxSupport[T] (согласно ScalikeJdbc); который создает неоднозначную ошибку ссылки, когда я добавляю в них методы Json reads[T], writes[T] и т. д.   -  person y2k-shubham    schedule 13.02.2018
comment
Хотя я описал, как я добился использования одних и тех же case classes для ScalikeJdbc и ScalaJson, анализ Enums с использованием ScalikeJdbc (и сопоставление их с Enum, определенным в коде Scala) все еще остается открытым вопросом.   -  person y2k-shubham    schedule 26.02.2018


Ответы (1)


Как сказано в комментарии к вопросу, я отказался от чтения Enums непосредственно из базы данных MySQL или JSON. Очевидно, что эти поля теперь являются простыми VARCHAR в базе данных, и я перенес логику проверки в свой Scala код. Поскольку я выполняю проверку отдельно, case classes, которые я использую для хранения данных, считанных из MySQL или JSON, больше не имеют полей типа Enum.Value.

Я бы сказал, что было бы гораздо логичнее иметь Enums непосредственно в базе данных; но из-за отсутствия простого решения я выбрал этот обходной путь. Это все еще открытый вопрос, и я обновлю ответ, как только найду решение.


Переходя к другой части моего вопроса

Можно ли использовать один и тот же класс case для анализа данных как в ScalikeJdbc, так и в ScalaJson, или они должны быть разными?

Да

Я создал case classes с companion objects и могу использовать их для автоматического преобразования как в ScalikeJdbc, так и в ScalaJson. Я публикую полный case class вместе с companion object, который служит этой двойной цели.

Примечание. Этот пример кода взят из фреймворка, предназначенного для перемещения MySQL таблиц из одного места в другое. [фактический производственный код]

Кейс-класс:

case class Table(dbId: Option[Int] = None,
                 id: Option[Int] = None,
                 name: String,
                 shouldCopy: Option[Boolean] = None,
                 lastUpdated: Option[DateTime] = None
                )

Компаньон-объект:

object Table {

  private def mapResultToTable(rs: WrappedResultSet): Table = Table(
    dbId = rs.intOpt("db_id"),
    id = rs.intOpt("id"),
    name = rs.string("name"),
    shouldCopy = rs.booleanOpt("should_copy"),
    lastUpdated = rs.dateTimeOpt("last_updated").flatMap { zonedDateTime: ZonedDateTime =>
      Some(DateTime.parse(zonedDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)).plusMinutes(330))
    }
  )

  def getTables(db: Db)(implicit session: DBSession): Seq[Table] = {
    sql"""
       SELECT
         *
       FROM
         db_name.tables
       WHERE
         db_id = ${db.id} AND
         should_copy = b'1'
       ORDER BY
         name ASC
    """.
      map(mapResultToTable).
      list().
      apply()
  }

  // for Play-Json [Scala-Json]
  implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")
  implicit val dateTimeWriter = JodaWrites.jodaDateWrites("dd/MM/yyyy HH:mm:ss")

  implicit val reads = Json.reads[Table]
  implicit val writes = Json.writes[Table]
}

Вот объяснение для определенных частей кода:

  • def mapResultToTable(rs: WrappedResultSet)

    Этот метод считывает набор результатов запроса к базе данных и создает object из case class

  • def getTables(db: Db)

    Этот метод запрашивает MySQL базу данных, используя ScalikeJdbc

  • implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")

  • implicit val dateTimeJsReader = JodaReads.jodaDateReads("yyyyMMddHHmmss")

    Это Joda DateTime преобразователи чтения-записи (используемые ScalaJson) для параметра Joda DateTime case class

  • implicit val reads = Json.reads[Table]

  • implicit val writes = Json.writes[Table]

    Это преобразователи чтения-записи для данного case class


Наконец, вот фрагмент кода для чтения данных этого класса case из файла Json.

val tableOpt: Option[Table] = try {
  val rawJsonString: String = Source.fromFile("path/to/file/fileName.json").mkString
  Some(Json.parse(rawJsonString).as[Table])
} catch {
  case ex: Throwable =>
    println(s"Exception while reading Json file: '$ex'")
    None
}

И последний совет для нубов вроде меня: если вы не знакомы с ScalaJson (и синтаксическим анализом JSON в целом), избегайте использования метода Json.parse(jsonString).asOpt[T] (в приведенном выше фрагменте используется метод .as[T]), чтобы преодолеть тонкие такие ошибки, как эта.


EDIT-1

Поддерживает ли ScalikeJdbc синтаксический анализ значений Enum, таких как ScalaJson?

Получение Enum.Value на самом деле не имеет ничего общего с ScalikeJdbc: если у вас есть String, насколько сложно преобразовать его в Enum.Value?

Вот небольшой пример:

// Enum
object CloudProvider extends Enumeration {

  val AWS: Value = Value("aws")
  val MicrosoftAzure: Value = Value("microsoft-azure")
  val GoogleCloud: Value = Value("google-cloud")
}

// Case Class
case class SiteInfo(website: String, provider: CloudProvider.Value)

// Mapper method
def mapResult(rs: WrappedResultSet): SiteInfo = {
  val website: String = rs.string("website")
  val provider: CloudProvider.Value = CloudProvider.values.
    find(_.toString == rs.string("provider")).
    getOrElse(CloudProvider.AWS)

  SiteInfo(website, provider)
}

Трудно понять, почему я не мог видеть ответ, когда он был прямо передо мной: (методmapResult)


.. в общем, стоит ли вообще использовать Enums..

Да, абсолютно

Весь смысл использования типа Enum заключается в ограничении диапазона значений, которые может принимать переменная. Я не вижу более простого способа обеспечить соблюдение таких правил, кроме Enum.

Кроме того, если у вас есть достаточное ноу-хау языка/библиотеки, которую вы используете (в то время я мало знал Scala и ScalikeJdbc), для использования Enum не будет абсолютно никаких накладных расходов (с точки зрения кодирования); на самом деле они делают код намного понятнее. Излишне говорить, что даже с точки зрения производительности использование Enums намного лучше, чем использование String.

person y2k-shubham    schedule 22.02.2018