Spring Data JPA mapuje natywny wynik zapytania na Non-Entity POJO

92

Mam metodę repozytorium Spring Data z natywnym zapytaniem

@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);

i chciałbym zmapować wynik do Non-Entity POJO GroupDetails.

Czy jest to możliwe, a jeśli tak, czy mógłbyś podać przykład?

aleksanoid
źródło

Odpowiedzi:

65

Zakładając, że GroupDetails jest zgodne z odpowiedzią orid, czy wypróbowałeś JPA 2.1 @ConstructorResult ?

@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")

i użyj następujących w interfejsie repozytorium:

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

Według danych Wiosna WZP dokumentacji , wiosna najpierw spróbować znaleźć nazwie zapytanie pasujących nazwę metody - tak za pomocą @NamedNativeQuery, @SqlResultSetMappingi @ConstructorResultpowinieneś być w stanie osiągnąć to zachowanie

Daimon
źródło
15
Aby dane sprężyny mogły być zgodne z NamedNativeQuery, nazwa klasy jednostki domeny, po której następuje kropka, musi być poprzedzona nazwą NamedNativeQuery. Więc nazwa powinna brzmieć (zakładając, że podmiotem domeny jest Group) „Group.getGroupDetails”.
Grant Lay
@GrantLay, czy możesz rzucić okiem na to pytanie: stackoverflow.com/q/44871757/7491770 Mam dokładnie taki problem.
baran
Jak zwrócę listę takich obiektów?
Nikhil Sahu
1
Aby to zadziałało, należy GroupDetailsoznaczyć @Entity? Jeśli to możliwe, czy możesz powiedzieć, do której klasy @NamedNativeQuerynależy zastosować adnotację ?
Manu
3
@SqlResultSetMappingi @NamedNativeQueryadnotacje muszą być obecne na encji używanej w Twoim repozytorium Spring Data (np. public interface CustomRepository extends CrudRepository<CustomEntity, Long>to jest CustomEntityklasa)
Tomasz W
112

Myślę, że najłatwiej to zrobić używając tzw. Projekcji. Może mapować wyniki zapytań do interfejsów. Używanie SqlResultSetMappingjest niewygodne i sprawia, że ​​kod jest brzydki :).

Przykład bezpośrednio z kodu źródłowego JPA danych sprężyny:

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();

  }
}

Możesz również użyć tej metody, aby uzyskać listę prognoz.

Sprawdź ten wiosenny wpis w dokumentach JPA, aby uzyskać więcej informacji na temat prognoz.

Notatka 1:

Pamiętaj, aby mieć swoją Userencję zdefiniowaną normalnie - pola z projektowanego interfejsu muszą być zgodne z polami w tej encji. W przeciwnym razie mapowanie pól może zostać uszkodzone ( getFirstname()może zwrócić wartość nazwiska i tak dalej).

Uwaga 2:

Jeśli używasz SELECT table.column ...notacji, zawsze definiuj aliasy pasujące do nazw z encji. Na przykład ten kod nie będzie działał poprawnie (projekcja zwróci wartości null dla każdego gettera):

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

Ale to działa dobrze:

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

W przypadku bardziej złożonych zapytań wolałbym zamiast tego używać JdbcTemplateniestandardowego repozytorium.

Michał Stochmal
źródło
To czystsze rozwiązanie. Sprawdziłem, ale wydajność jest znacznie gorsza niż przy użyciu SqlResultSetMapping (jest wolniejsza o 30-40% :()
kidnan1991
działa ładnie! upublicznij interfejs, jeśli chcesz go używać gdzie indziej
tibi
Nie działa, jeśli chcesz wyodrębnić pole typu XML (clob). Jakieś sugestie?
Ashish,
@Ashish Wolę zamiast tego użyć JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ). Możesz użyć getClobmetody na resultSet, aby pobrać clob InputStream. Na przykład: rs.getClob("xml_column").getCharacterStream().
Michał Stochmal
Co się stanie, jeśli w zapytaniu użyję SELECT *, a zapytanie jest natywne?
Salman Kazmi,
17

Myślę, że podejście Michała jest lepsze. Ale jest jeszcze jeden sposób, aby uzyskać wynik z natywnego zapytania.

@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);

Teraz możesz przekonwertować tę tablicę ciągów 2D na żądaną jednostkę.

Ashish
źródło
2
prosty i elegancki
Jan
9

Możesz napisać zapytanie natywne lub inne niż natywne tak, jak chcesz, a wyniki zapytania JPQL można opakować instancjami niestandardowych klas wyników. Utwórz DTO z tymi samymi nazwami kolumn zwróconych w zapytaniu i utwórz konstruktor zawierający wszystkie argumenty z taką samą sekwencją i nazwami, jakie zostały zwrócone przez zapytanie. Następnie użyj następującego sposobu, aby wysłać zapytanie do bazy danych.

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

Utwórz DTO:

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
źródło
poprawka: te same nazwy nie są obowiązkowe ... tylko ta sama sekwencja parametrów w konstruktorze i zwrócony zestaw wyników.
Waqas Memon
Działa to tylko wtedy, gdy Country jest klasą jednostki Java. Tak się nie stanie, jeśli Country nie jest klasą jednostki Java.
Yeshwant KAKAD
1
Mówisz, że powinno to działać również z zapytaniami natywnymi? Czy mógłbyś podać przykład?
Richard Tingle
OP pyta o natywne zapytanie, ale podany przykład jest inny niż natywny
CLS
0

Możesz zrobić coś takiego

@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)")

I musi być coś takiego jak Konstruktor

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
źródło
13
Pytanie dotyczy zapytań natywnych, a nie zapytań napisanych w HQL.
DBK,
-5

Na moim komputerze ten kod działa, trochę różni się od odpowiedzi Daimona.

@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
źródło