Spring JPA seleccionando columnas específicas

146

Estoy usando Spring JPA para realizar todas las operaciones de la base de datos. Sin embargo, no sé cómo seleccionar columnas específicas de una tabla en Spring JPA.

Por ejemplo:
SELECT projectId, projectName FROM projects

usuario1817436
fuente
3
vea este stackoverflow.com/questions/12618489/…
Abhishek Nayak
La idea detrás de que JPA no busque campos específicos es que el costo (en términos de eficiencia) es el mismo para traer una columna o todas las columnas de una fila de la tabla.
Desorden
77
@Desorder: el costo no siempre es el mismo. Probablemente no sea gran cosa para tipos de datos más simples y primitivos, pero la razón por la que terminé en esta página es porque noté que una simple consulta de "lista de documentos" se estaba ejecutando lentamente. Esa entidad tiene una columna BLOB (la necesita para cargar / almacenar archivos) y sospecho que es lenta porque está cargando los BLOB en la memoria a pesar de que no son necesarios para enumerar los documentos.
jm0
@ jm0 Por lo que recuerdas, ¿cuántas tablas tenían columnas BLOB?
Desorden
1
@Desorder era solo una tabla, pero estaba haciendo una función de "lista" (multirreferencia: enumera todos los documentos creados por una identificación determinada). La única razón por la que noté este problema fue porque esta simple consulta de lista tardaba varios segundos, mientras que las consultas más complejas en otras tablas ocurrían casi instantáneamente. Una vez que me di cuenta, sabía que sufriría cada vez más a medida que se agregan filas porque Spring JPA está cargando cada BLOB en la memoria, incluso aunque no se usen. Encontré una solución decente para los datos de Spring (publicados a continuación) pero creo que tengo una aún mejor que es pura anotación JPA, publicaré tmrw si funciona
jm0

Respuestas:

75

Puede establecer nativeQuery = truela @Queryanotación de una Repositoryclase como esta:

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

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

Tenga en cuenta que tendrá que hacer el mapeo usted mismo. Probablemente sea más fácil usar la búsqueda asignada regular como esta, a menos que realmente solo necesite esos dos valores:

public List<Project> findAll()

Probablemente también valga la pena mirar los documentos de datos de Spring .

Durandal
fuente
55
No hay necesidad de consultas nativas. Debe evitar usarlos, ya que arruinan las ventajas de JPQL. ver la respuesta de Atals.
phil294
1
Para mí que tenía que calificar el primer atributo (arriba FIND_PROJECTS) con el valuenombre del atributo (por lo tanto si se trataba de mi código que habría tenido que escribir como @Query(value = FIND_PROJECTS, nativeQuery = true), etc
smeeb
173

Puede usar proyecciones de Spring Data JPA (doc) . En su caso, cree la interfaz:

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

y agregue el siguiente método a su repositorio

List<ProjectIdAndName> findAll();
mpr
fuente
11
Esta es una solución limpia. Puede tener una plantilla de caldera, pero la interfaz puede ser la clase interna de la entidad. Haciéndolo bastante limpio.
Iceman
1
impresionante, solo recuerda no implementar la interfaz en tu entidad o no funcionará
alizelzele
1
¿A dónde va la interfaz proyectada? en su propio archivo o puede incluirse en la interfaz pública que devuelve las propiedades completas de la entidad?
Micho Rizo
8
Esta solución no funciona al extender JpaRepository, ¿alguien sabe una solución alternativa?
Alemán
44
No puede usar findAll (); ya que chocará con el método JPARepositorys. Necesita usar algo como List <ProjectIdAndName> findAllBy ();
Code_Mode
137

No me gusta la sintaxis en particular (parece un poco hacky ...) pero esta es la solución más elegante que pude encontrar (utiliza una consulta JPQL personalizada en la clase de repositorio 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);

Entonces, por supuesto, solo tiene que proporcionar un constructor Documentque acepte docId& filenamecomo argumentos de constructor.

jm0
fuente
9
(y, por cierto, lo verifiqué, no es necesario que proporcione el nombre de clase completo si se importa "Documento" - solo lo tuve así porque así fue como se hizo en la única muestra que pude encontrar)
jm0
Esta debería ser la respuesta aceptada. Funciona perfectamente y realmente selecciona solo los campos necesarios.
Yonatan Wilkof
1
También se incluyen los campos innecesarios, pero con el valor 'nulo', ¿ocuparían esos campos memoria?
charlatán el
sí, pero tan mínimo que en la gran mayoría de los casos sería realmente ridículo tratar de diseñar esto: stackoverflow.com/questions/2430655 /... tendrías que hacer objetos ligeros especializados sin estos campos y hacer que apunten a lo mismo ¿mesa? qué IMO no es deseable cuando se usan ORM y se aprovechan de ellos para sus relaciones ... la hiperoptimización es quizás más en el ámbito de solo usar algunas DSL de consulta livianas y mapear directamente a DTO, e incluso entonces creo que se desaconseja la redundancia
jm0
2
jm0 no funcionó para mí sin un nombre de clase completamente calificado, aunque fue importado. Sin embargo, se compiló con éxito.
Heisenberg
20

En mi situación, solo necesito el resultado json, y esto funciona para mí:

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

en el controlador:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;
}
Atal
fuente
2
debe sustituirlo Objectpor una interfaz personalizada como la describe mpr. funciona a la perfección
phil294
14

