Search code examples
scalahibernateanonymous-class

JPA and "anonymous" classes in scala


I'm a bit stuck and don't understand what's going on. This one doesn't work

@Entity
@DynamicInsert
@DynamicUpdate
@SelectBeforeUpdate
@Table
class Entity {
  @Column(nullable = false)
  var owner: String = _
}


    val myEntity = new Entity() {
            owner = "some owner 1"
          }
      session.persist(myEntity)

Hibernate throws exception:

java.lang.IllegalArgumentException: Unknown entity:persistence.dao.EntityDaoTest$$anonfun$13$$anonfun$14$$anon$5
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777)

This one works:

val myEntity = new Entity()
entity.owner = "some owner 1"
session.persist(myEntity)

Why? Why does hibernate don't recognize my Entity instance?

UPD: @Sheinbergon, thanks, it's clear. I completely forgot that annotations are lost. Is there any possibility to set entity fields with some shortcut? writing

val myEntity = new MyEntity()
myEntity.owner = "some owner"
myEntity.someOtherProperty = "value"

is super boring

One more question This one works:

val parent = new Parent
    parent.owner = "Our parent"
    parent.addChild(new Child() {
      name = "First parent's child"
      addGrandChild(new GrandChild() {
        name = "Grand child name"
        addGrandGrandChild(new GrandGrandChild() {
          name = "Grand Grand child name"
          address = new Address() {
            id = 1L
          }
        })
      })
    })

Why? Child, GrandChild, GrandGrandChild also created anonymously. addChild, addGrandChild, addGrandGrandChild are just list mutators.

def addChild(child: Child): Unit = {
    if (children == null) {
      children = new util.ArrayList[Child]()
    }
    if (Option(child.parent).isEmpty) {
      child.parent = this
    }
    children.add(child)
  }

Solution

  • What you are doing here is instantiating a class anonymously in Scala , and well... that creates an anonymous implementation of your class Entity ( like instantiating an interface anonymously in Java).

    you can see it by printing the class name - println(myEntity.getClass) in both cases

    Annotations applied to the original class do not apply to the anonymous one (reflection can still find them in the super class, but that's up to the code scanning them) and I guess that's why you're getting the various JPA exceptions

    In response to your added sub-questions

    • Regarding a shortcut - why don't you use companion objects for factories or turn this class into a case class (with defaults), allowing for nicer, more flexible initialization.
    • Regarding the second object graph(and assuming eachof your classes are annotated) - again it depends on how the reflective code treats the objects it scans. it's possible ( and more likely, given that it won't scan each member of the collection for annotations ) it takes annotation definitions from the erased type ( possible to get it's FQDN class name as ParameterizedType in Java's reflection API) of the collection and not from the actual members of the collection and that's why it works. I'm not really sure what it does about field definitions though (they are only present in the "super" class), but there's no "magic" here, just plain old reflection scans.