JPA Pitfalls

Ich hatte ja in meiner Bachelorarbeit die Umstellung einer Persistenzschicht zum Thema. Es sollte von JDO nach JPA gehen. Dabei habe ich viele Persistenzframeworks miteinander verglichen und mich damals für EclipseLink entschieden.

Jetzt wo meine Bachelorarbeit von 2010 umgesetzt wird, kommen zum Teil Probleme zum Vorschein, die in meiner kleinen Testumgebung damals noch nicht wirklich sichtbar waren.  Eines dieser Probleme ist die Limitierung eine SQL-Abfrage mit IN unter Oracle. Es sind nur 1000 Argumente erlaubt und es traten in der Software ringsum auch mal größere Argumentlisten auf, die der Datenbankserver regelmäßig mit ORA-01795 quitierte.

Um dies zu umgehen wurde schon das alte Persistenzframework ein wenig angepasst. Also kam mir die Aufgabe zu, das nun auch mit EclipseLink zu tun. Nach einiger Recherche und der Nachfrage auf der Mailingliste habe ich dazu mal einen Bugeintrag eingestellt. Meine vorläufige Umgehungsstrategie möchte ich euch natürlich nicht vorenthalten.

 /**
   * Passt den SQL String an, wenn zum beispiel eine IN-Funktion
   * 
   * @param query Die auszuführende Query
   * @param wrapper Wrapper (da wir hier statisch sind) oder {@code null} wenn wir aus der {@link DBVerbindung} kommen
   * @param em Der Entitymanager in dem das ganze stattfinden soll
   * 
   */
  static void preProcessQuery(Query query, EntityManager em, AbstractQueryWrapper< ? > wrapper) {
    Session session = em.unwrap(JpaEntityManager.class).getActiveSession();
    DatabaseQuery databaseQuery = ((EJBQueryImpl< ? >) query).getDatabaseQuery();

    DatabaseRecord record;
    if (wrapper == null) {
      record = new DatabaseRecord();
    } else {
      record = wrapper.createDBRecord();
    }

    String sqlString = databaseQuery.getTranslatedSQLString(session, record);

    if (sqlString.contains(" IN ")) {
      StringBuilder sqlBuilder = new StringBuilder();
      String[] inTokens = sqlString.split(" IN "); 

      // Der Teil vor dem ersten IN
      boolean not = inTokens[0].endsWith("NOT");
      sqlBuilder.append(inTokens[0]);

      // Der Spaltenname nach dem gesucht wird
      String descriptor = inTokens[0].substring(inTokens[0].indexOf('(') + 1);
      descriptor = descriptor.split(" ")[0]; 

      for (int i = 1; i < inTokens.length; i++) {         
          // Der Teil in der Klammer        
          String inArgument = inTokens[i];         
          if (i > 1) {
          not = inTokens[i - 1].endsWith("NOT"); 
          descriptor = inTokens[i - 1].substring(inTokens[i - 1].indexOf('(') + 1, inTokens[i - 1].lastIndexOf(" NOT")); 
        }
        StringTokenizer argumentTokenizer = new StringTokenizer(inArgument, ","); 
        if (argumentTokenizer.countTokens() < 1000) {
          sqlBuilder.append(" IN "); 
          sqlBuilder.append(inArgument);
        } else {
          sqlBuilder.append(" IN "); 
          int numArguments = argumentTokenizer.countTokens();
          for (int j = 1; j < numArguments; j++) {
            sqlBuilder.append(argumentTokenizer.nextToken());

            // Die magische ORA-Grenze
            if (j % 1000 == 0) {
              sqlBuilder.append(')');
              if (not) {
                sqlBuilder.append(" AND "); 
                sqlBuilder.append(descriptor);
                sqlBuilder.append(" NOT IN(");
              } else {
                sqlBuilder.append(" OR");
                sqlBuilder.append(descriptor);
                sqlBuilder.append(" IN(");              
              }
            } else {
              sqlBuilder.append(',');
            }
          }
          sqlBuilder.deleteCharAt(sqlBuilder.length() - 1);
          sqlBuilder.append("))"); 
        }

      }

      ReadAllQuery readAllQuery = query.unwrap(ReadAllQuery.class);
      readAllQuery.setSQLString(sqlBuilder.toString());
      readAllQuery.setShouldPrepare(true);
    }
  }

Über Steffen Förster

Softwareentwickler und Freifunker
Dieser Beitrag wurde unter Java veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.