Hibernate failing by prepending fully qualified class name to property name on ManyToMany association

安稳与你 提交于 2021-02-07 13:15:55

问题


I'm trying to map two objects to each other using a ManyToMany association, but for some reason when I use the mappedBy property, hibernate seems to be getting confused about exactly what I am mapping. The only odd thing about my mapping here is that the association is not done on a primary key field in one of the entries (the field is unique though).

The tables are:

Sequence (
  id NUMBER,
  reference VARCHAR,
)

Project (
  id NUMBER
)

Sequence_Project (
  proj_id number references Project(id),
  reference varchar references Sequence(reference)
)

The objects look like (annotations are on the getter, put them on fields to condense a bit):

class Sequence {
   @Id
   private int id;

   private String reference;

   @ManyToMany(mappedBy="sequences")
   private List<Project> projects;
}

And the owning side:

class Project {
    @Id
    private int id;

    @ManyToMany
    @JoinTable(name="sequence_project",
               joinColumns=@JoinColumn(name="id"),
               inverseJoinColumns=@JoinColumn(name="reference", 
                                     referencedColumnName="reference"))
    private List<Sequence> sequences;
}

This fails with a MappingException:

property-ref [_test_local_entities_Project_sequences] not found on entity [test.local.entities.Project]

It seems to weirdly prepend the fully qualified class name, divided by underscores. How can I avoid this from happening?

EDIT: I played around with this a bit more. Changing the name of the mappedBy property throws a different exception, namely:

org.hibernate.AnnotationException: mappedBy reference an unknown target entity property: test.local.entities.Project.sequences

So the annotation is processing correctly, but somehow the property reference isn't correctly added to Hibernate's internal configuration.


回答1:


I have done the same scenario proposed by your question. And, as expected, i get the same exception. Just as complementary task, i have done the same scenario but with one-to-many many-to-one by using a non-primary key as joined column such as reference. I get now

SecondaryTable JoinColumn cannot reference a non primary key

Well, can it be a bug ??? Well, yes (and your workaround works fine (+1)). If you want to use a non-primary key as primary key, you must make sure it is unique. Maybe it explains why Hibernate does not allow to use non-primary key as primary key (Unaware users can get unexpected behaviors).

If you want to use the same mapping, You can split your @ManyToMany relationship into @OneToMany-ManyToOne By using encapsulation, you do not need to worry about your joined class

Project

@Entity
public class Project implements Serializable {

    @Id
    @GeneratedValue
    private Integer id;

    @OneToMany(mappedBy="project")
    private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();

    @Transient
    private List<Sequence> sequenceList = null;

    // getters and setters

    public void addSequence(Sequence sequence) {
        projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(id, sequence.getReference())));
    }

    public List<Sequence> getSequenceList() {
        if(sequenceList != null)
            return sequenceList;

        sequenceList = new ArrayList<Sequence>();
        for (ProjectSequence projectSequence : projectSequenceList)
            sequenceList.add(projectSequence.getSequence());

        return sequenceList;
    }

}

Sequence

@Entity
public class Sequence implements Serializable {

    @Id
    private Integer id;
    private String reference;

    @OneToMany(mappedBy="sequence")
    private List<ProjectSequence> projectSequenceList = new ArrayList<ProjectSequence>();

    @Transient
    private List<Project> projectList = null;

    // getters and setters

    public void addProject(Project project) {
        projectSequenceList.add(new ProjectSequence(new ProjectSequence.ProjectSequenceId(project.getId(), reference)));
    }

    public List<Project> getProjectList() {
        if(projectList != null)
            return projectList;

        projectList = new ArrayList<Project>();
        for (ProjectSequence projectSequence : projectSequenceList)
            projectList.add(projectSequence.getProject());

        return projectList;
    }

}

ProjectSequence

@Entity
public class ProjectSequence {

    @EmbeddedId
    private ProjectSequenceId projectSequenceId;

    @ManyToOne
    @JoinColumn(name="ID", insertable=false, updatable=false)
    private Project project;

    @ManyToOne
    @JoinColumn(name="REFERENCE", referencedColumnName="REFERENCE", insertable=false, updatable=false)
    private Sequence sequence;

    public ProjectSequence() {}
    public ProjectSequence(ProjectSequenceId projectSequenceId) {
        this.projectSequenceId = projectSequenceId;
    }

    // getters and setters

    @Embeddable
    public static class ProjectSequenceId implements Serializable {

        @Column(name="ID", updatable=false)
        private Integer projectId;

        @Column(name="REFERENCE", updatable=false)
        private String reference;

        public ProjectSequenceId() {}
        public ProjectSequenceId(Integer projectId, String reference) {
            this.projectId = projectId;
            this.reference = reference;
        }

        @Override
        public boolean equals(Object o) {
            if (!(o instanceof ProjectSequenceId))
                return false;

            final ProjectSequenceId other = (ProjectSequenceId) o;
            return new EqualsBuilder().append(getProjectId(), other.getProjectId())
                                      .append(getReference(), other.getReference())
                                      .isEquals();
        }

        @Override
        public int hashCode() {
            return new HashCodeBuilder().append(getProjectId())
                                        .append(getReference())
                                        .hashCode();
        }

    }

}



回答2:


I finally figured it out, more or less. I think this is basically a hibernate bug.

edit: I tried to fix it by changing the owning side of the association:

class Sequence {
  @Id
  private int id;

  private String reference;

  @ManyToMany
  @JoinTable(name="sequence_project",
           inverseJoinColumns=@JoinColumn(name="id"),
           joinColumns=@JoinColumn(name="reference", 
                        referencedColumnName="reference"))
  private List<Project> projects;
}

class Project {
  @Id
  private int id;

  @ManyToMany(mappedBy="projects")
  private List<Sequence> sequences;
}

This worked but caused problems elsewhere (see comment). So I gave up and modeled the association as an entity with many-to-one associations in Sequence and Project. I think this is at the very least a documentation/fault handling bug (the exception isn't very pertinent, and the failure mode is just wrong) and will try to report it to the Hibernate devs.




回答3:


IMHO what you are trying to achieve is not possible with JPA/Hibernate annotations. Unfortunately, the APIDoc of JoinTable is a bit unclear here, but all the examples I found use primary keys when mapping join tables.

We had the same issue like you in a project where we also could not change the legacy database schema. The only viable option there was to dump Hibernate and use MyBatis (http://www.mybatis.org) where you have the full flexibility of native SQL to express more complex join conditions.




回答4:


I run into this problem a dozen times now and the only workaround i found is doing the configuration of the @JoinTable twice with swapped columns on the other side of the relation:

class Sequence {

    @Id
    private int id;

    private String reference;

    @ManyToMany
    @JoinTable(
        name = "sequence_project",
        joinColumns = @JoinColumn(name="reference", referencedColumnName="reference"),
        inverseJoinColumns = @JoinColumn(name="id")
    )
    private List<Project> projects;

}

class Project {

    @Id
    private int id;

    @ManyToMany
    @JoinTable(
        name = "sequence_project",
        joinColumns = @JoinColumn(name="id"),
        inverseJoinColumns = @JoinColumn(name="reference", referencedColumnName="reference")
    )
    private List<Sequence> sequences;

}

I did not yet tried it with a column different from the primary key.



来源:https://stackoverflow.com/questions/3637956/hibernate-failing-by-prepending-fully-qualified-class-name-to-property-name-on-m

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!