Spring JPA selezionando colonne specifiche


146

Sto usando Spring JPA per eseguire tutte le operazioni del database. Tuttavia, non so come selezionare colonne specifiche da una tabella in Spring JPA?

Per esempio:
SELECT projectId, projectName FROM projects



L'idea alla base di JPA che non cerca campi specifici è che il costo (in termini di efficienza) è lo stesso per portare una colonna o tutte le colonne da una riga della tabella.
Disordine

7
@Desorder - il costo non è sempre lo stesso. Probabilmente non è un grosso problema per tipi di dati più semplici e primitivi, ma il motivo per cui sono finito in questa pagina è perché ho notato che una semplice query "elenco documenti" stava funzionando lentamente. Quell'entità ha una colonna BLOB (ne ha bisogno per il caricamento / archiviazione dei file) e sospetto che sia lenta perché sta caricando i BLOB in memoria anche se non sono necessari per elencare i documenti.
jm0

@ jm0 Per quanto ricordi, quante tabelle avevano colonne BLOB?
Disordine

1
@Desorder era solo una tabella ma stavo facendo una funzione "list" (multirow - elenca tutti i documenti creati da un determinato ID). L'unico motivo per cui ho notato questo problema era perché questa semplice query dell'elenco impiegava diversi secondi, mentre le query più complesse su altre tabelle stavano accadendo quasi all'istante. Una volta realizzato, sapevo che avrebbe sofferto sempre di più man mano che venivano aggiunte righe perché Spring JPA sta caricando tutti i BLOB in memoria anche se non vengono utilizzati. Ho trovato una soluzione decente per i dati di Spring (pubblicati di seguito) ma penso di averne una ancora migliore che sia pura annotazione JPA, posterò tmrw se funziona
jm0

Risposte:


75

Puoi impostare nativeQuery = truel' @Queryannotazione da una Repositoryclasse come questa:

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

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

Nota che dovrai fare tu stesso la mappatura. Probabilmente è più semplice utilizzare la normale ricerca mappata in questo modo, a meno che non siano necessari solo questi due valori:

public List<Project> findAll()

Probabilmente vale la pena guardare anche i documenti di dati di Spring .


5
non sono necessarie query native. Dovresti evitare di usarli, perché rovinano i vantaggi di JPQL. vedi risposta Atals.
phil294

1
Per me ho dovuto qualificare il primo attributo (sopra FIND_PROJECTS) con il valuenome dell'attributo (quindi se questo fosse il mio codice avrei dovuto scriverlo come @Query(value = FIND_PROJECTS, nativeQuery = true), ecc.
smeeb

173

È possibile utilizzare le proiezioni dal JPA (doc) di Spring Data . Nel tuo caso, crea un'interfaccia:

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

e aggiungi il seguente metodo al tuo repository

List<ProjectIdAndName> findAll();

11
Questa è una soluzione pulita. può avere un modello di caldaia ma l'interfaccia può essere la classe interna dell'entità. Rendendolo abbastanza pulito.
iceman,

1
fantastico, ricordati solo di non implementare l'interfaccia sulla tua Entità o non funzionerà
alizelzele,

1
dove va l'interfaccia proiettata? nel proprio file o può essere incluso nell'interfaccia pubblica che restituisce le proprietà complete dell'entità?
Micho Rizo,

8
Questa soluzione non funziona durante l'estensione di JpaRepository, qualcuno conosce una soluzione alternativa?
Tedesco,

4
Non puoi usare findAll (); poiché si scontrerà con il metodo JPARepositorys. Devi usare qualcosa come List <ProjectIdAndName> findAllBy ();
Code_Mode

137

