Как условно включить аннотацию 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 потенциальных решения для вас. Оба являются общими идеями, и вам придется выполнять некоторые работы, чтобы их реализовать.
-
Используйте макросы. Вот немного старой статьи, которая делает некоторые манипуляции с АСТ, чтобы обогатить классы случаев. Вы должны быть в состоянии сделать что-то в этом ключе для своего дела. Вот способ передачи параметров вашему макросу во время компиляции. Основной конфликт с этим маршрутом заключается в том, что макро api был зависимым от версии scala, несколько грязным, неустойчивым и трудно найти хорошую документацию в последний раз, когда я проверял.
-
Используйте 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 = _
}
Надеюсь, что это работает....