java Hibernate中命名策略的迁移

jm81lzqq  于 2023-01-24  发布在  Java
关注(0)|答案(2)|浏览(151)

我正在做一个Java项目,这个项目很多年前就开始了,最初的开发人员都早已不在了,我继承的一个类是Hibernate NamingStrategy接口的一个实现。
现在NamingStrategy已经被弃用了,我想过渡到一个仍然支持的版本。不幸的是,我不知道该怎么做。我已经看到了关于这个主题的两种建议:大多数在线用户推荐使用隐式命名策略和物理命名策略,而Hibernate Javadoc推荐使用命名策略代理。
在这两种情况下,对于一个不成熟的Hibernate用户来说,可用的信息不足以理解如何进行迁移。NamingStrategy有10个方法,每个方法都接受字符串参数。PhysicalNamingStrategy有5个方法,每个方法都不接受字符串参数。
有人能帮助我找到安全的解决方法吗?如果有帮助的话,我在下面附上了我们的NamingStrategy实现。

package gov.nasa.ziggy.services.database;

import javax.persistence.Column;
import javax.persistence.Table;

import org.hibernate.AssertionFailure;
import org.hibernate.cfg.NamingStrategy;
import org.hibernate.internal.util.StringHelper;

import gov.nasa.ziggy.util.StringUtils;
import gov.nasa.ziggy.util.StringUtils.TextCase;

/**
 * This class implements {@link org.hibernate.cfg.NamingStrategy} for the database naming
 * conventions defined for the Ziggy project. Essentially, this consists of converting the Java
 * camel-case names to an all-caps with underscores format.
 * <p>
 * This code is based on {@link org.hibernate.cfg.ImprovedNamingStrategy}, the main difference being
 * that names that are explicitly defined in the code annotations (like {@link Table},
 * {@link Column}, etc.) are not modified.
 *
 */
public class ZiggyNamingStrategy implements NamingStrategy {
    /**
     * A convenient singleton instance
     */
    public static final NamingStrategy INSTANCE = new ZiggyNamingStrategy();

    /**
     * Return the unqualified class name, mixed case converted to underscores
     */
    @Override
    public String classToTableName(String className) {
        return addUnderscores(StringHelper.unqualify(className));
    }

    /**
     * Return the full property path with underscore seperators, mixed case converted to underscores
     */
    @Override
    public String propertyToColumnName(String propertyName) {
        return addUnderscores(StringHelper.unqualify(propertyName));
    }

    /**
     * This method is called for names explicitly defined in the annotations. Leave the name alone!
     */
    @Override
    public String tableName(String tableName) {
        return tableName;
    }

    /**
     * This method is called for names explicitly defined in the annotations. Leave the name alone!
     */
    @Override
    public String columnName(String columnName) {
        return columnName;
    }

    @Override
    public String collectionTableName(String ownerEntity, String ownerEntityTable,
        String associatedEntity, String associatedEntityTable, String propertyName) {
        String s = tableName(ownerEntityTable + '_' + propertyToColumnName(propertyName));
        System.out.println("collection table name " + s);
        return s;
    }

    /**
     * Return the argument
     */
    @Override
    public String joinKeyColumnName(String joinedColumn, String joinedTable) {
        String s = addUnderscores(joinedTable) + "_" + addUnderscores(joinedColumn);
        System.out.println("joinkey column name " + s);
        return s;
    }

    /**
     * Return the property name or propertyTableName
     */
    @Override
    public String foreignKeyColumnName(String propertyName, String propertyEntityName,
        String propertyTableName, String referencedColumnName) {
        String header = propertyName != null ? StringHelper.unqualify(propertyName)
            : propertyTableName;
        if (header == null) {
            throw new AssertionFailure("NamingStrategy not properly filled");
        }
        String s = addUnderscores(propertyTableName) + "_" + addUnderscores(referencedColumnName);
        if (s.equals("PI_PS_NAME_NAME")) {
            System.out.println("foreign key column name " + s);
        }
        return s;
    }

