fred
fred

Reputation: 1822

Hibernate composite primary keys

Im having trouble with hibernate sets of entities with composite primary keys.

We have a concept "Target" in our app. Target id should be the combination of three other tables (entities) primary ids. Target also has an int taget. Employees should have a collection of targets. The SQL looks like this:

CREATE TABLE IF NOT EXISTS `target` (
  `role_id` bigint(20) NOT NULL,
  `ApplicationPeriod_id` bigint(20) NOT NULL,
  `project_class_id` bigint(20) NOT NULL,
  `target` int(11) NOT NULL,
  PRIMARY KEY (`role_id`,`ApplicationPeriod_id`,`project_class_id`),
  KEY `fk_role_id` (`role_id`),
  KEY `fk_ApplicationPeriod_id` (`ApplicationPeriod_id`),
  KEY `fk_project_class_id` (`project_class_id`),
  KEY `FKCB7E71918717386C` (`project_class_id`),
  KEY `FKCB7E7191BEC322C1` (`ApplicationPeriod_id`),
  KEY `FKCB7E71917B617197` (`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

This SQL works fine, and it allows me to more than one target per role_id (Employee) as long as the applicationperiodid and projectclassid are different.

This is the targetID class

@Embeddable
public class TargetId implements Serializable {

    @ManyToOne
    private Employee employee;

    @ManyToOne
    private ApplicationPeriod applicationPeriod;

    @ManyToOne
    private ProjectClass projectClass;


    public Employee getEmployee() {
        return employee;
    }

    public void setEmployee(Employee employee) {
        this.employee = employee;
    }

    public ApplicationPeriod getApplicationPeriod() {
        return applicationPeriod;
    }

    public void setApplicationPeriod(ApplicationPeriod applicationPeriod) {
        this.applicationPeriod = applicationPeriod;
    }

    public ProjectClass getProjectClass() {
        return projectClass;
    }

    public void setProjectClass(ProjectClass projectClass) {
        this.projectClass = projectClass;
    }

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

        TargetId that = (TargetId) o;

        if (applicationPeriod != null ? !applicationPeriod.equals(that.applicationPeriod) : that.applicationPeriod != null)
            return false;
        if (employee != null ? !employee.equals(that.employee) : that.employee != null) return false;
        if (projectClass != null ? !projectClass.equals(that.projectClass) : that.projectClass != null) return false;

        return true;
    }

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

This is the target class

@Entity
@Table(name = "target")
@AssociationOverrides({
        @AssociationOverride(name = "targetId.employee",
            joinColumns = @JoinColumn(name = "role_id")),
        @AssociationOverride(name = "targetId.applicationPeriod",
            joinColumns = @JoinColumn(name = "ApplicationPeriod_id")),
        @AssociationOverride(name = "targetId.projectClass",
                joinColumns = @JoinColumn(name = "project_class_id"))
})
public class Target implements Serializable {

    @EmbeddedId
    private TargetId targetId;

    private int target;

    public TargetId getTargetId() {
        return targetId;
    }

    public void setTargetId(TargetId targetId) {
        this.targetId = targetId;
    }

    public int getTarget() {
        return target;
    }

    public void setTarget(int target) {
        this.target = target;
    }

    public Target() {
    }

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

        Target target = (Target) o;

        if (this.target != target.target) return false;
        if (targetId != null ? !targetId.equals(target.targetId) : target.targetId != null)
            return false;

        return true;
    }

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

This is the employee class, in which I want to store a set of targets for each employee.

@Entity
@Cacheable(true)
@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class Employee {

    ...

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "targetId.employee", cascade=CascadeType.ALL, orphanRemoval=true)
    private Set<Target> targets = new HashSet<Target>();

    public Set<Target> getTargets() {
        return targets;
    }

    public void setTargets(Set<Target> targets) {
        this.targets = targets;
    }

    ...

}

Creating and storing targets via hibernate works, and everything in the database looks good. The problem is that, even thought the database allows storing more than one target per employee as long as the applicationperiod and projectclass are different, Hibernate wont store more than one target per employee regardless of whether the applicationperiod and projectclass are different. What am I doing wrong? How can I make Hibernate let me store more than one target per employee?

Upvotes: 2

Views: 7360

Answers (1)

fred
fred

Reputation: 1822

OK, I figured it out. The problem, it seems, was that the TargetID class cant use Entity attributes, rather, it should use Longs to point to ids for the entities in question. You then use SQL to set the correct constraints and connections between columns in the DB.

SQL:

CREATE TABLE IF NOT EXISTS `target` (
  `applicationPeriodId` bigint(20) NOT NULL,
  `employeeId` bigint(20) NOT NULL,
  `projectClassId` bigint(20) NOT NULL,
  `target` int(11) NOT NULL,
  PRIMARY KEY (`applicationPeriodId`,`employeeId`,`projectClassId`),
  KEY `FKCB7E71913353DC5C` (`employeeId`),
  KEY `FKCB7E7191A520201E` (`projectClassId`),
  KEY `FKCB7E7191790761A4` (`applicationPeriodId`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

--
-- Constraints for table `target`
--
ALTER TABLE `target`
  ADD CONSTRAINT `FKCB7E7191790761A4` FOREIGN KEY (`applicationPeriodId`) REFERENCES `ApplicationPeriod` (`id`),
  ADD CONSTRAINT `FKCB7E71913353DC5C` FOREIGN KEY (`employeeId`) REFERENCES `role` (`id`),
  ADD CONSTRAINT `FKCB7E7191A520201E` FOREIGN KEY (`projectClassId`) REFERENCES `project_class` (`id`);

The compound ID class:

@Embeddable
public class TargetId implements Serializable {

    @Basic
    private Long employeeId;

    @Basic
    private Long applicationPeriodId;

    @Basic
    private Long projectClassId;

    public Long getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(Long employeeId) {
        this.employeeId = employeeId;
    }

    public Long getApplicationPeriodId() {
        return applicationPeriodId;
    }

    public void setApplicationPeriodId(Long applicationPeriodId) {
        this.applicationPeriodId = applicationPeriodId;
    }

    public Long getProjectClassId() {
        return projectClassId;
    }

    public void setProjectClassId(Long projectClassId) {
        this.projectClassId = projectClassId;
    }

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

        TargetId targetId = (TargetId) o;

        if (applicationPeriodId != null ? !applicationPeriodId.equals(targetId.applicationPeriodId) : targetId.applicationPeriodId != null)
            return false;
        if (employeeId != null ? !employeeId.equals(targetId.employeeId) : targetId.employeeId != null) return false;
        if (projectClassId != null ? !projectClassId.equals(targetId.projectClassId) : targetId.projectClassId != null)
            return false;

        return true;
    }

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

The Target entity:

@Entity
@Table(name = "target")
@AssociationOverrides({
        @AssociationOverride(name = "targetId.employeeId",
            joinColumns = @JoinColumn(name = "role_id")),
        @AssociationOverride(name = "targetId.applicationPeriodId",
            joinColumns = @JoinColumn(name = "ApplicationPeriod_id")),
        @AssociationOverride(name = "targetId.projectClassId",
                joinColumns = @JoinColumn(name = "project_class_id"))
})
public class Target implements Serializable {

    @EmbeddedId
    private TargetId targetId;

    private int target;

    public TargetId getTargetId() {
        return targetId;
    }

    public void setTargetId(TargetId targetId) {
        this.targetId = targetId;
    }

    public int getTarget() {
        return target;
    }

    public void setTarget(int target) {
        this.target = target;
    }

    public Target() {
    }

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

        Target target = (Target) o;

        if (this.target != target.target) return false;
        if (targetId != null ? !targetId.equals(target.targetId) : target.targetId != null)
            return false;

        return true;
    }

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

The Employee entity, which contains a Set of Targets:

@Entity
@Cacheable(true)
@Cache(usage= CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) //Hibernate specific
public class Employee extends ProjectTeamMember {
    ...
    public Set<Language> getLanguages() {
        return languages;
    }

    public void setLanguages(Set<Language> languages) {
        this.languages = languages;
    }

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "targetId.employeeId", cascade=CascadeType.ALL, orphanRemoval=true)
    private Set<Target> targets = new HashSet<Target>();

    public Set<Target> getTargets() {
        return targets;
    }

    public void setTargets(Set<Target> targets) {
        this.targets = targets;
    }    
    ...
}

Upvotes: 5

Related Questions