Tuesday 9 July 2013

Using a Map to represent an entity as name and value pair in JPA

Lets imagine there is a situation when there is a table in the database which contains some attributes as name and value pairs. Having this entity as a collection referencing to the other table using @OneToMany mapping and then looping through it by adding attributes: name as a key and value as a value into the Map is pretty straightforward, but persuading JPA to do the job requires a little bit more work. This tutorial will show exactly how to make this happen using plain JPA. Please note that all the annotation used in the following classes are coming from javax.persistence package. All of them are standard JPA annotations and none of them is framework specific.

Lets start with creating an entity named Attribute:

@Entity
@Table(name = "ATTRIBUTE")
public class Attribute {

    @Id
    private AttributePrimaryKey id;

    @Column(name = "VALUE", nullable = false)
    private String value;

    public String getValue() {
        return value;
    }

    public void setValue(String value) {
        this.value = value;
    }

    public AttributePrimaryKey getId() {
        return id;
    }

    public void setId(AttributePrimaryKey id) {
        this.id = id;
    }
}

Now we need to create a class which will represent Attribute's primary key. Lets name this class AttributePrimaryKey:

@Embeddable
public class AttributePrimaryKey implements Serializable {

    @Column(name = "NAME", nullable = false)
    private String name;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "ATTRIBUTE_GROUP_ID")
    private AttributeGroup attributeGroup;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public AttributeGroup getAttributeGroup() {
        return attributeGroup;
    }

    public void setAttributeGroup(AttributeGroup attributeGroup) {
        this.attributeGroup = attributeGroup;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof AttributePrimaryKey)) {
            return false;
        }

        AttributePrimaryKey that = (AttributePrimaryKey) o;

        return !(attributeGroup != null ? !attributeGroup.equals(that.attributeGroup) : that.attributeGroup != null)
                && !(name != null ? !name.equals(that.name) : that.name != null);

    }

    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + (attributeGroup != null ? attributeGroup.hashCode() : 0);
        return result;
    }
}

Make sure that equals and hasCode methods are overridden as this is the pirmary key of the Attribute entity and must be unique. And finally we need to create an entity which will hold the actual Map. Lets name it AttributeGroup:

@Entity
@Table(name = "ATTRIBUTE_GROUP")
public class AttributeGroup {

 @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @ElementCollection
    @MapKeyColumn(name = "NAME")
    @Column(name = "VALUE")
    @CollectionTable(name = "ATTRIBUTE", joinColumns = @JoinColumn(name = "ATTRIBUTE_GROUP_ID"))
    private Map<String, String> attributes = new HashMap<>();

    public void createAttribute(String name, String value) {
        AttributePrimaryKey primaryKey = new AttributePrimaryKey();
        primaryKey.setName(name);
        primaryKey.setAttributeGroup(this);

        Attribute attribute = new Attribute();
        attribute.setValue(value);
        attribute.setId(primaryKey);

        attributes.put(name, value);
    }

    public Long getId() {
        return id;
    } 
 
    public Map<String, String> getAttributes() {
        return attributes;
    }
}

So as you see this entity contains a Map<String, String> which represents values from Attribute entity holding a name as a key of the map and value as a value of the Map.

To create these tables in MySQL the following query has to be executed:

    CREATE TABLE ATTRIBUTE (
    NAME VARCHAR(255) NOT NULL,
    VALUE VARCHAR(255) NOT NULL,
    ATTRIBUTE_GROUP_ID BIGINT,
    PRIMARY KEY (ATTRIBUTE_GROUP_ID, NAME));

    CREATE TABLE ATTRIBUTE_GROUP (
    ID BIGINT NOT NULL AUTO_INCREMENT,
    PRIMARY KEY (ID));

    ALTER TABLE ATTRIBUTE
    ADD INDEX FK_BBTUDA7EL6XJVERGKSQBRGFTH (ATTRIBUTE_GROUP_ID),
    ADD CONSTRAINT FK_BBTUDA7EL6XJVERGKSQBRGFTH FOREIGN KEY (ATTRIBUTE_GROUP_ID)
    REFERENCES ATTRIBUTE_GROUP (ID);

No comments:

Post a Comment

Note: only a member of this blog may post a comment.