En mi caso, creé una clase de entidad separada sin los campos que no son obligatorios (solo con los campos que son obligatorios).

Asigne la entidad a la misma tabla. Ahora, cuando se requieren todas las columnas, uso la entidad anterior, cuando solo se requieren algunas columnas, uso la entidad lite.

p.ej

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

Puedes crear algo como:

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

Esto funciona cuando conoce las columnas para buscar (y esto no va a cambiar).

no funcionará si necesita decidir dinámicamente las columnas.

Sachin Sharma
fuente
Hola sachin, tengo una duda si crearé la entidad como mencionas anteriormente. cuándo se ejecutará JPA e intentará crear una tabla con el nombre del usuario. qué entidad usará.
user3364549
3
nunca cree una tabla con JPA, cree sus tablas manualmente en la base de datos, use JPA para asignar el mundo relacional al mundo de objetos.
Sachin Sharma
¿Por qué no puedes hacer uso de la herencia aquí?
deadbug
8

Supongo que la manera más fácil es usar QueryDSL , que viene con Spring-Data.

Usando su pregunta, la respuesta puede ser

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

El administrador de la entidad puede ser Autowired y siempre trabajará con objetos y clases sin usar * lenguaje QL.

Como puede ver en el enlace, la última opción parece, casi para mí, más elegante, es decir, usar DTO para almacenar el resultado. Aplique a su ejemplo que será:

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

Definiendo ProjectDTO como:

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
fuente
5

Con las versiones Spring más recientes, se puede hacer lo siguiente:

Si no utiliza la consulta nativa, puede hacerlo de la siguiente manera:

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

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

Usando la consulta nativa, lo mismo se puede hacer de la siguiente manera:

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

Para más detalles, consulte los documentos

jombie
fuente
4

En mi opinión, esta es una gran solución:

interface PersonRepository extends Repository<Person, UUID> {

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

y usándolo así

void someMethod(PersonRepository people) {

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

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
Evgeni Atanasov
fuente
¿Por qué no devolver List <T> en lugar de colección?
Abdullah Khan
@AbdullahKhan porque el resultado puede no tener siempre un pedido.
Ravi Sanwal
4

Usando Spring Data JPA hay una disposición para seleccionar columnas específicas de la base de datos

---- En DAOImpl ----

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

---- En Repo ----

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

Funcionó al 100% en mi caso. Gracias.

SR Ranjan
fuente
2

Puedes usar JPQL:

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

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

o puede usar la consulta SQL nativa.

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

Es posible especificar nullcomo valor de campo en 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);
hahn
fuente
2

Puede aplicar el siguiente código en su clase de interfaz de repositorio.

nombre de entidad significa el nombre de la tabla de la base de datos como proyectos. Y Lista significa que Proyecto es la clase Entidad en sus Proyectos.

@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
fuente
0

Usando la consulta nativa:

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

Puede usar la respuesta sugerida por @jombie y:

  • coloque la interfaz en un archivo separado, fuera de la clase de entidad;
  • use consultas nativas o no (la elección dependía de sus necesidades);
  • no anule el findAll()método para este propósito, pero use el nombre de su elección;
  • recuerde devolver un Listparametrizado con su nueva interfaz (por ejemplo List<SmallProject>).
Foxbit
fuente