Spring Data JPA ordnet das native Abfrageergebnis POJO ohne Entität zu

87

Ich habe eine Spring Data-Repository-Methode mit einer nativen Abfrage

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
    GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

und ich möchte das Ergebnis dem POJO von Nicht-Entitäten zuordnen GroupDetails.

Ist es möglich und wenn ja, können Sie bitte ein Beispiel nennen?

alexanoid
quelle

Antworten:

64

Angenommen, GroupDetails wie in der Antwort von orid, haben Sie JPA 2.1 @ConstructorResult ausprobiert ?

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID"),
                @ColumnResult(name="USER_ID")
            }
        )
    }
)

@NamedNativeQuery(name="getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

und verwenden Sie Folgendes in der Repository-Schnittstelle:

GroupDetails getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

Gemäß der Spring Data JPA- Dokumentation versucht spring zunächst, eine benannte Abfrage zu finden, die Ihrem Methodennamen entspricht. Verwenden Sie also @NamedNativeQuery, @SqlResultSetMappingund verwenden @ConstructorResultSie dieses Verhalten

Daimon
quelle
15
Damit Spring-Daten mit der NamedNativeQuery übereinstimmen können, muss dem Namen der NamedNativeQuery der Klassenname der Domänenentität gefolgt von einem Punkt vorangestellt werden. Der Name sollte also (vorausgesetzt, die Domänenentität ist Gruppe) 'Group.getGroupDetails' sein.
Grant Lay
@GrantLay können Sie sich diese Frage ansehen : stackoverflow.com/q/44871757/7491770 Ich habe genau diese Art von Problem.
Widder
Wie werde ich eine Liste solcher Objekte zurückgeben?
Nikhil Sahu
1
Um es zum Laufen zu bringen, sollte mit GroupDetailsmarkiert werden @Entity? Wenn möglich, können Sie bitte angeben, auf welche Klasse die Anmerkung @NamedNativeQueryangewendet werden muss?
Manu
3
@SqlResultSetMappingund @NamedNativeQueryAnmerkungen müssen auf der Entität vorhanden sein, die in Ihrem Spring Data-Repository verwendet wird (z. B. für public interface CustomRepository extends CrudRepository<CustomEntity, Long>die CustomEntityKlasse)
Tomasz W
103

Ich denke, der einfachste Weg, dies zu tun, ist die Verwendung der sogenannten Projektion. Es kann Abfrageergebnisse Schnittstellen zuordnen. Die Verwendung SqlResultSetMappingist unpraktisch und macht Ihren Code hässlich :).

Ein Beispiel direkt aus dem JPA-Quellcode von Spring Data:

public interface UserRepository extends JpaRepository<User, Integer> {

   @Query(value = "SELECT firstname, lastname FROM SD_User WHERE id = ?1", nativeQuery = true)
   NameOnly findByNativeQuery(Integer id);

   public static interface NameOnly {

     String getFirstname();

     String getLastname();

  }
}

Mit dieser Methode können Sie auch eine Liste der Projektionen abrufen.

Weitere Informationen zu Projektionen finden Sie in den JPA-Dokumenten dieses Frühlings.

Anmerkung 1:

Denken Sie daran, dass Ihre UserEntität als normal definiert ist. Die Felder der projizierten Schnittstelle müssen mit den Feldern in dieser Entität übereinstimmen. Andernfalls kann die Feldzuordnung unterbrochen werden ( getFirstname()möglicherweise wird der Wert des Nachnamens usw. zurückgegeben).

Anmerkung 2:

Wenn Sie die SELECT table.column ...Notation verwenden, definieren Sie immer Aliase, die mit den Namen der Entität übereinstimmen. Zum Beispiel funktioniert dieser Code nicht richtig (die Projektion gibt für jeden Getter Nullen zurück):