In particolare la sintassi non mi piace (sembra un po 'confusa ...) ma questa è la soluzione più elegante che sono riuscito a trovare (utilizza una query JPQL personalizzata nella classe di repository 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);

Quindi, naturalmente, devi solo fornire un costruttore Documentche accetti docIde filenamecome sostiene il costruttore.


9
(e tra l'altro ho verificato, non è necessario fornire il nome di classe completo se viene importato "Documento" - l'ho fatto in questo modo perché è stato fatto così nell'unico campione che sono riuscito a trovare)
jm0

questa dovrebbe essere la risposta accettata. Funziona perfettamente e seleziona davvero solo i campi necessari.
Yonatan Wilkof,

1
Sono inclusi anche i campi non necessari, ma con il valore 'null', quei campi occuperebbero memoria?
gabbler

sì, ma così minimale che nella stragrande maggioranza dei casi sarebbe davvero ridicolo provare a progettarlo - stackoverflow.com/questions/2430655/… dovresti creare oggetti leggeri specializzati senza questi campi e farli puntare allo stesso tavolo? quale IMO non è desiderato quando si usano gli ORM e li si fa leva per le loro relazioni ... l'iper-ottimizzazione è forse più nel regno dell'utilizzo di alcune query DSL leggere e del mapping direttamente ai DTO, e anche allora penso che la ridondanza sia scoraggiata
jm0

2
jm0 non ha funzionato per me senza un nome classe completo, sebbene sia stato importato. Si è compilato con successo però.
Heisenberg,

20

Nella mia situazione, ho solo bisogno del risultato json e questo funziona per me:

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

nel controller:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;
}

2
dovresti sostituire Objectcon un'interfaccia personalizzata come descritto da mpr. funziona perfettamente
phil294,

14

Nel mio caso ho creato una classe di entità separata senza i campi che non sono richiesti (solo con i campi richiesti).

Mappa l'entità sulla stessa tabella. Ora, quando sono necessarie tutte le colonne, utilizzo la vecchia entità, quando sono necessarie solo alcune colonne, utilizzo l'entità lite.

per esempio

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

Puoi creare qualcosa come:

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

Funziona quando conosci le colonne da recuperare (e questo non cambierà).

non funzionerà se devi decidere dinamicamente le colonne.


Ciao sachin, ho un dubbio se creerò l'entità come hai detto sopra. quando verrà eseguito JPA e proverà a creare una tabella con il nome dell'utente. quale entità utilizzerà.
user3364549

3
mai creare una tabella con JPA, creare manualmente le tabelle nel db, utilizzare JPA per mappare il mondo relazionale al mondo degli oggetti.
Sachin Sharma,

Perché non puoi usare l'eredità qui?
deadbug

8

Immagino che il modo più semplice sia usare QueryDSL , fornito con Spring-Data.

Usando la tua domanda la risposta può essere

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

Il gestore entità può essere Autowired e lavorerai sempre con oggetti e clase senza usare il linguaggio * QL.

Come puoi vedere nel link, l'ultima scelta sembra, quasi per me, più elegante, cioè usare DTO per memorizzare il risultato. Applica al tuo esempio che sarà:

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

Definire ProjectDTO come:

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(){....}
}

5

Con le versioni Spring più recenti Uno può fare come segue:

Se non si utilizza la query nativa, è possibile procedere come di seguito:

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

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

Utilizzando la query nativa si può fare lo stesso come di seguito:

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

Per i dettagli, consultare i documenti


4

Secondo me questa è un'ottima soluzione:

interface PersonRepository extends Repository<Person, UUID> {

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

e usandolo così

void someMethod(PersonRepository people) {

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

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

Perché non restituire Elenco <T> anziché raccolta ?!
Abdullah Khan,

@AbdullahKhan perché il risultato potrebbe non avere sempre un ordine.
Ravi Sanwal,

4

Utilizzando Spring Data JPA è disponibile una disposizione per selezionare colonne specifiche dal database

---- In DAOImpl ----

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

---- In Repo ----

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

Nel mio caso ha funzionato al 100%. Grazie.


2

Puoi usare JPQL:

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

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

oppure puoi usare la query sql nativa.

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

2

È possibile specificare nullcome valore di campo in sql nativo.

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

2

È possibile applicare il codice seguente nella classe dell'interfaccia del repository.

nome entità indica il nome della tabella del database come i progetti. E Lista indica che Progetto è la classe Entità nei tuoi Progetti.

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

0

Utilizzo della query nativa:

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

0

Puoi usare la risposta suggerita da @jombie e:

  • posizionare l'interfaccia in un file separato, al di fuori della classe di entità;
  • usa la query nativa o meno (la scelta dipendeva dalle tue esigenze);
  • non sovrascrivere il findAll()metodo per questo scopo ma usare il nome di tua scelta;
  • ricordati di restituire un Listparametro con la tua nuova interfaccia (ad es List<SmallProject>.).
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.