Как условно включить аннотацию Hibernate?

У меня есть код ниже в Play for Scala для доступа к таблице SAP Hana с Hibernate. Мне нужно реализовать тот же код с MySql, но проблема в том, что MySql не поддерживает последовательности (он работает с столбцами AUTO_INCREMENT), а код разбивается, потому что я должен указать @SequenceGenerator для Hana. Есть ли способ скомпилировать этот код с условием исключения аннотации @SequenceGenerator, поэтому он работает одновременно с MySql и Hana?

@Entity
@Table(name = "clients")
class ClientJpa {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
    @SequenceGenerator(name="generator", sequenceName = "cliSeq", allocationSize = 1)    
    var surrogateKey: Int = _
    var code: String = _
    var name: String = _
}

Ответ 1

Этот ответ пытается реализовать предложение Юджина (поэтому, если он работает, отдайте должное Юджину).

Учитывая следующее определение макроса @ifNotMysql

import scala.reflect.macros.blackbox
import scala.language.experimental.macros
import scala.annotation.{StaticAnnotation, compileTimeOnly}

object ifNotMysqlMacro {
  val targetIsMySql = sys.props.get("target-mysql").contains("true")

  def impl(c: blackbox.Context)(annottees: c.Expr[Any]*): c.Expr[Any] = {
    import c.universe._

    def mysqlAnnots(annotations: Seq[c.universe.Tree]): Seq[c.universe.Tree] =
      annotations
        .filterNot(_.toString.contains("SequenceGenerator"))
        .filterNot(_.toString.contains("GeneratedValue"))
        .:+(q"""new GeneratedValue(strategy = GenerationType.IDENTITY)""")

    val result = annottees.map(_.tree).toList match {
      case q"@..$annots var $pat: $tpt = $expr" :: Nil =>
        q"""
            @..${if (targetIsMySql) mysqlAnnots(annots) else annots}
            var $pat: $tpt = $expr
          """
    }
    c.Expr[Any](result)
  }
}

@compileTimeOnly("enable macro paradise to expand macro annotations")
class ifNotMysql extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ifNotMysqlMacro.impl
}

если мы напишем @ifNotMysql @GeneratedValue(...) @SequenceGenerator например, так

@ifNotMysql 
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
@SequenceGenerator(name="generator", sequenceName = "cliSeq", allocationSize = 1)    
var surrogateKey: Int = _

и предоставить системное свойство target-mysql например, так

sbt -Dtarget-mysql=true compile

то аннотация @SequenceGenerator будет исключена, и @GeneratedValue(strategy = GenerationType.IDENTITY) добавляется так же

@GeneratedValue(strategy = GenerationType.IDENTITY)
var surrogateKey: Int = _

Эта реализация основана на scalamacros/sbt-example-paradise

Ответ 2

Наверное, не то, что вы хотите услышать, но AFAIK нет никакого способа условно включать аннотации. Альтернативой будет включение общей функциональности в @MappedSuperclass и приведение конкретного экземпляра в зависимости от времени сборки в зависимости от среды. Что-то вроде этого:-

@MappedSuperclass
abstract class AbstractClientJpa {
    var surrogateKey: Int   // abstract
    var code: String = _
    var name: String = _
}

...

@Entity
@Table(name = "clients")
class HanaClientJpa extends AbstractClientJpa {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "generator")
    @SequenceGenerator(name="generator", sequenceName = "cliSeq", allocationSize = 1)    
    var surrogateKey: Int = _
}

...

@Entity
@Table(name = "clients")
class MySQLClientJpa extends AbstractClientJpa {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var surrogateKey: Int = _
}

Ответ 3

Предполагая, что я правильно понимаю вашу проблему, у меня есть 2 потенциальных решения для вас. Оба являются общими идеями, и вам придется выполнять некоторые работы, чтобы их реализовать.

  1. Используйте макросы. Вот немного старой статьи, которая делает некоторые манипуляции с АСТ, чтобы обогатить классы случаев. Вы должны быть в состоянии сделать что-то в этом ключе для своего дела. Вот способ передачи параметров вашему макросу во время компиляции. Основной конфликт с этим маршрутом заключается в том, что макро api был зависимым от версии scala, несколько грязным, неустойчивым и трудно найти хорошую документацию в последний раз, когда я проверял.

  2. Используйте AspectJ. Вы должны иметь возможность объявлять аннотации, которые вам нужны в классах во время сборки. Главное, что вам нужно добавить в свою сборку AspectJ, что может быть или не быть легким.

Ответ 4

Я думаю, что способ сделать это - создать пользовательский IdGeneratorStrategyInterpreter и зарегистрировать его с помощью MetadataBuilder.applyIdGenerationTypeInterpreter. В пользовательских IdGeneratorStrategyInterpreter вы можете переопределить determineGeneratorName вернуть "identity" константу GenerationType.SEQUENCE, если вы знаете, что код выполняется на MySql и возвращать null во всех остальных случаях, чтобы позволить FallbackInterpreter сделать его по умолчанию задание (строка "identity" также происходит от реализации FallbackInterpreter.determineGeneratorName). И вы ничего не можете сделать в других методах и позволить FallbackInterpreter выполнять обычную работу.

PS Также обратите внимание, что Hibernate по умолчанию SequenceStyleGenerator фактически знает о DB, которые не поддерживают "последовательности" ( Dialect.supportsSequences через Dialect.supportsSequences) и могут эмулировать подобное поведение, используя дополнительную таблицу. Это может быть или не совсем нормально для вашего сценария.

Ответ 5

Если идентификатору в mysql задан автоинкремент, то в идентификаторе сопоставления спящего режима должна быть IDENTITY

Замените это на

@Entity
@Table(name="clients")
class ClientJpa{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY, generator = "generator")
  var surrogateKey: Int = _
  var code: String = _
  var name: String = _

 }

Надеюсь, что это работает....