    /**
     * Return the column name or the unqualified property name
     */
    @Override
    public String logicalColumnName(String columnName, String propertyName) {
        String s = StringHelper.isNotEmpty(columnName) ? columnName
            : StringHelper.unqualify(propertyName);
        if (columnName == null) {
            System.out
                .println("logical column name " + columnName + "  " + propertyName + "  " + s);
        }
        return s;
    }

    /**
     * Returns either the table name if explicit or if there is an associated table, the
     * concatenation of owner entity table and associated table otherwise the concatenation of owner
     * entity table and the unqualified property name
     */
    @Override
    public String logicalCollectionTableName(String tableName, String ownerEntityTable,
        String associatedEntityTable, String propertyName) {
        if (tableName != null) {
            return tableName;
        }
        // use of a stringbuffer to workaround a JDK bug
        String s = new StringBuffer(ownerEntityTable).append("_")
            .append(associatedEntityTable != null ? associatedEntityTable
                : StringHelper.unqualify(propertyName))
            .toString();
        System.out.println("logical collection table name " + s);
        return s;
    }

    /**
     * Return the column name if explicit or the concatenation of the property name and the
     * referenced column
     */
    @Override
    public String logicalCollectionColumnName(String columnName, String propertyName,
        String referencedColumn) {
        String s = StringHelper.isNotEmpty(columnName) ? columnName
            : StringHelper.unqualify(propertyName) + "_" + referencedColumn;
        if (!s.equals(columnName)) {
            System.out.println("logical collection column name " + columnName + "  " + propertyName
                + "  " + referencedColumn + "  " + s);
        }
        return s;
    }

    protected String addUnderscores(String original) {
        return StringUtils.camelCaseToUnderscores(original, TextCase.UPPER);
    }

}
rqenqsqc

rqenqsqc1#

所以我最近重写了这些接口的Javadoc,以便于理解它们之间的区别,但是新的Javadoc还不能在线使用,因为6.2 final的发布推迟了一周。
所以让我复制/粘贴Javadoc中的解释,HTH。

Package org.hibernate.boot.model.naming

此API允许泛型代码在确定数据库对象(表、列和约束)名称的过程中进行干预。
名称确定分为两个阶段:
1.* 逻辑命名 * 是应用命名规则来确定在O/RMap中未显式分配名称的对象的名称的过程。
在这里,这是org.hibernate.boot.model.naming.ImplicitNamingStrategy实现的职责。
1.* 物理命名 * 是应用附加规则将逻辑名称转换为将在数据库中使用的实际"物理"名称的过程。例如,规则可能包括使用标准化缩写或修剪标识符长度等内容。
在这里,这是PhysicalNamingStrategy实现的职责。

ImplicitNamingStrategy

一组规则,用于在Java域模型元素的Map未显式指定(既未在Java代码注解中指定,也未在基于XML的Map文档中指定)时确定Map关系数据库对象的逻辑名称。
例如,如果注解为@Entity的Java类没有@Table注解,则使用ImplicitEntityNameSource调用determinePrimaryTableName,以提供对有关Java类及其实体名称的信息的访问。
另一方面,当明确指定逻辑名时,例如,使用@Table来指定表名,或者使用@Column来指定列名,则不调用PhysicalNamingStrategy,并且PhysicalNamingStrategy没有机会干预逻辑名的确定。
然而,通过org.hibernate.boot.model.naming.ImplicitNamingStrategy将进一步的处理级别应用于所得到的逻辑名称,以便确定关系数据库模式中的"最终最终"物理名称。强烈建议使用自定义的ImplicitNamingStrategy,而不要使用繁琐和重复的显式表名和列名Map。我们预计大多数使用Hibernate的项目将以ImplicitNamingStrategy的定制实现为特色。
可以使用配置属性"hibernate.implicit_naming_strategy"来选择ImplicitNamingStrategy

PhysicalNamingStrategy

一组规则,用于根据对象/关系Map所指定的逻辑名来确定关系数据库架构中对象的物理名。

  • 物理名称是用于与数据库交互的名称,并且将始终用于生成的SQL(DML和DDL)中。
  • 逻辑名称是用于Java代码注解和XMLMap文档中的名称。

