Spring JPA memilih kolom tertentu

156

Saya menggunakan Spring JPA untuk melakukan semua operasi database. Namun saya tidak tahu cara memilih kolom tertentu dari tabel di Spring JPA?

Sebagai contoh:
SELECT projectId, projectName FROM projects

pengguna1817436
sumber
Ide di balik JPA tidak mencari bidang tertentu adalah bahwa biaya (efisiensi bijaksana) sama untuk membawa satu kolom atau semua kolom dari satu baris tabel.
Desorder
7
@Desorder - biayanya tidak selalu sama. Ini mungkin bukan masalah besar untuk jenis data yang sederhana dan primitif, tetapi alasan saya berakhir di halaman ini adalah karena saya melihat kueri "daftar dokumen" sederhana berjalan lambat. Entitas tersebut memiliki kolom BLOB (membutuhkannya untuk mengunggah / penyimpanan file) & saya menduga ini lambat karena memuat BLOB ke dalam memori meskipun mereka tidak diperlukan untuk membuat daftar dokumen.
jm0
@ jm0 Seingat Anda, berapa banyak tabel yang memiliki kolom BLOB?
Desorder
1
@Desorder itu hanya satu tabel tetapi saya melakukan fungsi "daftar" (multirow - daftar semua dokumen yang dibuat oleh id yang diberikan). Satu-satunya alasan saya melihat masalah ini adalah karena kueri daftar sederhana ini memakan waktu beberapa detik, sedangkan kueri yang lebih kompleks di tabel lain terjadi hampir seketika. Setelah saya menyadarinya, saya tahu itu akan semakin menderita karena baris ditambahkan karena Spring JPA memuat setiap BLOB ke dalam memori meskipun mereka tidak digunakan. Saya menemukan solusi yang layak untuk data Musim Semi (diposting di bawah) tetapi saya pikir saya memiliki yang lebih baik yaitu anotasi JPA murni, saya akan memposting tmrw jika berfungsi
jm0

Jawaban:

79

Anda dapat mengatur nativeQuery = truedalam @Querypenjelasan dari Repositorykelas seperti ini:

public static final String FIND_PROJECTS = "SELECT projectId, projectName FROM projects";

@Query(value = FIND_PROJECTS, nativeQuery = true)
public List<Object[]> findProjects();

Perhatikan bahwa Anda harus melakukan pemetaan sendiri. Mungkin lebih mudah untuk menggunakan pencarian yang dipetakan biasa seperti ini kecuali Anda benar-benar hanya membutuhkan dua nilai tersebut:

public List<Project> findAll()

Mungkin ada baiknya juga melihat dokumen data Spring .

