Spring Data JPA memetakan hasil kueri asli ke POJO Non-Entitas

92

Saya memiliki metode penyimpanan Data Musim Semi dengan kueri asli

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

dan saya ingin memetakan hasilnya ke POJO Non-Entitas GroupDetails.

Apakah mungkin dan jika demikian, dapatkah Anda memberikan contoh?

alexanoid
sumber

Jawaban:

65

Dengan asumsi GroupDetails sebagai jawaban orid, apakah Anda sudah mencoba 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")

dan gunakan berikut ini di antarmuka repositori:

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

Menurut dokumentasi JPA Spring Data , spring pertama-tama akan mencoba menemukan kueri bernama yang cocok dengan nama metode Anda - jadi dengan menggunakan @NamedNativeQuery, @SqlResultSetMappingdan @ConstructorResultAnda harus dapat mencapai perilaku itu

Daimon
sumber
15
Agar data musim semi dapat mencocokkan NamedNativeQuery, nama Kelas entitas domain yang diikuti dengan titik, harus diawali dengan nama NamedNativeQuery. Jadi namanya harus (Dengan asumsi entitas domain adalah Grup) 'Group.getGroupDetails'.
Grant Lay
@GrantLay dapatkah Anda melihat pertanyaan ini: stackoverflow.com/q/44871757/7491770 Saya memiliki masalah seperti ini.
ram
Bagaimana cara mengembalikan daftar Objek tersebut?
Nikhil Sahu
1
Agar berhasil, harus GroupDetailsditandai dengan @Entity? Jika memungkinkan dapatkah Anda memberi tahu pada kelas mana anotasi @NamedNativeQueryharus diterapkan?
Manu
3
@SqlResultSetMappingdan @NamedNativeQueryanotasi harus ada pada entitas yang digunakan dalam penyimpanan Spring Data Anda (misalnya untuk public interface CustomRepository extends CrudRepository<CustomEntity, Long>itu adalah CustomEntitykelas)
Tomasz W
112

Saya pikir cara termudah untuk melakukannya adalah dengan menggunakan apa yang disebut proyeksi. Itu dapat memetakan hasil kueri ke antarmuka. Menggunakan SqlResultSetMappingtidak nyaman dan membuat kode Anda jelek :).

Contoh langsung dari kode sumber JPA data musim semi:

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

  }
}

Anda juga dapat menggunakan metode ini untuk mendapatkan daftar proyeksi.

Lihat entri dokumen JPA data musim semi ini untuk info selengkapnya tentang proyeksi.

Catatan 1:

Ingatlah untuk Usermenetapkan entitas Anda seperti biasa - bidang dari antarmuka yang diproyeksikan harus cocok dengan bidang di entitas ini. Jika pemetaan lapangan mungkin rusak ( getFirstname()mungkin mengembalikan nilai nama belakang dan sebagainya).

Catatan 2:

Jika Anda menggunakan SELECT table.column ...notasi, selalu tentukan alias yang cocok dengan nama dari entitas. Misalnya kode ini tidak akan berfungsi dengan baik (proyeksi akan mengembalikan null untuk setiap pengambil):

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

Tapi ini berfungsi dengan baik:

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

Dalam kasus kueri yang lebih kompleks, saya lebih suka menggunakan JdbcTemplaterepositori kustom sebagai gantinya.

Michał Stochmal
sumber
Ini adalah solusi yang lebih bersih. Saya telah memeriksa tetapi kinerjanya jauh lebih buruk daripada menggunakan SqlResultSetMapping (lebih lambat sekitar 30-40% :()
kidnan1991
bekerja dengan baik! jadikan antarmuka publik jika Anda ingin menggunakannya di tempat lain
tibi
Tidak berfungsi jika Anda ingin mengekstrak kolom tipe XML (clob). Ada saran?
Ashish
@Ashish Saya lebih suka menggunakan JdbcTemplate( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) sebagai gantinya. Anda dapat menggunakan getClobmetode on resultSetuntuk mengambil clob InputStream. Untuk contoh: rs.getClob("xml_column").getCharacterStream().
Michał Stochmal
Bagaimana jika saya menggunakan SELECT * dalam kueri dan kueri tersebut asli?
Salman Kazmi
17

Saya pikir pendekatan Michal lebih baik. Namun, ada satu cara lagi untuk mendapatkan hasil dari kueri asli.

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

Sekarang, Anda dapat mengubah larik string 2D ini menjadi entitas yang Anda inginkan.

Ashish
sumber
2
sederhana dan elegan
john
9

Anda dapat menulis kueri native atau non-native seperti yang Anda inginkan, dan Anda dapat menggabungkan hasil kueri JPQL dengan instance kelas hasil kustom. Buat DTO dengan nama kolom yang sama yang dikembalikan dalam kueri dan buat semua konstruktor argumen dengan urutan dan nama yang sama seperti yang dikembalikan oleh kueri. Kemudian gunakan cara berikut untuk membuat kueri database.

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

Buat 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
sumber
koreksi: nama yang sama tidak wajib ... hanya urutan parameter yang sama dalam konstruktor dan set hasil yang dikembalikan.
Waqas Memon
Ini hanya berfungsi jika Negara adalah kelas entitas java Anda. Ini tidak akan terjadi jika Negara bukan kelas entitas java Anda.
Yeshwant KAKAD
1
Anda mengatakan ini juga harus bekerja dengan kueri asli? Bisakah Anda memberi contoh tentang itu?
Richard Tingle
OP meminta kueri native, tetapi contoh yang diberikan adalah non-native
CLS
0

Anda bisa melakukan sesuatu seperti

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

Dan harus ada Constructor like

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
sumber
13
Pertanyaannya adalah tentang kueri asli, bukan kueri yang ditulis dalam HQL.
DBK
-5

Di komputer saya, saya mendapatkan kode ini berfungsi. Ini sedikit berbeda dari jawaban Daimon.

@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
sumber