@Query(value = "SELECT user.firstname, user.lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

Aber das funktioniert gut:

@Query(value = "SELECT user.firstname AS firstname, user.lastname AS lastname FROM SD_User user WHERE id = ?1", nativeQuery = true)
NameOnly findByNativeQuery(Integer id);

Bei komplexeren Abfragen würde ich stattdessen lieber ein JdbcTemplatebenutzerdefiniertes Repository verwenden.

Michał Stochmal
quelle
Es ist eine sauberere Lösung. Ich hatte überprüft, aber die Leistung ist viel schlechter als mit SqlResultSetMapping (es ist langsamer etwa 30-40% :()
Kidn1991
funktioniert gut! Machen Sie die Schnittstelle öffentlich, wenn Sie sie an anderer Stelle verwenden möchten
Tibi
Funktioniert nicht, wenn Sie ein XML-Typfeld (Clob) extrahieren möchten. Irgendein Vorschlag?
Ashish
@Ashish Ich würde stattdessen lieber JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) verwenden. Sie können die getClobMethode on resultSetverwenden, um Clob abzurufen InputStream. Zum Beispiel : rs.getClob("xml_column").getCharacterStream().
Michał Stochmal
Was ist, wenn ich SELECT * in der Abfrage verwende und die Abfrage eine native ist?
Salman Kazmi
13

Ich denke, Michals Ansatz ist besser. Es gibt jedoch noch eine weitere Möglichkeit, das Ergebnis aus der nativen Abfrage herauszuholen.

@Query(value = "SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", nativeQuery = true)
String[][] getGroupDetails(@Param("userId") Integer userId, @Param("groupId") Integer groupId);

Jetzt können Sie dieses 2D-String-Array in Ihre gewünschte Entität konvertieren.

Ashish
quelle
einfach und elegant
John
9

Sie können Ihre native oder nicht native Abfrage wie gewünscht schreiben und JPQL-Abfrageergebnisse mit Instanzen benutzerdefinierter Ergebnisklassen umschließen. Erstellen Sie ein DTO mit denselben Namen der in der Abfrage zurückgegebenen Spalten und erstellen Sie einen Konstruktor für alle Argumente mit derselben Sequenz und denselben Namen wie von der Abfrage zurückgegeben. Verwenden Sie dann die folgende Methode, um die Datenbank abzufragen.

@Query("SELECT NEW example.CountryAndCapital(c.name, c.capital.name) FROM Country AS c")

DTO erstellen:

package example;

public class CountryAndCapital {
    public String countryName;
    public String capitalName;

    public CountryAndCapital(String countryName, String capitalName) {
        this.countryName = countryName;
        this.capitalName = capitalName;
    }
}
Waqas Memon
quelle
Korrektur: Gleiche Namen sind nicht obligatorisch ... nur dieselbe Folge von Parametern im Konstruktor und in der zurückgegebenen Ergebnismenge.
Waqas Memon
Dies funktioniert nur, wenn Country Ihre Java-Entitätsklasse ist. Dies ist nicht der Fall, wenn Country nicht Ihre Java-Entitätsklasse ist.
Yeshwant KAKAD
Sie sagen, dies sollte auch mit nativen Abfragen funktionieren? Können Sie ein Beispiel dafür geben?
Richard Tingle
1

Sie können so etwas tun

@NamedQuery(name="IssueDescriptor.findByIssueDescriptorId" ,

    query=" select new com.test.live.dto.IssuesDto (idc.id, dep.department, iss.issueName, 
               cat.issueCategory, idc.issueDescriptor, idc.description) 
            from Department dep 
            inner join dep.issues iss 
            inner join iss.category cat 
            inner join cat.issueDescriptor idc 
            where idc.id in(?1)")

Und es muss Konstruktoren geben

public IssuesDto(long id, String department, String issueName, String issueCategory, String issueDescriptor,
            String description) {
        super();
        this.id = id;
        this.department = department;
        this.issueName = issueName;
        this.issueCategory = issueCategory;
        this.issueDescriptor = issueDescriptor;
        this.description = description;
    }
Chandan Gawri
quelle
11
Die Frage bezieht sich auf native Abfragen, nicht auf in HQL geschriebene Abfragen.
DBK
-4

Auf meinem Computer funktioniert dieser Code. Er unterscheidet sich ein wenig von Daimons Antwort.

@SqlResultSetMapping(
    name="groupDetailsMapping",
    classes={
        @ConstructorResult(
            targetClass=GroupDetails.class,
            columns={
                @ColumnResult(name="GROUP_ID",type=Integer.class),
                @ColumnResult(name="USER_ID",type=Integer.class)
            }
        )
    }
)

@NamedNativeQuery(name="User.getGroupDetails", query="SELECT g.*, gm.* FROM group g LEFT JOIN group_members gm ON g.group_id = gm.group_id and gm.user_id = :userId WHERE g.group_id = :groupId", resultSetMapping="groupDetailsMapping")

Jiangke
quelle