逻辑名称在Map和数据库模式之间提供了一个额外的间接级别,并且PhysicalNamingStrategy甚至允许在关系模式的特征特别不优雅的遗留命名约定的情况下在Map中使用更"自然"的命名,例如,它可以保护Map不受老式实践的影响,比如在表名前面加上TBL_
但是,请注意,手写的原生SQL必须以物理名称的形式编写,因此这里的抽象在某种意义上是"不完整的"。
可以使用配置属性"hibernate.physical_naming_strategy"来选择PhysicalNamingStrategy

gxwragnw

gxwragnw2#

小故事

不要使用Hibernate中的命名策略,这个特性不稳定、不可靠、不可维护,而且还会增加技术债务(这正是您实际得到的:* 此时NamingStrategy已弃用,我希望过渡到将来仍受支持的功能 *),此外,即使Hibernate团队也承认此功能不稳定:

@Incubating
public interface PhysicalNamingStrategy {

孵育时:
将某些包、类型等标记为孵化中,可能是递归的。孵化表示某些东西仍在积极开发中,因此可能在以后更改;“技术预览”。
这些类型和方法的用户被认为是早期采用者,他们帮助形成这些类型/方法的最终定义沿着消费者的需求。
最好的选择是在相关实体/字段上放置有效/实际的@Table@Column注解,并结束技术债务--作为开发人员,保持代码可维护是您的责任。
Hibernate团队无意中打开了潘多拉的盒子,即将发布的6.2版本是关闭它的最佳候选版本,唯一需要的操作是将@Incubating替换为@Deprecated
"说来话长"
I.命名策略不能跨JPA实现移植,例如,如果您试图找到如何在eclipselink中实现相同的策略,您可能会发现something like

@Override
    public void customize(Session session) throws SQLException {
        for (ClassDescriptor descriptor : session.getDescriptors().values()) {
            // Only change the table name for non-embedable entities with no
            // @Table already
            if (!descriptor.getTables().isEmpty() && descriptor.getAlias().equalsIgnoreCase(descriptor.getTableName())) {
                String tableName = addUnderscores(descriptor.getTableName());
                descriptor.setTableName(tableName);
                for (IndexDefinition index : descriptor.getTables().get(0).getIndexes()) {
                    index.setTargetTable(tableName);
                }
            }
            for (DatabaseMapping mapping : descriptor.getMappings()) {
                // Only change the column name for non-embedable entities with
                // no @Column already

                if (mapping instanceof AggregateObjectMapping) {
                    for (Association association : ((AggregateObjectMapping) mapping).getAggregateToSourceFieldAssociations()) {
                        DatabaseField field = (DatabaseField) association.getValue();
                        field.setName(addUnderscores(field.getName()));
                        
                        for (DatabaseMapping attrMapping : session.getDescriptor(((AggregateObjectMapping) mapping).getReferenceClass()).getMappings()) {
                            if (attrMapping.getAttributeName().equalsIgnoreCase((String) association.getKey())) {
                                ((AggregateObjectMapping) mapping).addFieldTranslation(field, addUnderscores(attrMapping.getAttributeName()));
                                ((AggregateObjectMapping) mapping).getAggregateToSourceFields().remove(association.getKey());
                                break;
                            }
                        }
                    }
                } else if (mapping instanceof ObjectReferenceMapping) {
                    for (DatabaseField foreignKey : ((ObjectReferenceMapping) mapping).getForeignKeyFields()) {
                        foreignKey.setName(addUnderscores(foreignKey.getName()));
                    }
                } else if (mapping instanceof DirectMapMapping) {
                    for (DatabaseField referenceKey : ((DirectMapMapping) mapping).getReferenceKeyFields()) {
                        referenceKey.setName(addUnderscores(referenceKey.getName()));
                    }
                    for (DatabaseField sourceKey : ((DirectMapMapping) mapping).getSourceKeyFields()) {
                        sourceKey.setName(addUnderscores(sourceKey.getName()));
                    }
                } else {
                    DatabaseField field = mapping.getField();
                    if (field != null && !mapping.getAttributeName().isEmpty() && field.getName().equalsIgnoreCase(mapping.getAttributeName())) {
                        field.setName(addUnderscores(mapping.getAttributeName()));
                    }
                }
            }
        }
    }

抱歉,但这不是我想要维护的代码,另一方面,我可以肯定地说eclipselink不是我想要处理的JPA实现-问题是它严格遵循JPA规范(Hibernate肯定更加灵活和可扩展)但是,您需要记住,命名策略是全局适用的,因此,当您采用基于JPA的第三方库或插件时,您可能会“惊讶”该库或插件与您的命名策略冲突。
二、可预测的名字有意义:

  • 在诊断性能问题时,DBA通常会告诉您哪些查询比较慢,然后您需要以某种方式确定哪些代码会产生这些比较慢的查询(在这里我们需要对spring-data-jpa的命名约定说声“谢谢”),这需要花时间将SQL查询与JPQL查询进行匹配,如果您认为SQL查询没有意义并且可以使用micrometer/grafana收集性能统计信息,我有个坏消息要告诉你that does not work in Hibernate
  • 命名策略设法覆盖表名和列名,但不覆盖索引名,这从性能Angular 看实际上很重要:我试着猜测名称类似UK_jymdjmt0g2vxkk0fp56xlhjud的索引覆盖了哪些列。需要询问DBA吗?好吧,您的代码是不可维护的...维护有关数据库的单一真实来源,并在源代码中保留尽可能多的信息
  • 如果你不知道数据库中列名和表名,你就缺少了很多重要的东西,例如:
  • 无法验证数据库状态(无需介绍hibernate.hbm2ddl.auto=validate-它的功能极其有限)
  • 您无法将数据库错误转换为用户友好的消息(如何将“重复键值违反了唯一约束“UK_jymdjmt0g2vxkk0fp56xlhjud”转换为“名称为XXX的用户已经存在”?)
  • 无法确定文本字段的最大允许长度和数值字段的精度
  • 最后,如果我不知道目标列名和表名,我应该如何编写SQL查询、迁移场景等?

III.特征被破坏
小测验:

:当InheritanceType=JOINED

/**
     * (Optional) The name of the table that contains the column. 
     * If absent the column is assumed to be in the primary table.
     */
    String table() default "";

AHibernate同时接受逻辑表名和物理表名。
Q:如果您创建了一个“视图”,并且希望保持视图状态与持久性上下文同步,那么您需要在@Synchronize注解中指定什么:

@Target(TYPE)
@Retention(RUNTIME)
public @interface Synchronize {
    /**
     * Table names.
     */
    String[] value();
}

A:一个
Q:您需要触发一个SQL过程/更新语句,并且希望它的执行与持久化上下文保持同步,您需要在org.hibernate.query.native.spaces hint中指定什么(另一个概念,没有描述):

/**
     * Hint for specifying query spaces to be applied to a NativeQuery.
     *
     * Passed value can be any of:<ul>
     *     <li>List of the spaces</li>
     *     <li>array of the spaces</li>
     *     <li>String as "whitespace"-separated list of the spaces</li>
     * </ul>
     *
     * Note that the passed space need not match any real spaces/tables in
     * the underlying query.  This can be used to completely circumvent
     * the auto-flush checks as well as any cache invalidation that might
     * occur as part of a flush.  See {@link org.hibernate.query.SynchronizeableQuery}
     * and {@link FlushMode#MANUAL} for more information.
     *
     * @see org.hibernate.query.SynchronizeableQuery
     * @see #HINT_FLUSH_MODE
     */
    String HINT_NATIVE_SPACES = "org.hibernate.query.native.spaces";

A:您需要指定您认为可行的所有内容:物理表名、逻辑表名、实体名、高速缓存区域名、母亲的婚前姓、第一只宠物的名字等--没有人知道如何正确使用这些名称,例如Thorben Janssen认为we need to pass class name thereHibernate假定查询空间实际上是一个物理表名(如何使用@jakarta.persistence.QueryHint注解将实体类作为提示值传递?)即使Hibernate项目主管has no idea about how to use that properly

逻辑名称是在Java代码和XMLMap文档的注解中使用的名称
如果是@jakarta.persistence.QueryHint,我们需要使用物理表名!逻辑在哪里???

相关问题