EntityManager uniqueResult

krgewb

Top Contributor
Das ist Mein Code:
Java:
public String getValueOfSettingsByVariable(String variable) {
        Query query = entityManager.createQuery("SELECT e.value FROM SettingsEntity e WHERE e.variable = :variable");
        query.setParameter("variable", variable);
        query.setMaxResults(1);
        List<String> list = query.getResultList();
        if (list != null && list.size() > 0) {
            return list.get(0);
        }
        return null;
}
(VARIABLE ist Primärschlüssel)

Ich würde es gerne anders machen. Das wäre mein Ziel (Pseudocode):
Java:
public String getValueOfSettingsByVariable(String variable) {
        Query query = entityManager.createQuery("SELECT e.value FROM SettingsEntity e WHERE e.variable = :variable");
        query.setParameter("variable", variable);
        return query.uniqueResult();
}
 

mihe7

Top Contributor
Es gibt getSingleResult(). In dem Fall muss das Ergebnis aber tatsächlich aus genau einer Zeile (nicht 0, nicht 2, nicht mehr) bestehen, ansonsten fliegt eine Exception.

Sonst sollte möglich sein:
Java:
return query.getResultStream().findFirst().map(String::valueOf).orElse(null);

In Deinem Fall würde ich allerdings gleich eine TypedQuery verwenden:
Java:
public String getValueOfSettingsByVariable(String variable) {
    TypedQuery<String> query = entityManager.createQuery("SELECT e.value FROM SettingsEntity e WHERE e.variable = :variable", String.class);
    query.setParameter("variable", variable);
    return query.getResultStream().findFirst().orElse(null);
}
 

krgewb

Top Contributor
EntityManager#find ?
Danke. Das werde ich am Montag ausprobieren.

Kein Problem. An einer anderen Stelle suche ich nicht anhand des Primärschlüssels. Da will ich mit ASC oder DESC sortieren und dann nur ein einziges Objekt holen.

