Hibernate NamingStrategy für Oracle

In dem Projekt in dem ich aktuell arbeite, generieren wir unser Datenbank praktisch vollständig per Hibernate Annotations. Was die Benennung von Tabellen und Spalten angeht, verlassen wir uns dabei praktisch vollständig auf die Standards von Hibernate, die recht brauchbar sind. Eigentlich gibt es nur zwei echte Probleme und eine kleinere Unanehmlichkeit:

  1. Oracle unterstützt nur Spaltennamen und Tabellennamen bis zu einer Länge von 30 Zeichen (Ende der 70er war dies vermutlich up-to-date oder sogar fortschrittlich).
  2. Werden mehrere gleichartige Objekte ‘Embedded‘ in einer Entität so gibt es einen Namenskonflikt.
  3. Standardmäßig geht die Gliederung eines Namens durch CamelCasing verloren, wenn er in Tabellen und Spaltennamen umgewandelt werden. (z.B. wird aus tollerAttributName einfach TOLLERATTRIBUTNAME. Viele würden TOLLER_ATTRIBUT_NAME bevorzugen.

Für Problem 2. und 3. gibt es eine fertige Lösung: DefaultComponentSafeNamingStrategy, die einfach vor der Erzeugung der SessionFactory in der Konfiguration gesetzt wird:

Configuration config = new AnnotationConfiguration().configure();
config.setNamingStrategy(new DefaultComponentSafeNamingStrategy());
sessionFactory = config.buildSessionFactory();

Auf der Suche nach dieser Lösung bin ich auch bei ein paar Seiten vorbeigekommen, bei denen Namenskonvention mit Hilfe einer Namingstrategy umgesetzt werden.

we extended the ImprovedNamingStrategy further to handle pluralization of tablenames

Das verletzt zwar das Prinzip: Don’t repeat yourself, denn niemand würde wohl erwarten, dass die CUSTOMER Tabelle nur einen Kunden enthält, aber wer’s mag. Immer noch besser als Code Obfuscation

class: tr.PopulationCenter à table: T_PPLTN_CNTR
property: upperPopulationCenter à column: UPPR_PPLTN_CNTR

Ganz Abseits von der länglichen Diskussion über Codeconventionen, so ist die Idee, sie per NamingStrategy umzusetzen, doch definitiv eine Gute. Ich habe mein neu gewonnenes Wissen erstmal ausprobiert um eine Namingstrategy zu konstruieren, die Tabellen und Spaltennamen auf 30 Zeichen kürzt, hoffentlich ohne dabei völlig unleserlich zu werden. Der Algorithmus wandelt Namen wie Auftragsbestaetigung_Rueckantwort_lineitem um in Auftragsb_Rueckantw_lineitem. Der Algorithmus sucht nach dem längsten Teilstück und kürzt es um die letzen Vokale inklusive eventuell nachfolgender Konsonanten. Dies wird wiederholt, bis der Name maximal noch 30 Zeichen lang ist.

Auftragsbestaetigung aus dem obigen Beispiel wird also der Reihe nach Auftragsbestaetig, Auftragsbestaet, Auftragsbest und schließlich Auftragsbest


import java.util.StringTokenizer;

import org.hibernate.cfg.DefaultComponentSafeNamingStrategy;

public class OracleNamingStrategy
     extends DefaultComponentSafeNamingStrategy
{

  @Override
  public String collectionTableName(String ownerEntity,
      String ownerEntityTable, String associatedEntity,
      String associatedEntityTable, String propertyName)
  {
    return abbreviateName(super.collectionTableName(
        ownerEntity,
        ownerEntityTable, associatedEntity,
        associatedEntityTable, propertyName));
  }

  @Override
  public String foreignKeyColumnName(String propertyName,
      String propertyEntityName, String propertyTableName,
      String referencedColumnName)
  {
    return abbreviateName(super.foreignKeyColumnName(
        propertyName,
        propertyEntityName, propertyTableName,
        referencedColumnName));
  }

  @Override
  public String logicalCollectionColumnName(
      String columnName,
      String propertyName,
      String referencedColumn)
  {
    return abbreviateName(super.logicalCollectionColumnName(
        columnName,
        propertyName, referencedColumn));
  }

  @Override
  public String logicalCollectionTableName(String tableName,
      String ownerEntityTable, String associatedEntityTable,
      String propertyName)
  {
    return abbreviateName(super.logicalCollectionTableName(
        tableName,
        ownerEntityTable, associatedEntityTable,
        propertyName));
  }

  @Override
  public String logicalColumnName(String columnName,
        String propertyName)
  {
    return abbreviateName(super.logicalColumnName(
        columnName, propertyName));
  }

  @Override
  public String propertyToColumnName(String propertyName)
  {
    return abbreviateName(
        super.propertyToColumnName(propertyName));
  }

  private static final int MAX_LENGTH = 30;

  public static String abbreviateName(String someName)
  {
    if (someName.length() <= MAX_LENGTH) return someName;

    String[] tokens = splitName(someName);
    shortenName(someName, tokens);

    return assembleResults(tokens);
  }

  private static String[] splitName(String someName)
  {
    StringTokenizer toki = new StringTokenizer(someName, "_");
    String[] tokens = new String[toki.countTokens()];
    int i = 0;
    while (toki.hasMoreTokens())
    {
      tokens[i] = toki.nextToken();
      i++;
    }
    return tokens;
  }

  private static void shortenName(
       String someName, String[] tokens)
  {
    int currentLength = someName.length();
    while (currentLength > MAX_LENGTH)
    {
      int tokenIndex = getIndexOfLongest(tokens);
      String oldToken = tokens[tokenIndex];
      tokens[tokenIndex] = abbreviate(oldToken);
      currentLength -=
           oldToken.length() - tokens[tokenIndex].length();
    }
  }

  private static String assembleResults(String[] tokens)
  {
    StringBuilder result = new StringBuilder(tokens[0]);
    for (int j = 1; j < tokens.length; j++)
    {
      result.append("_").append(tokens[j]);
    }
    return result.toString();
  }

  private static String abbreviate(String token)
  {
    final String VOWELS = "AEIOUaeiou";
    boolean vowelFound = false;
    for (int i = token.length() - 1; i >= 0; i--)
    {
      if (!vowelFound)
        vowelFound = VOWELS.contains(
             String.valueOf(token.charAt(i)));
      else if (!VOWELS.contains(String.valueOf(token.charAt(i))))
        return token.substring(0, i + 1);
    }
    return "";
  }

  private static int getIndexOfLongest(String[] tokens)
  {
    int maxLength = 0;
    int index = -1;
    for (int i = 0; i < tokens.length; i++)
    {
      String string = tokens[i];
      if (maxLength < string.length())
      {
        maxLength = string.length();
        index = i;
      }
    }
    return index;
  }
}

Das ganze ist im Halbschlaf entstanden und weder gründlich getestet noch intensiv genutzt worden. Benutzung also auf eigene Gefahr. Sollte es sich aber als praktisch erweisen, so wie es ist, oder in abgewandelter Form so würde ich mich sehr über einen entsprechenden Kommentar freuen

Share:
  • DZone
  • Digg
  • del.icio.us
  • Reddit
  • Facebook
  • Twitter
This entry was written by Jens Schauder , posted on Friday February 22 2008at 12:02 am , filed under Java, Oracle, Softwareentwicklung . Bookmark the permalink . Post a comment below or leave a trackback: Trackback URL.

9 Responses to “Hibernate NamingStrategy für Oracle”

  • Hallo echt guten Blog, bekommst du Verdienste dadurch ? Ich selbst hab für meiner Seite sehr lange nach Gewinnmöglichkeiten gesucht aber gar nichts gefunden. gestern bin ich zum Glück auf backlinkfountain.de (siehe: http://snurl.com/xpj2i ) gestossen, damit mache ich fast 70 Euro im Monat. Hättest du zufaellig eine zusätzliche Alternative fuer mich ? Waehre echt gut. 35p1h5z

  • Dominik S. says:

    Vielen Dank, Sie haben mir viel Arbeit abgenommen! Nur einen Verbesserungsvorschlag möchte ich gerne platzieren: Die gezeigte NamingStrategy hat Mühe mit Namen, welche mehrere zusammenhängende Grossbuchstaben aufweisen. So wird aus EMailAdresse em_ail_adresse, was nicht sehr leserlich ist.

  • Dominik S. says:

    Nochmals Nachtrag (bezieht sich auf meinen Post im anderen Blogeintrag): Für mich stimmt’s wenn ich die Methode isMultipleUpperToLower in addUnderscores rausnehme. Ich habe das Ganze übrigens auf NHibernate und C# übertragen:

        public class OracleNamingStrategy : INamingStrategy
        {
            public string ClassToTableName(string className)
            {
                return AbbreviateName(TableName(StringHelper.Unqualify(className)));
            }
    
            public string PropertyToColumnName(string propertyName)
            {
                return AbbreviateName(AddUnderscores(propertyName));
            }
    
            public string TableName(string tableName)
            {
                return AbbreviateName(AddUnderscores(tableName));
            }
    
            public string ColumnName(string columnName)
            {
                return AbbreviateName(AddUnderscores(columnName));
            }
    
            public string PropertyToTableName(string className, string propertyName)
            {
                string tableName = "Lt";
                tableName += className.Substring(className.LastIndexOf(".")).Remove(0,1);
                tableName += propertyName.Substring(propertyName.LastIndexOf(".")).Remove(0,1);
                return AbbreviateName(AddUnderscores(tableName));
            }
    
            public string LogicalColumnName(string columnName, string propertyName)
            {
                return ColumnName(columnName) + '_' + PropertyToColumnName(propertyName);
            }
    
            private static string AddUnderscores(string name)
            {
                if (name == null)
                    return null;
                StringBuilder buf = new StringBuilder(name.Replace('.', '_'));
                for (int i = 1; i  1 && char.IsUpper(buf[i - 1])
                        && char.IsUpper(buf[i - 2])
                        && char.IsLower(buf[i]);
            }
    
            private static bool IsLowerToUpper(StringBuilder buf, int i)
            {
                return char.IsLower(buf[i - 1])
                        && char.IsUpper(buf[i]);
            }
    
            private static int MAX_LENGTH = 30;
    
            public static string AbbreviateName(string someName)
            {
                if (someName.Length  MAX_LENGTH)
                {
                    int tokenIndex = GetIndexOfLongest(tokens);
                    string oldToken = tokens[tokenIndex];
                    tokens[tokenIndex] = Abbreviate(oldToken);
                    currentLength -= oldToken.Length - tokens[tokenIndex].Length;
                }
            }
    
            private static string AssembleResults(string[] tokens)
            {
                StringBuilder result = new StringBuilder(tokens[0]);
                for (int j = 1; j = 0; i--)
                {
                    if (!vowelFound)
                        vowelFound = VOWELS.Contains(token[i].ToString());
                    else if (!VOWELS.Contains(token[i].ToString()))
                        return token.Substring(0, i + 1);
                }
                return "";
            }
    
            private static int GetIndexOfLongest(string[] tokens)
            {
                int maxLength = 0;
                int index = -1;
                for (int i = 0; i < tokens.Length; i++)
                {
                    string tString = tokens[i];
                    if (maxLength < tString.Length)
                    {
                        maxLength = tString.Length;
                        index = i;
                    }
                }
                return index;
            }
    
        }
    

    Es hat darin noch eine kleine Modifikation: Collection tables werden bei uns mit einem vorangestellten “lt_” benannt.

    Hoffe, das nützt jemandem.

    – English summary:

    I’ve translated the NamingStrategy for Oracle to NHibernate and C#. Small additions:
    - I’m not using the IsMultipleUpperToLower() method in AddUnderscores() because I’m not happy with the result when there’s a bunch of consecutive uppercase characters present.
    - We want our collection tables to be called lt_* so the above code reflects that.

  • Cool, dass das jemanden geholfen hat. Und danke für die modifizierte Version.

  • Dominik S. says:

    Leider hat WordPress meinen Code-Tag nicht richtig angewandt oder ich bin zu wenig geübt darin, auf WordPress Kommentare mit Code zu schreiben… Können Sie die Formatierung meines Kommentars noch beeinflussen?

  • Der Hinweis über dem Kommentarfeld ist leider falsch. Einfach das

     tag verwenden, dann funktioniert es.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>