4

I have the following usage in JoinColumns

@Entity
public class EntityOne{

  private String action;
  private String type;

  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @NotFound(action = NotFoundAction.IGNORE)
  @JoinColumns({
      @JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
      @JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false)
  })
  private Entitytwo entitytwo;
  }

And

@Entity
public class EntityTwo {

  @Id
  @Column(name = "type_name")
  private String typeName;

  @Id
  @Column(name = "action_name")
  private String actionName;

  }

This setup causes hibernate error of

Referenced column '" + column.getName()
                                    + "' mapped by target property '" + property.getName()
                                    + "' occurs out of order in the list of '@JoinColumn's

If i change the order inside the @JoinColumns it seems to work, but can stop working at the next time the application starts.

The hibernate comments at the begining of the relevant code states:

    // Now we need to line up the properties with the columns in the
    // same order they were specified by the @JoinColumn annotations
    // this is very tricky because a single property might span
    // multiple columns.
    // TODO: For now we only consider the first property that matched
    //       each column, but this means we will reject some mappings
    //       that could be made to work for a different choice of
    //       properties (it's also not very deterministic)

And on the relevant code itself:

            // we have the first column of a new property
            orderedProperties.add( property );
            if ( property.getColumnSpan() > 1 ) {
                if ( !property.getColumns().get(0).equals( column ) ) {
                    // the columns have to occur in the right order in the property
                    throw new AnnotationException("Referenced column '" + column.getName()
                            + "' mapped by target property '" + property.getName()
                            + "' occurs out of order in the list of '@JoinColumn's");
                }
                currentProperty = property;
                lastPropertyColumnIndex = 1;
            }

How should i set the @JoinColumn for it to consistently work?

Brian Clozel
  • 56,583
  • 15
  • 167
  • 176
mosh
  • 404
  • 2
  • 8
  • 16
  • That is a very weird requirement. File a bug if there isn't one, as there is no JPA requirement for ordering of annotations and java properties. Maybe show the stack and error message as it might indicate more context that might point at a better workaround. – Chris Dec 07 '22 at 14:09
  • have you made any progress? I am stuck with the same issue. Have you raised this with hibernate yet. To me it seems like a bug – Chris Dec 19 '22 at 10:19
  • @Chris - no, my team dropped it as higher priority tasks arrived. I will get back to it sometime. No didn't raise the issue because from the documentation above it seems intentional. – mosh Dec 20 '22 at 14:40
  • Was a solution ever discovered for this issue? I am experiencing the exact same issue after upgrading to Spring Boot 3. – Keith Bennett Feb 17 '23 at 20:15
  • 1
    @keith I hope It's fixed with Hibernate 6.2. I opened a [Bug](https://hibernate.atlassian.net/browse/HHH-16263) that will be fixed with 6.2 this might help you guys aswell – Chris Mar 13 '23 at 14:43

2 Answers2

0

If the action and type attributes of EntityOne are meant to refer to the corresponding attributes of EntityTwo, they are useless and misleading.

The attribute private Entitytwo entitytwo is enough to design the @ManytoOne relation.

Remove these two attributes and if you need to get the action and type value of the entityTwo linked to an entityOne, simply use entityOne.entitytwo.getAction() (or entityOne.entitytwo.getType()).

  • 1
    Can you please explain why "useless and misleading". Isn't that what the @JoinColumns is for? – mosh Dec 07 '22 at 08:22
  • The `@JoinColumn` will define the name of the column in the database to store the foreign key (and also target the column of the primary key in the targeted table), but on the relational-object model, it doesn't need to appear : in other words, it hidden behind the 'entitytwo' java attribute. And, even if the primary key of EntityTwo relies on 2 attributes, the `entitytwo` attribute annotated with `@ManyToOne` is enough. – Pierre Demeestere Dec 07 '22 at 10:46
  • Are you saying the model doesn't need the EntityOne.action and type string properties? They are not useless or misleading, as it is common to want to refer to the values individually, and can allow for fetching without performing joins and other performance considerations. If you have the fk values, you don't need to serialize (and so fetch) the full entityTwo entity with EntityOne. – Chris Dec 07 '22 at 14:07
  • If not useless, it is redundant. Therefore it is misleading hibernate mechanism. If you are really afraid of fetching `entitytwo` to get this information, you can try to leave them but add an annotation to define the column name to match the column name given on the `@joinColumn`. But if you set properly the indices on the column type and action of the entityOne table, the join will not be expensive. – Pierre Demeestere Dec 07 '22 at 15:01
0

I just tried the code you posted in Hibernate 6.1, and I observed no error. Even after permuting various things, still no error. So then to make things harder, I added a third column to the FK and tried permuting things. Still no error.

I now have:

@Entity
public class EntityOne {

  @Id @GeneratedValue
  Long id;

  String action;
  String type;
  int count;

  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @NotFound(action = NotFoundAction.IGNORE)
  @JoinColumns({
          @JoinColumn(name = "count", referencedColumnName = "count", updatable = false, insertable = false),
          @JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
          @JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false),
  })
  EntityTwo entitytwo;
}

@Entity
public class EntityTwo {

  @Id
  @Column(name = "type_name")
  String typeName;

  @Id
  @Column(name = "count")
  int count;

  @Id
  @Column(name = "action_name")
  String actionName;

}

and the test code:

@DomainModel(annotatedClasses = {EntityOne.class, EntityTwo.class})
@SessionFactory
public class BugTest {
    @Test
    public void test(SessionFactoryScope scope) {
        scope.inTransaction( session -> {
            EntityOne entityOne = new EntityOne();
            entityOne.action = "go";
            entityOne.type = "thing";
            EntityTwo entityTwo = new EntityTwo();
            entityTwo.actionName = "go";
            entityTwo.typeName = "thing";
            entityOne.entitytwo = entityTwo;
            session.persist( entityOne );
        } );
    }
}

Perhaps there's something you're not telling us? Like, for example, something to do with the @Id of EntityOne which is missing in your original posted code?

Just in case, also tried this variation:

@Entity
public class EntityOne {

  @Id
  String action;
  @Id
  String type;
  @Id
  int count;

  @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
  @NotFound(action = NotFoundAction.IGNORE)
  @JoinColumns({
          @JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
          @JoinColumn(name = "count", referencedColumnName = "count", updatable = false, insertable = false),
          @JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false),
  })
  EntityTwo entitytwo;
}

But still no error.

Gavin King
  • 3,182
  • 1
  • 13
  • 11