How to conditionally include a Hibernate annotation?

mysql scala hibernate playframework hana

631 观看

5回复

603 作者的声誉

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?

@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 = _
}
作者: ps0604 的来源 发布者: 2017 年 12 月 27 日

回应 5


2

23461 作者的声誉

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:-

@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 = _
}
作者: Steve Chambers 发布者: 2018 年 5 月 30 日

2

9565 作者的声誉

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.

  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.

作者: Eugene Loy 发布者: 2018 年 5 月 30 日

0

21808 作者的声誉

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.

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.

作者: SergGr 发布者: 2018 年 6 月 2 日

2

16076 作者的声誉

决定

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

Given the following definition of @ifNotMysql macro

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(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

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

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

作者: Mario Galic 发布者: 2018 年 6 月 3 日

0

117 作者的声誉

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

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....

作者: Jyoti Jadhav 发布者: 2018 年 6 月 5 日
32x32