Durandal
sumber
7
tidak perlu kueri asli. Anda harus menghindari penggunaannya, karena mereka merusak keuntungan JPQL. lihat jawaban Atals.
phil294
1
Bagi saya, saya harus memenuhi syarat atribut pertama (di atas FIND_PROJECTS) dengan valuenama atribut (maka jika ini adalah kode saya, saya harus menuliskannya sebagai @Query(value = FIND_PROJECTS, nativeQuery = true), dll.
smeeb
190

Anda dapat menggunakan proyeksi dari Spring Data JPA (doc) . Dalam kasus Anda, buat antarmuka:

interface ProjectIdAndName{
    String getId();
    String getName();
}

dan tambahkan metode berikut ke repositori Anda

List<ProjectIdAndName> findAll();
mpr
sumber
13
Ini adalah solusi yang bersih. itu mungkin memiliki template boiler tetapi antarmukanya dapat menjadi kelas dalam entitas. Menjadikannya cukup bersih.
Iceman
1
luar biasa, ingatlah untuk tidak mengimplementasikan antarmuka pada Entitas Anda atau itu tidak akan berhasil
alizelzele
1
kemana perginya antarmuka yang diproyeksikan? di filenya sendiri atau dapatkah itu dimasukkan dalam antarmuka publik yang mengembalikan properti entitas penuh?
Micho Rizo
9
Solusi ini tidak berfungsi saat memperluas JpaRepository, ada yang tahu solusinya?
Bahasa Jerman
4
Anda tidak dapat menggunakan findAll (); karena akan bentrok dengan metode JPARepositorys. Anda perlu menggunakan sesuatu seperti List <ProjectIdAndName> findAllBy ();
Vikas Yadav
141

Saya tidak suka sintaks secara khusus (ini terlihat sedikit hacky ...) tetapi ini adalah solusi paling elegan yang dapat saya temukan (ini menggunakan kueri JPQL khusus di kelas repositori JPA):

@Query("select new com.foo.bar.entity.Document(d.docId, d.filename) from Document d where d.filterCol = ?1")
List<Document> findDocumentsForListing(String filterValue);

Kemudian tentu saja, Anda hanya perlu menyediakan konstruktor untuk Documentyang accept docId& filenameas constructor args.

jm0
sumber
9
(dan btw saya verifikasi, Anda tidak perlu memberikan nama kelas yang sepenuhnya memenuhi syarat jika "Dokumen" diimpor - cukup lakukan seperti itu karena begitulah cara melakukannya dalam satu-satunya sampel yang dapat saya temukan)
jm0
ini harus menjadi jawaban yang diterima. Ini bekerja dengan sempurna dan benar-benar hanya memilih bidang yang diperlukan.
Yonatan Wilkof
1
Bidang yang tidak perlu juga disertakan, tetapi dengan nilai 'null', apakah bidang tersebut akan menempati memori?
obrolan
ya, tetapi sangat minim sehingga dalam sebagian besar kasus akan sangat konyol untuk mencoba merekayasa hal ini - stackoverflow.com/questions/2430655/… Anda harus membuat objek ringan khusus tanpa bidang ini & membuatnya mengarah ke hal yang sama meja? IMO mana yang tidak diinginkan saat menggunakan ORM dan memanfaatkannya untuk hubungan mereka ... hyper-optimization mungkin lebih di ranah hanya menggunakan beberapa DSL permintaan ringan dan pemetaan langsung ke DTO, & bahkan kemudian saya pikir redundansi tidak disarankan
jm0
2
jm0 itu tidak bekerja untuk saya tanpa nama kelas yang memenuhi syarat, meskipun itu diimpor. Itu berhasil dikompilasi meskipun.
Heisenberg
21

Dalam situasi saya, saya hanya membutuhkan hasil json, dan ini berfungsi untuk saya:

public interface SchoolRepository extends JpaRepository<School,Integer> {
    @Query("select s.id, s.name from School s")
    List<Object> getSchoolIdAndName();
}

di Controller:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;
}
Atal
sumber
2
Anda harus mengganti Objectdengan antarmuka khusus seperti yang dijelaskan oleh mpr. bekerja dengan sempurna
phil294
14

Dalam kasus saya, saya membuat kelas entitas terpisah tanpa bidang yang tidak diperlukan (hanya dengan bidang yang diperlukan).

Petakan entitas ke tabel yang sama. Sekarang ketika semua kolom diperlukan saya menggunakan entitas lama, ketika hanya beberapa kolom yang diperlukan, saya menggunakan entitas lite.

misalnya

@Entity
@Table(name = "user")
Class User{
         @Column(name = "id", unique=true, nullable=false)
         int id;
         @Column(name = "name", nullable=false)
         String name;
         @Column(name = "address", nullable=false)
         Address address;
}

Anda dapat membuat sesuatu seperti:

@Entity
@Table(name = "user")
Class UserLite{
         @Column(name = "id", unique=true, nullable=false)
         int id;
         @Column(name = "name", nullable=false)
         String name;
}

Ini berfungsi ketika Anda mengetahui kolom yang akan diambil (dan ini tidak akan berubah).

tidak akan berfungsi jika Anda perlu menentukan kolom secara dinamis.

Sachin Sharma
sumber
Hai sachin, saya ragu apakah saya akan membuat entitas seperti yang Anda sebutkan di atas. ketika JPA akan dijalankan dan akan mencoba membuat tabel dengan nama pengguna. entitas mana yang akan digunakan.
pengguna3364549
4
jangan pernah membuat tabel dengan JPA, buat tabel Anda secara manual di db, gunakan JPA untuk memetakan dunia relasional ke dunia objek.
Sachin Sharma
Mengapa Anda tidak dapat menggunakan warisan di sini?
deadbug
Anda akan mewarisi kolom yang coba Anda kecualikan. @deadbug
Steve
8

Saya rasa cara termudah adalah menggunakan QueryDSL , yang disertakan dengan Spring-Data.

Menggunakan pertanyaan Anda, jawabannya bisa

JPAQuery query = new JPAQuery(entityManager);
List<Tuple> result = query.from(projects).list(project.projectId, project.projectName);
for (Tuple row : result) {
 System.out.println("project ID " + row.get(project.projectId));
 System.out.println("project Name " + row.get(project.projectName)); 
}}

Manajer entitas dapat menjadi Autowired dan Anda akan selalu bekerja dengan objek dan klas tanpa menggunakan bahasa * QL.

Seperti yang Anda lihat di tautan, pilihan terakhir tampaknya, bagi saya, lebih elegan, yaitu menggunakan DTO untuk menyimpan hasilnya. Terapkan ke contoh Anda yang akan menjadi:

JPAQuery query = new JPAQuery(entityManager);
QProject project = QProject.project;
List<ProjectDTO> dtos = query.from(project).list(new QProjectDTO(project.projectId, project.projectName));

Mendefinisikan ProjectDTO sebagai:

class ProjectDTO {

 private long id;
 private String name;
 @QueryProjection
 public ProjectDTO(long projectId, String projectName){
   this.id = projectId;
   this.name = projectName;
 }
 public String getProjectId(){ ... }
 public String getProjectName(){....}
}
kszosze.dll
sumber
6

Dengan versi Spring yang lebih baru, Seseorang dapat melakukan hal berikut:

Jika tidak menggunakan kueri asli, ini dapat dilakukan seperti di bawah ini:

public interface ProjectMini {
    String getProjectId();
    String getProjectName();
}

public interface ProjectRepository extends JpaRepository<Project, String> { 
    @Query("SELECT p FROM Project p")
    List<ProjectMini> findAllProjectsMini();
}

Menggunakan kueri asli, hal yang sama dapat dilakukan seperti di bawah ini:

public interface ProjectRepository extends JpaRepository<Project, String> { 
    @Query(value = "SELECT projectId, projectName FROM project", nativeQuery = true)
    List<ProjectMini> findAllProjectsMini();
}

Untuk detail, periksa dokumennya

zombie
sumber
5

Menggunakan Spring Data JPA ada ketentuan untuk memilih kolom tertentu dari database

---- Dalam DAOImpl ----

@Override
    @Transactional
    public List<Employee> getAllEmployee() throws Exception {
    LOGGER.info("Inside getAllEmployee");
    List<Employee> empList = empRepo.getNameAndCityOnly();
    return empList;
    }

---- Dalam Repo ----

public interface EmployeeRepository extends CrudRepository<Employee,Integer> {
    @Query("select e.name, e.city from Employee e" )
    List<Employee> getNameAndCityOnly();
}

Ini berhasil 100% dalam kasus saya. Terima kasih.

SR Ranjan
sumber
4

Menurut saya ini adalah solusi yang bagus:

interface PersonRepository extends Repository<Person, UUID> {

    <T> Collection<T> findByLastname(String lastname, Class<T> type);
}

dan menggunakannya seperti itu

void someMethod(PersonRepository people) {

  Collection<Person> aggregates =
    people.findByLastname("Matthews", Person.class);

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
Evgeni Atanasov
sumber
Mengapa tidak mengembalikan List <T> alih-alih collection ?!
Abdullah Khan
@AbdullahKhan karena hasilnya tidak selalu ada order.
Ravi Sanwal
3

Anda dapat menerapkan kode di bawah ini di kelas antarmuka repositori Anda.

nama entitas berarti nama tabel database Anda seperti proyek. Dan Daftar berarti Proyek adalah kelas Entitas di Proyek Anda.

@Query(value="select p from #{#entityName} p where p.id=:projectId and p.projectName=:projectName")

List<Project> findAll(@Param("projectId") int projectId, @Param("projectName") String projectName);
ajaz
sumber
2

Anda dapat menggunakan JPQL:

TypedQuery <Object[]> query = em.createQuery(
  "SELECT p.projectId, p.projectName FROM projects AS p", Object[].class);

List<Object[]> results = query.getResultList();

atau Anda dapat menggunakan kueri sql asli.

Query query = em.createNativeQuery("sql statement");
List<Object[]> results = query.getResultList();
Henrik
sumber
2

Dimungkinkan untuk menentukan nullsebagai nilai bidang dalam sql asli.

@Query(value = "select p.id, p.uid, p.title, null as documentation, p.ptype " +
            " from projects p " +
            "where p.uid = (:uid)" +
            "  and p.ptype = 'P'", nativeQuery = true)
Project findInfoByUid(@Param("uid") String uid);
hahn
sumber
0

Menggunakan Native Query:

Query query = entityManager.createNativeQuery("SELECT projectId, projectName FROM projects");
List result = query.getResultList();
ukchaudhary.dll
sumber
0

Anda dapat menggunakan jawaban yang disarankan oleh @jombie, dan:

  • tempatkan antarmuka dalam file terpisah, di luar kelas entitas;
  • menggunakan kueri asli atau tidak (pilihan tergantung pada kebutuhan Anda);
  • jangan mengganti findAll()metode untuk tujuan ini tetapi gunakan nama pilihan Anda;
  • ingatlah untuk mengembalikan Listparameter dengan antarmuka baru Anda (misalnya List<SmallProject>).
foxbit
sumber