Wrong ordering in generated table in jpa

Hibernate generates columns in alphabetical order. According to this post the reason is given as:

It is sorted to ensurce deterministic ordering across clusters.

We can't rely on the vm to return the methods in the same order every time so we had to do something.

Apparently it used to be in the order of occurrence but this changed between 3.2.0 GA and 3.2.1 GA.

I also found Schema auto generation creates columns in alphabetical order for compound primary keys and this seems to be like your problem. This ticket is about the order changing in primary keys and that negatively impacts index performance.

There is no fix for this other than a workaround of naming the columns in such a way that they come out in the correct order (no, I'm not kidding).


DataNucleus allows the extension specifying the position for schema generation, FWIW.


You can set the order you want whenever you can change the internal name of the class members, since the order of the columns is taken from the field name, not from the getter, setter, or column.

Thus, if the class members are private (as desired), you should only list them (for example by prefixing them with "a_", "b_", "c_", ...) without changing either the getters, the setters, or the column names.

For example, the following class definition:

@Id
@Column(name = "parent")
UUID parent;

@Id
@Column(name = "type", length = 10)
String type;

@Id
@Column(name = "child")
UUID child;

It generates the following table:

  Column   |         Type          | Collation | Nullable | Default 
-----------+-----------------------+-----------+----------+---------
 child     | uuid                  |           | not null | 
 parent    | uuid                  |           | not null | 
 type      | character varying(10) |           | not null | 
Indexes:
    "...whatever..." PRIMARY KEY, btree (child, parent, type)

Which is not efficient because normally we will search by parent and type of relationship to obtain the children.

We can change the private names without affecting the rest of the implementation by doing:

@Id
@Column(name = "parent")
UUID a_parent;

@Id
@Column(name = "type", length = 10)
String b_type;

@Id
@Column(name = "child")
UUID c_child;

public UUID getParent() { return a_parent; }
public UUID getChild() { return c_child; }
public String getType() { return b_type; }
public void setParent(UUID parent) { a_parent = parent; }
public void setChild(UUID child) { c_child = child; }
public void setType(String type) { b_type = type; }

In that it has now been generated:

  Column   |         Type          | Collation | Nullable | Default 
-----------+-----------------------+-----------+----------+---------
 parent    | uuid                  |           | not null | 
 type      | character varying(10) |           | not null | 
 child     | uuid                  |           | not null | 
Indexes:
    "...whatever..." PRIMARY KEY, btree (parent, type, child)

Of course it is not best to depend on the internal lexicographical order of the class members but I do not see a better solution.


I have the same problem. Finally, I found the solution.

Find your External Libraries about hibernate core and find the org.hibernate.cfg.PropertyContainer class and copy the content.

enter image description here

In your root folder create the org.hibernate.cfg package and PropertyContainer class.
enter image description here

Paste the org.hibernate.cfg.PropertyContainer content and replace all TreeMap to LinkedHashMap in your create PropertyContainer class.

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */

// $Id$

package org.hibernate.cfg;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import javax.persistence.Access;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;

import org.hibernate.AnnotationException;
import org.hibernate.MappingException;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.Target;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.boot.jaxb.Origin;
import org.hibernate.boot.jaxb.SourceType;
import org.hibernate.cfg.annotations.HCANNHelper;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.StringHelper;

import org.jboss.logging.Logger;

/**
 * A helper class to keep the {@code XProperty}s of a class ordered by access type.
 *
 * @author Hardy Ferentschik
 */
class PropertyContainer {
//
//    static {
//        System.setProperty("jboss.i18n.generate-proxies", "true");
//    }

    private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, PropertyContainer.class.getName());

    /**
     * The class for which this container is created.
     */
    private final XClass xClass;
    private final XClass entityAtStake;

    /**
     * Holds the AccessType indicated for use at the class/container-level for cases where persistent attribute
     * did not specify.
     */
    private final AccessType classLevelAccessType;

    private final TreeMap<String, XProperty> persistentAttributeMap;

    PropertyContainer(XClass clazz, XClass entityAtStake, AccessType defaultClassLevelAccessType) {
        this.xClass = clazz;
        this.entityAtStake = entityAtStake;

        if ( defaultClassLevelAccessType == AccessType.DEFAULT ) {
            // this is effectively what the old code did when AccessType.DEFAULT was passed in
            // to getProperties(AccessType) from AnnotationBinder and InheritanceState
            defaultClassLevelAccessType = AccessType.PROPERTY;
        }

        AccessType localClassLevelAccessType = determineLocalClassDefinedAccessStrategy();
        assert localClassLevelAccessType != null;

        this.classLevelAccessType = localClassLevelAccessType != AccessType.DEFAULT
                ? localClassLevelAccessType
                : defaultClassLevelAccessType;
        assert classLevelAccessType == AccessType.FIELD || classLevelAccessType == AccessType.PROPERTY;

        this.persistentAttributeMap = new TreeMap<String, XProperty>();

        final List<XProperty> fields = xClass.getDeclaredProperties( AccessType.FIELD.getType() );
        final List<XProperty> getters = xClass.getDeclaredProperties( AccessType.PROPERTY.getType() );

        preFilter( fields, getters );

        final Map<String,XProperty> persistentAttributesFromGetters = new HashMap<String, XProperty>();

        collectPersistentAttributesUsingLocalAccessType(
                persistentAttributeMap,
                persistentAttributesFromGetters,
                fields,
                getters
        );
        collectPersistentAttributesUsingClassLevelAccessType(
                persistentAttributeMap,
                persistentAttributesFromGetters,
                fields,
                getters
        );
    }

    private void preFilter(List<XProperty> fields, List<XProperty> getters) {
        Iterator<XProperty> propertyIterator = fields.iterator();
        while ( propertyIterator.hasNext() ) {
            final XProperty property = propertyIterator.next();
            if ( mustBeSkipped( property ) ) {
                propertyIterator.remove();
            }
        }

        propertyIterator = getters.iterator();
        while ( propertyIterator.hasNext() ) {
            final XProperty property = propertyIterator.next();
            if ( mustBeSkipped( property ) ) {
                propertyIterator.remove();
            }
        }
    }

    private void collectPersistentAttributesUsingLocalAccessType(
            TreeMap<String, XProperty> persistentAttributeMap,
            Map<String,XProperty> persistentAttributesFromGetters,
            List<XProperty> fields,
            List<XProperty> getters) {

        // Check fields...
        Iterator<XProperty> propertyIterator = fields.iterator();
        while ( propertyIterator.hasNext() ) {
            final XProperty xProperty = propertyIterator.next();
            final Access localAccessAnnotation = xProperty.getAnnotation( Access.class );
            if ( localAccessAnnotation == null
                    || localAccessAnnotation.value() != javax.persistence.AccessType.FIELD ) {
                continue;
            }

            propertyIterator.remove();
            persistentAttributeMap.put( xProperty.getName(), xProperty );
        }

        // Check getters...
        propertyIterator = getters.iterator();
        while ( propertyIterator.hasNext() ) {
            final XProperty xProperty = propertyIterator.next();
            final Access localAccessAnnotation = xProperty.getAnnotation( Access.class );
            if ( localAccessAnnotation == null
                    || localAccessAnnotation.value() != javax.persistence.AccessType.PROPERTY ) {
                continue;
            }

            propertyIterator.remove();

            final String name = xProperty.getName();

            // HHH-10242 detect registration of the same property getter twice - eg boolean isId() + UUID getId()
            final XProperty previous = persistentAttributesFromGetters.get( name );
            if ( previous != null ) {
                throw new org.hibernate.boot.MappingException(
                        LOG.ambiguousPropertyMethods(
                                xClass.getName(),
                                HCANNHelper.annotatedElementSignature( previous ),
                                HCANNHelper.annotatedElementSignature( xProperty )
                        ),
                        new Origin( SourceType.ANNOTATION, xClass.getName() )
                );
            }

            persistentAttributeMap.put( name, xProperty );
            persistentAttributesFromGetters.put( name, xProperty );
        }
    }

    private void collectPersistentAttributesUsingClassLevelAccessType(
            TreeMap<String, XProperty> persistentAttributeMap,
            Map<String,XProperty> persistentAttributesFromGetters,
            List<XProperty> fields,
            List<XProperty> getters) {
        if ( classLevelAccessType == AccessType.FIELD ) {
            for ( XProperty field : fields ) {
                if ( persistentAttributeMap.containsKey( field.getName() ) ) {
                    continue;
                }

                persistentAttributeMap.put( field.getName(), field );
            }
        }
        else {
            for ( XProperty getter : getters ) {
                final String name = getter.getName();

                // HHH-10242 detect registration of the same property getter twice - eg boolean isId() + UUID getId()
                final XProperty previous = persistentAttributesFromGetters.get( name );
                if ( previous != null ) {
                    throw new org.hibernate.boot.MappingException(
                            LOG.ambiguousPropertyMethods(
                                    xClass.getName(),
                                    HCANNHelper.annotatedElementSignature( previous ),
                                    HCANNHelper.annotatedElementSignature( getter )
                            ),
                            new Origin( SourceType.ANNOTATION, xClass.getName() )
                    );
                }

                if ( persistentAttributeMap.containsKey( name ) ) {
                    continue;
                }

                persistentAttributeMap.put( getter.getName(), getter );
                persistentAttributesFromGetters.put( name, getter );
            }
        }
    }

    public XClass getEntityAtStake() {
        return entityAtStake;
    }

    public XClass getDeclaringClass() {
        return xClass;
    }

    public AccessType getClassLevelAccessType() {
        return classLevelAccessType;
    }

    public Collection<XProperty> getProperties() {
        assertTypesAreResolvable();
        return Collections.unmodifiableCollection( persistentAttributeMap.values() );
    }

    private void assertTypesAreResolvable() {
        for ( XProperty xProperty : persistentAttributeMap.values() ) {
            if ( !xProperty.isTypeResolved() && !discoverTypeWithoutReflection( xProperty ) ) {
                String msg = "Property " + StringHelper.qualify( xClass.getName(), xProperty.getName() ) +
                        " has an unbound type and no explicit target entity. Resolve this Generic usage issue" +
                        " or set an explicit target attribute (eg @OneToMany(target=) or use an explicit @Type";
                throw new AnnotationException( msg );
            }
        }
    }
//
//  private void considerExplicitFieldAndPropertyAccess() {
//      for ( XProperty property : fieldAccessMap.values() ) {
//          Access access = property.getAnnotation( Access.class );
//          if ( access == null ) {
//              continue;
//          }
//
//          // see "2.3.2 Explicit Access Type" of JPA 2 spec
//          // the access type for this property is explicitly set to AccessType.FIELD, hence we have to
//          // use field access for this property even if the default access type for the class is AccessType.PROPERTY
//          AccessType accessType = AccessType.getAccessStrategy( access.value() );
//            if (accessType == AccessType.FIELD) {
//              propertyAccessMap.put(property.getName(), property);
//          }
//            else {
//              LOG.debug( "Placing @Access(AccessType.FIELD) on a field does not have any effect." );
//          }
//      }
//
//      for ( XProperty property : propertyAccessMap.values() ) {
//          Access access = property.getAnnotation( Access.class );
//          if ( access == null ) {
//              continue;
//          }
//
//          AccessType accessType = AccessType.getAccessStrategy( access.value() );
//
//          // see "2.3.2 Explicit Access Type" of JPA 2 spec
//          // the access type for this property is explicitly set to AccessType.PROPERTY, hence we have to
//          // return use method access even if the default class access type is AccessType.FIELD
//            if (accessType == AccessType.PROPERTY) {
//              fieldAccessMap.put(property.getName(), property);
//          }
//            else {
//              LOG.debug( "Placing @Access(AccessType.PROPERTY) on a field does not have any effect." );
//          }
//      }
//  }

//  /**
//   * Retrieves all properties from the {@code xClass} with the specified access type. This method does not take
//   * any jpa access rules/annotations into account yet.
//   *
//   * @param access The access type - {@code AccessType.FIELD}  or {@code AccessType.Property}
//   *
//   * @return A maps of the properties with the given access type keyed against their property name
//   */
//  private TreeMap<String, XProperty> initProperties(AccessType access) {
//      if ( !( AccessType.PROPERTY.equals( access ) || AccessType.FIELD.equals( access ) ) ) {
//          throw new IllegalArgumentException( "Access type has to be AccessType.FIELD or AccessType.Property" );
//      }
//
//      //order so that property are used in the same order when binding native query
//      TreeMap<String, XProperty> propertiesMap = new TreeMap<String, XProperty>();
//      List<XProperty> properties = xClass.getDeclaredProperties( access.getType() );
//      for ( XProperty property : properties ) {
//          if ( mustBeSkipped( property ) ) {
//              continue;
//          }
//          // HHH-10242 detect registration of the same property twice eg boolean isId() + UUID getId()
//          XProperty oldProperty = propertiesMap.get( property.getName() );
//          if ( oldProperty != null ) {
//              throw new org.hibernate.boot.MappingException(
//                      LOG.ambiguousPropertyMethods(
//                              xClass.getName(),
//                              HCANNHelper.annotatedElementSignature( oldProperty ),
//                              HCANNHelper.annotatedElementSignature( property )
//                      ),
//                      new Origin( SourceType.ANNOTATION, xClass.getName() )
//              );
//          }
//
//          propertiesMap.put( property.getName(), property );
//      }
//      return propertiesMap;
//  }

    private AccessType determineLocalClassDefinedAccessStrategy() {
        AccessType classDefinedAccessType;

        AccessType hibernateDefinedAccessType = AccessType.DEFAULT;
        AccessType jpaDefinedAccessType = AccessType.DEFAULT;

        org.hibernate.annotations.AccessType accessType = xClass.getAnnotation( org.hibernate.annotations.AccessType.class );
        if ( accessType != null ) {
            hibernateDefinedAccessType = AccessType.getAccessStrategy( accessType.value() );
        }

        Access access = xClass.getAnnotation( Access.class );
        if ( access != null ) {
            jpaDefinedAccessType = AccessType.getAccessStrategy( access.value() );
        }

        if ( hibernateDefinedAccessType != AccessType.DEFAULT
                && jpaDefinedAccessType != AccessType.DEFAULT
                && hibernateDefinedAccessType != jpaDefinedAccessType ) {
            throw new MappingException(
                    "@AccessType and @Access specified with contradicting values. Use of @Access only is recommended. "
            );
        }

        if ( hibernateDefinedAccessType != AccessType.DEFAULT ) {
            classDefinedAccessType = hibernateDefinedAccessType;
        }
        else {
            classDefinedAccessType = jpaDefinedAccessType;
        }
        return classDefinedAccessType;
    }

    private static boolean discoverTypeWithoutReflection(XProperty p) {
        if ( p.isAnnotationPresent( OneToOne.class ) && !p.getAnnotation( OneToOne.class )
                .targetEntity()
                .equals( void.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( OneToMany.class ) && !p.getAnnotation( OneToMany.class )
                .targetEntity()
                .equals( void.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( ManyToOne.class ) && !p.getAnnotation( ManyToOne.class )
                .targetEntity()
                .equals( void.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( ManyToMany.class ) && !p.getAnnotation( ManyToMany.class )
                .targetEntity()
                .equals( void.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( org.hibernate.annotations.Any.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( ManyToAny.class ) ) {
            if ( !p.isCollection() && !p.isArray() ) {
                throw new AnnotationException( "@ManyToAny used on a non collection non array property: " + p.getName() );
            }
            return true;
        }
        else if ( p.isAnnotationPresent( Type.class ) ) {
            return true;
        }
        else if ( p.isAnnotationPresent( Target.class ) ) {
            return true;
        }
        return false;
    }

    private static boolean mustBeSkipped(XProperty property) {
        //TODO make those hardcoded tests more portable (through the bytecode provider?)
        return property.isAnnotationPresent( Transient.class )
                || "net.sf.cglib.transform.impl.InterceptFieldCallback".equals( property.getType().getName() )
                || "org.hibernate.bytecode.internal.javassist.FieldHandler".equals( property.getType().getName() );
    }
}

Fixed the Organization class.

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Organization {
    @Id
    @GeneratedValue
    private Long id;

    @Column(unique = true, nullable = false)
    private String organizationNumber;

    @Column(nullable = false)
    private String name;

    public Organization() {
    }

    public Organization(String name) {
        this.name = name;
    }

    public Long getId() {
        return id;
    }

    @SuppressWarnings("unused")
    public void setId(Long id) {
        this.id = id;
    }

    public String getOrganizationNumber() {
        return organizationNumber;
    }

    public void setOrganizationNumber(String organizationNumber) {
        this.organizationNumber = organizationNumber;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return this.name + " " + this.organizationNumber;
    }
}

Start the Spring Boot App. See the result in the console.

Hibernate: create table organization (id bigint not null, organization_number varchar(255) not null, name varchar(255) not null, primary key (id)) engine=InnoDB

In the database see the desc result.

mysql> desc organization;
+---------------------+--------------+------+-----+---------+-------+
| Field               | Type         | Null | Key | Default | Extra |
+---------------------+--------------+------+-----+---------+-------+
| id                  | bigint(20)   | NO   | PRI | NULL    |       |
| organization_number | varchar(255) | NO   | UNI | NULL    |       |
| name                | varchar(255) | NO   |     | NULL    |       |
+---------------------+--------------+------+-----+---------+-------+
3 rows in set (0.00 sec)