An manchen Stellen verwende ich Repository. Da kann ich ganz einfach alle Objekte holen oder nur ein bestimmtes (anhand der ID). Ich möchte aber an manchen Stellen nicht das Objekt, sondern nur eine Spalte (im Beispiel aus Beitrag #1 ist das der String "value"). Deshalb verwende ich EntityManager.

An manchen Stellen habe ich kompliziertere Primärschlüssel. Deshalb verwende ich dort ebenfalls kein Repository. mihe7 hat mir hier gezeigt, wie es mit EntitiyManager geht: https://www.java-forum.org/thema/select-ohne-idclass-annotation.204930/#post-1376146
 

krgewb

Top Contributor
Wenn ich mehrere Spalten holen will, mache ich das so:

Java:
Query query = entityManager.createQuery("SELECT f.id, f.url, f.vpos from LayerEntity f");
List<Object[]> list = query.getResultList();
 

Oneixee5

Top Contributor
Mit List<Object[]> zu arbeiten ist doch der Horror. Man benötigt irgendwelche "magic numbers" für die Spalten. Das macht nachträgliche Änderungen unnötig schwierig.
Java:
package my.playground.c3p0;

    public record LayerRecord(Long id, String url, BigInteger vpos) {
    }

    List<LayerRecord> p = entityManager.createQuery(
                "SELECT new my.playground.c3p0.LayerRecord(e.id, e.url, e.vpos) FROM LayerEntity e",
                LayerRecord.class
        ).getResultList();

Möglich wäre theoretisch auch so etwas:
Java:
public <T> List<T> findT(Class<T> clazz) {

    String constructorVariables = Arrays.stream(clazz.getDeclaredFields())
            .map(Field::getName)
            .reduce((s1, s2) -> s1 + ", " + s2)
            .orElse("");

    var p = entityManager.createQuery(
            "SELECT new " + clazz.getName() + "(" + constructorVariables + ") FROM LayerEntity e",
            clazz
    ).getResultList();
}
Bei diesem naiven und sehr generischen Ansatz muss die Projektionsklasse die gleichen Feldnamen haben wie die Spalten der Datenbanktabelle. So ist es möglich beliebige Spaltenkombinationen abzufragen.

PS: ich habe das nicht getestet, nur so aus dem Gedächtnis geschrieben.
 
Zuletzt bearbeitet:

krgewb

Top Contributor
Wenn ich alle Objekte holen will, schreibe ich
Java:
List<StreamMonitorDataEntity> list = streamMonitorDataRepository.findAll();

Wenn ich ein Objekt holen will, von dem ich die ID weiß, schreibe ich:
Java:
Optional<CamEntity> e = camRepository.findById(viewId);
if (e.isPresent()) {
    return e.get();
} else {
    return null;
}
 

krgewb

Top Contributor
Wow. In unserem Code gibt es noch solche Stellen:

Java:
            jdbcTemplate.query(QueryConstants.SELECT_ALL_CAMS_WITH_ALL, (ResultSet rs, int rowNum) -> {

                CamDTO dbDTO = new CamDTO();

                dbDTO.setId(rs.getLong(1));
                dbDTO.setDivicro_path(rs.getString(2));
                dbDTO.setDivicro_name(rs.getString(3));
                dbDTO.setDivicroIp(rs.getString(4));
                dbDTO.setPort((rs.getString(5)));
                dbDTO.setIp(rs.getString(4));
                dbDTO.setCameraIp(rs.getString(6));
                dbDTO.setLive_url(rs.getString(7));

                NewCamDataDTO newCamDataDTO = new NewCamDataDTO();

                newCamDataDTO.setMnemonic(rs.getString(41));
                newCamDataDTO.setCameraPort(rs.getString(26));
                newCamDataDTO.setMaxOffset(rs.getInt(27));
                newCamDataDTO.setChannelId(rs.getInt(32));

                dbDTO.setNewCamData(newCamDataDTO);

                CamConfigDTO conf = new CamConfigDTO();
                conf.setDescription(rs.getString(15));
                if (rs.getString(16) != null) {
                    conf.setRecordPath(rs.getString(16).replace("/", "\\"));
                } else {
                    conf.setRecordPath(rs.getString(16));
                }
                conf.setCycleTime(rs.getInt(17));
                conf.setRecordCycleTime(rs.getInt(18));
                conf.setRecording(rs.getInt(19));
                conf.setManufacturer(rs.getString(20));
                conf.setModel(rs.getString(21));
                conf.setUsername(rs.getString(22));
                conf.setPassword(rs.getString(23));
                conf.setRecordGopSize(rs.getInt(24));
                conf.setSchedule(rs.getString(25));
           
                conf.setBuffercapacity((Integer) rs.getObject(28));
                conf.setCamServerAlias(rs.getString(29));
                conf.setCamServer(rs.getLong(30));
                conf.setCameraType((Integer) rs.getObject(31));
                dbDTO.getNewCamData().setKeepDays((Integer) rs.getObject(39));
                dbDTO.getNewCamData().setSaveRotation((Integer) rs.getObject(40));
                dbDTO.getNewCamData().setAdditionalUri(rs.getString(42));

                dbDTO.setCamConfig(conf);

                CamMotionDetetionConfigDTO mdData = new CamMotionDetetionConfigDTO();
                mdData.setMdCycleTime((Integer) rs.getObject(33));
                mdData.setMdPrealarm((Integer) rs.getObject(34));
                mdData.setMdPostalarm((Integer) rs.getObject(35));
                mdData.setMdTolerance((Integer) rs.getObject(36));
                mdData.setMdMinimumDifference((Integer) rs.getObject(37));
                mdData.setMdMinimumLightness((Integer) rs.getObject(38));
 

KonradN

Super-Moderator
Mitarbeiter
Hm, hier ist die Frage: Was sind die Alternativen? Ich selbst habe in einem Projekt ähnlichen Code, denn Hibernate & Co waren für mich einfach zu lahm. Klar - das ist ggf. für Dich nicht der Fall, aber ich sehe, dass Du in einer Abfrage gleich mehrere Entities bekommst. Hibernate macht daraus mehrere Abfragen, was dann kritisch sein kann. Das ist aber nur eine reine Option - ich habe keine Ahnung, ob es so bei Dir ist oder nicht und die Chance darauf mag sehr klein sein.

Ich komme nur eben darauf, weil ich genau so einen Fall schon bzw. immer noch habe. Ich muss sehr viele Daten exportieren und wenn Hibernate da so zusammenhängende Konstrukte laden soll, dann werden da so viele Einzelabfragen gemacht, dass es "nie" fertig wird. Da bleibt dann nur die Möglichkeit, die Abfragen komplett selbst zu machen um da dann auch entsprechend optimieren zu können.
(So gab es dann bei mir auch noch die Optimierung, dass Daten erst in temporären Tabellen sammle um diese Daten jeweils als Ganzes einfach von Kopf bis Fuß zu laden.)

Und da hatte ich dann Code, der in etwa dem entsprach, den Du da gezeigt hast. Nur eben haben wir das in die Entity gesetzt, die Entities haben einen Builder und wir nutzen keine Ordinals sondern nutzen die Strings. Da mehrere Entities in einer Row sein können und sich Feldnamen überschneiden können, habe ich noch einen Prefix dabei, so dass man etwas hat wie:
Java:
public static MyEntity createFromResultSet(String prefix, ResultSet rs) {
    return MyEntity.builder()
        .whatever(rs.getString(prefix + "whatever"))
        // ...
        .build();
}

Prinzipiell war ich am überlegen, sowas dynamisch zu bauen. Die Entities haben alle die Annotations wie @Column(name = ...) und so, aber der Aufwand diesbezüglich überschreitet die Aufwände, dies einmal selbst zu schreiben (bzw. eine KI dies aus den SQL CREATE Befehlen erzeugen zu lassen - ist ja reine Tipparbeit und die muss man nicht selbst machen ...)

Beim Zielformat habe ich übrigens dann den Code, der das automatisiert. Es werden halt Bulk Scripte erzeugt mit INSERT INTO Statements und da werte ich die Entities aus um Tabellennamen, Columnnamen, Discriminator Columns/Values, ... auszuwerten um dann dynamisch diese SQL Statements erzeugen zu können incl Unit Tests, die mich auf Veränderungen hinweisen (Ich habe eine Abhängigkeit auf die Entities der Neuentwicklung) und so ...

Also worauf ich hinaus will: So Code muss nicht zwingend falsch oder schlecht sein. Er ist heutzutage unüblich aber ich habe (teilweise schmerzlich) lernen dürfen, dass es da durchaus Notwendigkeiten geben kann :)
 

mihe7

Top Contributor
Wir haben auch Code, der Abfragen dynamisch zusammenbaut... Witzigerweise sitze ich aktuell genau an dem Refactoring von dem Mist. Dagegen ist der Code von @krgewb eine absolut saubere Lösung. Was mir allerdings nicht gefällt:
Java:
                if (rs.getString(16) != null) {
                    conf.setRecordPath(rs.getString(16).replace("/", "\\"));
                } else {
                    conf.setRecordPath(rs.getString(16));
                }
Denn das wäre ja nichts anderes als
Java:
                if (rs.getString(16) != null) {
                    conf.setRecordPath(rs.getString(16).replace("/", "\\"));
                } else {
                    conf.setRecordPath(null);
                }
was sich zu
Java:
                conf.setRecordPath(rs.getString(16) != null ? rs.getString(16).replace("/", "\\") : null);
verkürzt. Naja, und das könnte man in eine Methode auslagern:
Java:
static String windowsPathOf(String path) {
    return path == null ? null : path.replace("/", "\\");
}
so dass am Ende steht
Java:
                conf.setRecordPath(windowsPathOf(rs.getString(16));
Beim Methodennamen sind der Kreativität natürlich keine Grenzen gesetzt :D
 

krgewb

Top Contributor
Ich habe solch einen String.

Java:
protected static final String SELECT_ALL_CAMS_WITHOUT_CAMSERVER_old2 = ""
        + "SELECT "
        + "cam.ID, "                 
        + "cam.DIVICRO_PATH, "           
        + "cam.CAMERA_PORT "
        + "FROM CAMERA.CAM cam";

Die Tabelle CAMERA hat keine Spalte namens CAMERA_PORT. Trotzdem funktioniert mein Programm. Ich wollte hergehen und überall AS hinmachen.

Java:
protected static final String SELECT_ALL_CAMS_WITHOUT_CAMSERVER_old2 = ""
        + "SELECT "
        + "cam.ID AS CAM_ID, "                         
        + "cam.DIVICRO_PATH AS PATH, "             
        + "cam.CAMERA_PORT AS PORT "               
        + "FROM CAMERA.CAM cam";

Dann dann funktioniert aber mein Programm nicht mehr. Es fliegt eine Exception, weil es keine Spalte namens CAMERA_PORT gibt.
 

mihe7

Top Contributor
Die Tabelle CAMERA hat keine Spalte namens CAMERA_PORT. Trotzdem funktioniert mein Programm.
Wenn ich es richtig lese, dann hast Du auch keine Tabelle CAMERA, sondern ein Schema CAMERA mit einer Tabelle CAM.
Wie auch immer: wenn Du auf eine Spalte "zugreifst", die nicht existiert, dann sollte das zu einem Fehler führen. Anonsten wäre das schon ein "besonderes Feature" des DBMS, das ich mir schwer vorstellen kann. Auch möglich wären gewisse Datentypen.

Dann dann funktioniert aber mein Programm nicht mehr. Es fliegt eine Exception, weil es keine Spalte namens CAMERA_PORT gibt.
Das wäre denkbar, wenn man die Abfrage als Subquery verwendet, z. B. so
Java:
String query = String.format("SELECT CAMERA_PORT FROM (%s) x", SELECT_ALL_CAMS_WITHOUT_CAMSERVER_old2);
Im ersten Fall liefert die Subquery ein Spalte CAMERA_PORT, im zweiten Fall eben nicht mehr.

Ebenfalls denkbar wäre, dass die Exception fliegt, wenn man auf die Spaltennamen zugreift, z. B.
Java:
rs.getString("CAMERA_PORT);
Auch dann würde es mit der ersten Variante funktionieren und mit der zweiten nicht mehr.

Da wäre also zunächst die Frage, an welcher Stelle die Exception genau fliegt.
 

krgewb

Top Contributor
Ja. Die Tabelle heißt CAM.

Ich schaue nächste Woche in meinem Code nach, wie die Query aufgerufen wird und probiere nochmal herum, damit ich dir sagen kann, wo die Exception fliegt.
 

krgewb

Top Contributor
Ich habe nun versucht, es zu reproduzieren. Es wird auch beim ersten Beispiel eine Exception geworfen. Also habe ich mich am Freitag verguckt.
 

Ähnliche Java Themen

Neue Themen


Oben