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

时间:2021-06-24 16:52:41

I have the code below in Play for Scala to access a SAP Hana table with Hibernate. I need to implement the same code with MySql, but the problem is that MySql doesn't support sequences (it works with AUTO_INCREMENT columns) and the code breaks because I have to specify @SequenceGenerator for Hana. Is there a way to compile this code with a condition to exclude the @SequenceGenerator annotation, so it works for MySql and Hana at the same time?

下面的代码是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 = _
}

5 个解决方案

#1


2  

This answer attempts to implement Eugene's suggestion (so if it works please give credit to Eugene).

这一回答试图实现尤金的建议(因此,如果可行,请将功劳归于尤金)。

Given the following definition of @ifNotMysql macro

给出了下面的@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
}

if we write @ifNotMysql @GeneratedValue(...) @SequenceGenerator like so

如果我们像这样写@ifNotMysql @GeneratedValue(…)@SequenceGenerator的话

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

and provide system property target-mysql like so

并像这样提供系统属性

sbt -Dtarget-mysql=true compile

then @SequenceGenerator annotation will be excluded and @GeneratedValue(strategy = GenerationType.IDENTITY) added like so

然后将会排除@SequenceGenerator注释,并添加@GeneratedValue(strategy = GenerationType.IDENTITY)

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

This implementation is based on scalamacros/sbt-example-paradise

这个实现基于scalamacros/sbt-example-paradise。

#2


2  

Assuming I understand your problem correctly I have 2 potential solutions for you. Both are general ideas and you'll have to do some legwork to actually implement them.

假设我正确地理解了你的问题,我有两个可能的解决方案。这两个都是一般的想法,你需要做一些实际的工作来实现它们。

  1. Use macros. Here is a 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. Here is a way pass parameters to your macro at compile time. Main con with this route is that macro api was scala version dependent, somewhat messy, unstable and hard to find good documentation for last time I checked.

    使用宏。这里有一篇有点旧的文章,它做了一些AST操作来丰富case类。你应该能为你的案子做点什么。这里有一种在编译时将参数传递给宏的方法。这种方法的主要缺点是,宏api依赖于scala版本,有点混乱,不稳定,上次检查时很难找到好的文档。

  2. Use AspectJ. You should be able to declare annotations you need on classes in build-time. Main con here is that you'll have to add AspectJ weaving to your build which may or may not be easy.

    使用AspectJ。应该能够在构建时对类声明所需的注释。这里的主要问题是,您必须在构建中添加AspectJ编织,这可能容易,也可能不容易。

#3


1  

Probably not what you want to hear but AFAIK there's no way to conditionally include annotations. An alternative would be to include the common functionality in a @MappedSuperclass and inject the concrete instance as appropriate at build time depending on the environment. Something like this:-

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

#4


0  

I think the way to do it is to provide a custom 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 implementation). And you can do nothing in other methods and let the FallbackInterpreter do it's usual job.

我认为实现它的方法是提供一个自定义IdGeneratorStrategyInterpreter并使用metadatabuilder . applyidgenerationtype解释器注册它。在您的自定义idgeneratorstrategy解释器中,您可以重写决定性生成名,以返回生成类型的“identity”常量。如果您知道代码是针对MySql运行的,并且在所有其他情况下返回null,以便让fallback解释器执行其默认工作(字符串“identity”也来自fallbackinterpretation . determinegeneratorname实现)。在其他方法中你什么都做不了让回退解释器做它通常的工作。

P.S. Please also note that Hibernate's default SequenceStyleGenerator is actually aware of DBs not supporting "sequences" (exposed via Dialect.supportsSequences) and is able to emulate similar behavior using additional table. This might or might not be OK for your scenario.

请注意,Hibernate默认的SequenceStyleGenerator实际上意识到DBs不支持“序列”(通过辩证法. supportssequence公开),并且能够使用附加的表模拟类似的行为。这对于您的场景可能是合适的,也可能不是合适的。

#5


0  

