How to use Hibernate @Any-related annotations?

27,007

Solution 1

Hope this article brings some light to the subject:

Sometimes we need to map an association property to different types of entities that don't have a common ancestor entity - so a plain polymorphic association doesn't do the work.

For example let's assume three different applications which manage a media library - the first application manages books borrowing, the second one DVDs, and the third VHSs. The applications have nothing in common. Now we want to develop a new application that manages all three media types and reuses the exiting Book, DVD, and VHS entities. Since Book, DVD, and VHS classes came from different applications they don't have any ancestor entity - the common ancestor is java.lang.Object. Still we would like to have one Borrow entity which can refer to any of the possible media type.

To solve this type of references we can use the any mapping. this mapping always includes more than one column: one column includes the type of the entity the current mapped property refers to and the other includes the identity of the entity, for example if we refer to a book it the first column will include a marker for the Book entity type and the second one will include the id of the specific book.

@Entity
@Table(name = "BORROW")
public class Borrow{

    @Id
    @GeneratedValue
    private Long id;

    @Any(metaColumn = @Column(name = "ITEM_TYPE"))
    @AnyMetaDef(idType = "long", metaType = "string", 
            metaValues = { 
             @MetaValue(targetEntity = Book.class, value = "B"),
             @MetaValue(targetEntity = VHS.class, value = "V"),
             @MetaValue(targetEntity = DVD.class, value = "D")
       })
    @JoinColumn(name="ITEM_ID")
    private Object item;

     .......
    public Object getItem() {
        return item;
    }

    public void setItem(Object item) {
        this.item = item;
    }

}

Solution 2

The @Any annotation defines a polymorphic association to classes from multiple tables. This type of mapping always requires more than one column. The first column holds the type of the associated entity. The remaining columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very special cases (eg. audit logs, user session data, etc). The @Any annotation describes the column holding the metadata information. To link the value of the metadata information and an actual entity type, The @AnyDef and @AnyDefs annotations are used.

@Any( metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@AnyMetaDef(
    idType = "integer",
    metaType = "string",
    metaValues = {
        @MetaValue( value = "S", targetEntity = StringProperty.class ),
        @MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
    return mainProperty;
}

idType represents the target entities identifier property type and metaType the metadata type (usually String). Note that @AnyDef can be mutualized and reused. It is recommended to place it as a package metadata in this case.

//on a package
@AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
package org.hibernate.test.annotations.any;
//in a class
@Any( metaDef="property", metaColumn = @Column( name = "property_type" ), fetch=FetchType.EAGER )
@JoinColumn( name = "property_id" )
public Property getMainProperty() {
    return mainProperty;
}

@ManyToAny allows polymorphic associations to classes from multiple tables. This type of mapping always requires more than one column. The first column holds the type of the associated entity. The remaining columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very special cases (eg. audit logs, user session data, etc).

@ManyToAny(
metaColumn = @Column( name = "property_type" ) )
@AnyMetaDef(
    idType = "integer",
    metaType = "string",
    metaValues = {
@MetaValue( value = "S", targetEntity = StringProperty.class ),
@MetaValue( value = "I", targetEntity = IntegerProperty.class ) } )
@Cascade( { org.hibernate.annotations.CascadeType.ALL } )
@JoinTable( name = "obj_properties", joinColumns = @JoinColumn( name = "obj_id" ),
    inverseJoinColumns = @JoinColumn( name = "property_id" ) )
public List<Property> getGeneralProperties() {

Src: Hibernate Annotations Reference Guide 3.4.0GA

Hope it Helps!

Solution 3

The @Any annotation defines a polymorphic association to classes from multiple tables, right, but polymorphic associations such as these are an SQL anti-pattern! The main reason is that you can´t define a FK constraint if a column can refer to more than one table.

One of the solutions, pointed out by Bill Karwin in his book, is to create intersection tables to each type of "Any", instead of using one column with "type", and using the unique modifier to avoid duplicates. This solution may be a pain to work with JPA.

Another solution, also proposed by Karwin, is to create a super-type for the connected elements. Taking the example of borrowing Book, DVD or VHS, you could create a super type Item, and make Book, DVD and VHS inherit from Item, with strategy of Joined table. Borrow then points to Item. This way you completely avoid the FK problem. I translated the book example to JPA bellow:

@Entity
@Table(name = "BORROW")
public class Borrow{
//... id, ...
@ManyToOne Item item;
//...
}

@Entity
@Table(name = "ITEMS")
@Inheritance(strategy=JOINED)
public class Item{
  // id, ....
  // you can add a reverse OneToMany here to borrow.
}

@Entity
@Table(name = "BOOKS")    
public class Book extends Item {
  // book attributes
}

@Entity
@Table(name = "VHS")    
public class VHS extends Item {
  // VHSattributes
}

@Entity
@Table(name = "DVD")    
public class DVD extends Item {
  // DVD attributes
}

Solution 4

Have you read the Hibernate Annotations documentation for @Any? Haven't used that one myself yet, but it looks like some extended way of defining references. The link includes an example, though I don't know if it's enough to fully understand the concept...

Share:
27,007
Henrik Paul
Author by

Henrik Paul

Currently programming mainly in PHP in the spare time, but writing Java for a living. Started writing 5-liners of BASIC on a C64 when I was about 6, and hooked in programming ever since.

Updated on January 17, 2020

Comments

  • Henrik Paul
    Henrik Paul over 4 years

    Could someone explain to me how Any-related annotations (@Any, @AnyMetaDef, @AnyMetaDefs and @ManyToAny) work in practice. I have a hard time finding any useful documentation (JavaDoc alone isn't very helpful) about these.

    I have thus far gathered that they somehow enable referencing to abstract and extended classes. If this is the case, why is there not an @OneToAny annotation? And is this 'any' referring to a single 'any', or multiple 'any'?

    A short, practical and illustrating example would be very much appreciated (doesn't have to compile).

    Edit: as much as I would like to accept replies as answers and give credit where due, I found both Smink's and Sakana's answers informative. Because I can't accept several replies as the answer, I will unfortunately mark neither as the answer.