【问题标题】:How to conditionally include a Hibernate annotation?如何有条件地包含 Hibernate 注释?
【发布时间】:2018-06-08 07:02:00
【问题描述】:

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

【问题讨论】:

  • 我有同样的问题你找到解决办法了吗?

标签: mysql scala hibernate playframework hana


【解决方案1】:

可能不是您想听到的,但 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】:

    假设我正确理解您的问题,我有 2 个可能的解决方案供您参考。两者都是一般的想法,您必须做一些工作才能真正实现它们。

    1. 使用宏。这是一个bit old article,它进行了一些 AST 操作以丰富案例类。你应该能够为你的情况做一些事情。 Here 是一种在编译时将参数传递给宏的方法。这条路线的主要缺点是宏 api 依赖于 scala 版本,有点混乱,不稳定,而且我上次检查时很难找到好的文档。

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

    【讨论】:

    • 我已尝试实施您的建议并将其作为答案提供。我希望没关系。如果不能随意将代码粘贴到您的答案中,我可以删除我的。 stackoverflow.com/questions/47995473/…
    • @MarioGalic 我没问题。
    【解决方案3】:

    我认为这样做的方法是提供一个自定义IdGeneratorStrategyInterpreter 并使用MetadataBuilder.applyIdGenerationTypeInterpreter 注册它。在您的自定义 IdGeneratorStrategyInterpreter 中,您可以覆盖 determineGeneratorName 以返回 "identity" 常量为 GenerationType.SEQUENCE 如果您知道代码是针对 MySql 运行并在所有其他情况下返回 null 以让 FallbackInterpreter 执行其默认设置作业(字符串"identity" 也来自FallbackInterpreter.determineGeneratorName 实现)。你可以用其他方法什么都不做,让FallbackInterpreter做它的日常工作。

    附:另请注意,Hibernate 的默认 SequenceStyleGenerator 实际上知道 DB 不支持“序列”(通过 Dialect.supportsSequences 公开),并且能够使用附加表模拟类似的行为。这可能适合您的方案,也可能不适合。

    【讨论】:

      【解决方案4】:

      此答案尝试实现Eugene's suggestion(因此,如果可行,请感谢 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

      【讨论】:

      • @GeneratedValue也需要根据编译是在Hana还是MySql上来改变,看来你的解决方案只处理@SequenceGenerator
      • 如果环境是 MySql 则应插入以下行而不是示例中的行:@GeneratedValue(strategy = GenerationType.IDENTITY)
      • 马里奥,在MySQL中插入一行会报错,你确定在设置MySQL的时候替换成GenerationType.IDENTITY
      • @ps0604 错误是什么?可能是因为我没有给@GeneratedValue提供generator参数,也就是@GeneratedValue(strategy = GenerationType.IDENTITY, generator = "generator")。我已编辑答案,请重试。
      • 顺便说一句,如果我在 MySQL 中编译删除 Hana 注释,这在 @GeneratedValue(strategy = GenerationType.IDENTITY) 和没有 generator 参数的情况下可以正常工作。
      【解决方案5】:

      如果 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 = _
      
       }
      

      希望它有效....

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-03-15
        • 1970-01-01
        • 2014-12-11
        • 1970-01-01
        • 2011-02-04
        • 2016-02-12
        • 2019-03-27
        • 2020-12-29
        相关资源
        最近更新 更多