If ID in mysql is given auto-increment then in hibernate mapping ID should be IDENTITY

如果mysql中的ID是自动递增的,那么hibernate中的映射ID应该是标识

Replace that with

取代,

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

 }

Hope it works....

希望它是....

#1


2  

This answer attempts to implement Eugene's suggestion (so if it works please give credit to Eugene).

这一回答试图实现尤金的建议(因此,如果可行,请将功劳归于尤金)。

Given the following definition of @ifNotMysql macro

给出了下面的@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
}

if we write @ifNotMysql @GeneratedValue(...) @SequenceGenerator like so

如果我们像这样写@ifNotMysql @GeneratedValue(…)@SequenceGenerator的话

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

and provide system property target-mysql like so

并像这样提供系统属性

sbt -Dtarget-mysql=true compile

then @SequenceGenerator annotation will be excluded and @GeneratedValue(strategy = GenerationType.IDENTITY) added like so

然后将会排除@SequenceGenerator注释,并添加@GeneratedValue(strategy = GenerationType.IDENTITY)

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

This implementation is based on scalamacros/sbt-example-paradise

这个实现基于scalamacros/sbt-example-paradise。

#2


2  

Assuming I understand your problem correctly I have 2 potential solutions for you. Both are general ideas and you'll have to do some legwork to actually implement them.

假设我正确地理解了你的问题,我有两个可能的解决方案。这两个都是一般的想法,你需要做一些实际的工作来实现它们。

  1. Use macros. Here is a 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. Here is a way pass parameters to your macro at compile time. Main con with this route is that macro api was scala version dependent, somewhat messy, unstable and hard to find good documentation for last time I checked.

    使用宏。这里有一篇有点旧的文章,它做了一些AST操作来丰富case类。你应该能为你的案子做点什么。这里有一种在编译时将参数传递给宏的方法。这种方法的主要缺点是,宏api依赖于scala版本,有点混乱,不稳定,上次检查时很难找到好的文档。

  2. Use AspectJ. You should be able to declare annotations you need on classes in build-time. Main con here is that you'll have to add AspectJ weaving to your build which may or may not be easy.

    使用AspectJ。应该能够在构建时对类声明所需的注释。这里的主要问题是,您必须在构建中添加AspectJ编织,这可能容易,也可能不容易。

#3


1  

Probably not what you want to hear but AFAIK there's no way to conditionally include annotations. An alternative would be to include the common functionality in a @MappedSuperclass and inject the concrete instance as appropriate at build time depending on the environment. Something like this:-

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

#4


0  

I think the way to do it is to provide a custom 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 implementation). And you can do nothing in other methods and let the FallbackInterpreter do it's usual job.

我认为实现它的方法是提供一个自定义IdGeneratorStrategyInterpreter并使用metadatabuilder . applyidgenerationtype解释器注册它。在您的自定义idgeneratorstrategy解释器中,您可以重写决定性生成名,以返回生成类型的“identity”常量。如果您知道代码是针对MySql运行的,并且在所有其他情况下返回null,以便让fallback解释器执行其默认工作(字符串“identity”也来自fallbackinterpretation . determinegeneratorname实现)。在其他方法中你什么都做不了让回退解释器做它通常的工作。

P.S. Please also note that Hibernate's default SequenceStyleGenerator is actually aware of DBs not supporting "sequences" (exposed via Dialect.supportsSequences) and is able to emulate similar behavior using additional table. This might or might not be OK for your scenario.

请注意,Hibernate默认的SequenceStyleGenerator实际上意识到DBs不支持“序列”(通过辩证法. supportssequence公开),并且能够使用附加的表模拟类似的行为。这对于您的场景可能是合适的,也可能不是合适的。

#5


0  

If ID in mysql is given auto-increment then in hibernate mapping ID should be IDENTITY

如果mysql中的ID是自动递增的,那么hibernate中的映射ID应该是标识

Replace that with

取代,

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

 }

Hope it works....

希望它是....