Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing EAV pattern with Hibernate for User -> Settings relationship

I'm trying to setup a simple EAV pattern in my web app using Java/Spring MVC and Hibernate. I can't seem to figure out the magic behind the hibernate XML setup for this scenario.

My database table "SETUP" has three columns:

  • user_id (FK)
  • setup_item
  • setup_value

The database composite key is made up of user_id | setup_item

Here's the Setup.java class:

public class Setup implements CommonFormElements, Serializable {
  private Map data = new HashMap();
  private String saveAction;
  private Integer speciesNamingList;
  private User user;

  Logger log = LoggerFactory.getLogger(Setup.class);

  public String getSaveAction() {
    return saveAction;
  }

  public void setSaveAction(String action) {
    this.saveAction = action;
  }

  public User getUser() {
    return user;
  }

  public void setUser(User user) {
    this.user = user;
  }

  public Integer getSpeciesNamingList() {
    return speciesNamingList;
  }

  public void setSpeciesNamingList(Integer speciesNamingList) {
    this.speciesNamingList = speciesNamingList;
  }

  public Map getData() {
    return data;
  }

  public void setData(Map data) {
    this.data = data;
  }
}

My problem with the Hibernate setup, is that I can't seem to figure out how to map out the fact that a foreign key and the key of a map will construct the composite key of the table... this is due to a lack of experience using Hibernate. Here's my initial attempt at getting this to work:

<composite-id>
  <key-many-to-one foreign-key="id" name="user" column="user_id" class="Business.User">
    <meta attribute="use-in-equals">true</meta>
  </key-many-to-one>
</composite-id>

<map lazy="false" name="data" table="setup">
  <key column="user_id" property-ref="user"/>
  <composite-map-key class="Command.Setup">
    <key-property name="data" column="setup_item" type="string"/>
  </composite-map-key>

  <element column="setup_value" not-null="true" type="string"/>
</map>

Any insight into how to properly map this common scenario would be most appreciated!

like image 697
Trevor Avatar asked Apr 15 '10 14:04

Trevor


People also ask

When to use EAV?

In a nutshell, EAV is useful when your list of attributes is frequently growing, or when it's so large that most rows would be filled with mostly NULLs if you made every attribute a column.

What is EAV in SQL?

Entity–attribute–value model (EAV) is a data model to encode, in a space-efficient manner, entities where the number of attributes (properties, parameters) that can be used to describe them is potentially vast, but the number that will actually apply to a given entity is relatively modest.

Why EAV?

The benefits the EAV model brings are following: Flexible versatile data structure (it's possible to change the number of properties without having to change the database schema). When adding a new attribute for a given entity, we have a possibility to use it in other entities. Quick to implement.

What is attribute value database?

In relational databases, attributes are the describing characteristics or properties that define all items pertaining to a certain category applied to all cells of a column. The rows, instead, are called tuples, and represent data sets applied to a single entity to uniquely identify each item.


1 Answers

As shown by yourself, you have a inconsistent mapping

You said Setup class defines a composite primary key (Notice i have created a composite primary key class (SetupId - see bellow) which must implements Serializable and equals and hashcode method)

package ar.domain;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

public class Setup implements Serializable {

    private SetupId setupId;

    private User user;
    private Map data= new HashMap();

    public SetupId getSetupId() {
        return setupId;
    }

    public void setSetupId(SetupId setupId) {
        this.setupId = setupId;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Map getData() {
        return data;
    }

    public void setData(Map data) {
        this.data = data;
    }


    public static class SetupId implements Serializable {

        private Integer userId;
        private String setupItem;

        public String getSetupItem() {
            return setupItem;
        }

        public void setSetupItem(String setupItem) {
            this.setupItem = setupItem;
        }

        public Integer getUserId() {
            return userId;
        }

        public void setUserId(Integer userId) {
            this.userId = userId;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null)
                return false;

            if (!(o instanceof SetupId))
                return false;

            final SetupId other = (SetupId) o;
            if (!(getUserId().equals(other.getUserId())))
                return false;
            if (!(getSetupItem().equals(other.getSetupItem())))
                return false;

            return true;
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 11 * hash + (getUserId() != null ? getUserId().hashCode() : 0);
            hash = 11 * hash + (getSetupItem() != null ? getSetupItem().hashCode() : 0);
            return hash;
        }

    }

}

Because of your Setup class has a Map of value Type, you should define its composite foreign key when defining its relationship (see key element)

<class name="ar.domain.Setup">
    <composite-id name="setupId" class="ar.domain.Setup$SetupId">
        <key-property name="setupItem" type="string" column="SETUP_ITEM"/>
        <key-property name="userId" type="integer" column="USER_ID"/>
    </composite-id>
    <many-to-one name="user" class="ar.domain.User" column="USER_ID" insert="false" update="false"/>
    <map name="data" table="DATA_TABLE">
        <key>
            <column name="SETUP_ITEM"/>
            <column name="USER_ID"/>
        </key>
        <map-key column="USER_ID"/>
        <element column="SETUP_VALUE" not-null="true" type="string"/>
    </map>
</class>

And, at the same time, use a composite foreign key column as map-key (USER_ID, right ?) which does not make sense. Why ?

  • Hibernate does not allow you update a (composite) primary key column

Besides that, Hibernate does not support automatic generation of composite primary key

Suppose here goes your SETUP Table

SETUP_ITEM USER_ID
0          1
0          2

And your DATA_TABLE

SETUP_ITEM USER_ID
0          1

What happens whether you try the following one

Integer userId = 3;
String setupValue = "someValue";

setup.getData().put(userId, setupValue);

Because of SETUP Table does not define a USER_ID which value is 3, you will see a constraint violation.

Keep it in mind

When you have a (composite) primary key, which can not be updatable, avoid to use it, someway, to change a mutable property which depends on it. Otherwise, Hibernate will complain it.

like image 113
Arthur Ronald Avatar answered Nov 15 '22 06:11

Arthur Ronald