jpa中生成的表中的错误排序

Posted

技术标签:

【中文标题】jpa中生成的表中的错误排序【英文标题】:Wrong ordering in generated table in jpa 【发布时间】:2010-11-20 20:44:10 【问题描述】:

这(应该)是一件相当简单的事情,但是我很挣扎。

我想要这样生成一个表格:

ID 组织编号 姓名

但是,当我查看数据库时,我发现排序错误。有人知道我如何强制休眠/jpa 生成具有正确顺序的表吗?

描述组织; +--------------------+-------------+------+------+ ---------+----------------+ |领域 |类型 |空 |钥匙 |默认 |额外 | +--------------------+-------------+------+------+ ---------+----------------+ |编号 |大整数(20) |否 |优先级 |空 |自动增量 | |姓名 | varchar(255) |否 | |空 | | |组织编号 | varchar(255) |否 |统一 |空 | | +--------------------+-------------+------+------+ ---------+----------------+

这是我的实体 bean 的样子:

@Entity
@NamedQuery(name = "allOrganizations", query = "SELECT org FROM Organization org order by name")
public class Organization 

    private Long id;
    private String organizationNumber;
    private String name;

    public Organization() 
    

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

    @Id
    @GeneratedValue
    public Long getId() 
        return id;
    

    @SuppressWarnings("unused")
    private void setId(Long id) 
        this.id = id;
    

    @NotEmpty
    @Column(unique=true, nullable=false)
    public String getOrganizationNumber() 
        return organizationNumber;
    
       public void setOrganizationNumber(String organizationNumber) 
        this.organizationNumber = organizationNumber;
    


    @NotEmpty
    @Column(nullable=false)
    public String getName() 
        return name;
    

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

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

【问题讨论】:

【参考方案1】:

Hibernate 按字母 顺序生成列。根据this post 给出的原因是:

它被排序以确保确定性 跨集群排序。

我们不能依赖虚拟机返回 每次都以相同的顺序执行方法 所以我们必须做点什么。

显然它曾经是按发生顺序排列的,但这在 3.2.0 GA 和 3.2.1 GA 之间发生了变化。

我还找到了Schema auto generation creates columns in alphabetical order for compound primary keys,这似乎是你的问题。这张票是关于主键顺序变化的,这会对索引性能产生负面影响。

除了以正确的顺序命名列的解决方法之外,没有其他解决方法(不,我不是在开玩笑)。

【讨论】:

您关于为什么会这样的答案是正确的,因为在注释方面没有真正的顺序。另一种解决方案是不使用模式自动生成。 您不能使用反射,遍历字段并以这种方式构建列列表吗? 哈哈。我无法理解在 @Column(order=1) 中有一个值有多困难,以便它接受一个 order 元素,然后确保正确的排序。不过,谢谢您的回答。 @Shervin 我认为这很难@Column 的定义是在 JPA 规范中指定的,所以 Hibernate 不能改变它。他们需要通过工作组/社区推动将新功能包含在 JPA 的下一次迭代中——这将受到其他供应商的同意。 2019年还是一无所有?【参考方案2】:

DataNucleus 允许扩展指定模式生成的位置,FWIW。

【讨论】:

我建议:一旦 Hibernate 创建了一个表,您可以更改它以按照您想要的方式对列进行排序。我已经这样做了。 Toad 之类的工具将通过 UI 上的“向上/向下”列选项轻松实现 您能否分享一下,如何使用 Jpa 向上/向下更改表格列? @sabarinathanu【参考方案3】:

只要可以更改类成员的内部名称,您就可以设置所需的顺序,因为列的顺序取自字段名称,而不是取自 getter、setter 或列。

因此,如果类成员是私有的(根据需要),您应该只列出它们(例如通过在它们前面加上“a_”、“b_”、“c_”...)而不更改任何一个 getter,设置器或列名。

例如下面的类定义:

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

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

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

生成下表:

  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)

这不是很有效,因为通常我们会按父级和关系类型来搜索子级。

我们可以在不影响其余实现的情况下更改私有名称:

@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; 

现在已经生成了:

  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)

当然,最好不要依赖类成员的内部字典顺序,但我没有看到更好的解决方案。

【讨论】:

我认为与其他替代方案相比,它是一个更好、更清洁、更简单的解决方案。谢谢。【参考方案4】:

我也有同样的问题。终于找到了解决办法。

查找有关 hibernate 核心的外部库并找到 org.hibernate.cfg.PropertyContainer 类并复制内容。

在您的根文件夹中创建org.hibernate.cfg 包和PropertyContainer 类。

粘贴org.hibernate.cfg.PropertyContainer 内容并将所有TreeMap 替换为您创建的PropertyContainer 类中的LinkedHashMap

/*
 * 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 XPropertys 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() );
    

修复了组织类。

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;
    

启动 Spring Boot 应用程序。在控制台中查看结果。

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

在数据库中查看 desc 结果。

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)

【讨论】:

以上是关于jpa中生成的表中的错误排序的主要内容,如果未能解决你的问题,请参考以下文章

JPA 存储库:将实体保存在大表中的问题 - 超时错误 [重复]

派生数据中生成的未知文件夹

使用 JDBC PreparedStatement 返回 MySql 中生成的键

在 scss 辅助方法中生成的资产路径具有重复的资产前缀

在 Python 3.5 中读取在 Python 2.7 中生成的文件

如何在 Excel 中生成的 SQL 插入语句中获取行数?