如何有条件地包含 Hibernate 注释?

How to conditionally include a Hibernate annotation?

我在 Play for Scala 中有下面的代码,可以使用 Hibernate 访问 SAP Hana table。我需要用 MySql 实现相同的代码,但问题是 MySql 不支持序列(它适用于 AUTO_INCREMENT 列)并且代码中断,因为我必须指定 @SequenceGenerator 给哈娜。有没有一种方法可以在排除 @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 = _
}

可能不是您想听到的,但 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 = _
}

假设我正确理解了您的问题,我为您提供了 2 个可能的解决方案。两者都是一般性想法,您必须做一些跑腿工作才能实际实施它们。

  1. 使用宏。这是 bit old article that does some AST manipulation to enrich case classes. You should be able to do something in that vein for your case. 是一种在编译时将参数传递给宏的方法。这条路线的主要缺点是宏 api 依赖于 scala 版本,有点混乱,不稳定并且很难找到我上次检查时的好文档。

  2. 使用 AspectJ。您应该能够 declare 在构建时 类 上需要的注释。这里的主要缺点是您必须将 AspectJ 编织添加到您的构建中,这可能容易也可能不容易。

我认为实现它的方法是提供自定义 IdGeneratorStrategyInterpreter and register it using MetadataBuilder.applyIdGenerationTypeInterpreter. In your custom IdGeneratorStrategyInterpreter you can override determineGeneratorName to return "identity" constant for GenerationType.SEQUENCE if you know that the code is run against MySql and return null in all other cases to let the FallbackInterpreter do its default job (the string "identity" also comes from FallbackInterpreter.determineGeneratorName 实现)。你可以用其他方法什么都不做,让 FallbackInterpreter 做它通常的工作。

P.S。另请注意,Hibernate 的默认 SequenceStyleGenerator is actually aware of DBs not supporting "sequences" (exposed via Dialect.supportsSequences) 并且能够使用额外的 table 来模拟类似的行为。这可能适用于您的情况,也可能不适用。

此答案试图实现 (因此,如果可行,请感谢 Eugene)。

给定下面的@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

如果 mysql 中的 ID 是自动递增的,那么在休眠映射中 ID 应该是 IDENTITY

将其替换为

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

 }

希望有用....