Spring JPA selecionando colunas específicas

146

Estou usando o Spring JPA para executar todas as operações do banco de dados. No entanto, não sei como selecionar colunas específicas de uma tabela no Spring JPA?

Por exemplo:
SELECT projectId, projectName FROM projects

user1817436
fonte
3
veja este stackoverflow.com/questions/12618489/…
Abhishek Nayak
A idéia por trás da JPA de não procurar campos específicos é que o custo (eficiência) é o mesmo para trazer uma coluna ou todas as colunas de uma linha da tabela.
Desordem 15/09/14
7
@ Desordem - o custo nem sempre é o mesmo. Provavelmente não é grande coisa para tipos de dados mais simples e primitivos, mas o motivo pelo qual acabei nesta página é porque notei que uma consulta simples de "lista de documentos" estava ficando lenta. Essa entidade possui uma coluna BLOB (precisa dela para upload / armazenamento de arquivo) e eu suspeito que seja lento porque está carregando os BLOBs na memória, mesmo que não sejam necessários para listar os documentos.
jm0
@ jm0 Até onde você se lembra, quantas tabelas tinham colunas BLOB?
Desordem 14/03/2015
1
@ Desorder era apenas uma tabela, mas eu estava executando uma função "list" (multirow - listar todos os documentos criados por um determinado ID). O único motivo pelo qual notei esse problema foi porque essa consulta simples da lista estava demorando alguns segundos, enquanto consultas mais complexas em outras tabelas estavam acontecendo quase instantaneamente. Depois que percebi, sabia que sofreria cada vez mais, à medida que as linhas são adicionadas porque o Spring JPA está carregando cada BLOB na memória, mesmo que não seja usado. Eu encontrei uma solução decente para dados Primavera (publicado abaixo), mas eu acho que tenho um ainda melhor que é puro anotação JPA, vou postar tmrw se ele funciona
jm0

Respostas:

75

Você pode definir nativeQuery = truea @Queryanotação de uma Repositoryclasse como esta:

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

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

Observe que você terá que fazer o mapeamento sozinho. Provavelmente é mais fácil usar a pesquisa mapeada regular como esta, a menos que você realmente precise apenas desses dois valores:

public List<Project> findAll()

Provavelmente vale a pena examinar também os documentos de dados do Spring .

Durandal
fonte
5
não há necessidade de consultas nativas. Você deve evitar usá-los, pois eles arruinam as vantagens do JPQL. veja Atals responder.
precisa saber é
1
Para mim, eu tinha para qualificar o primeiro atributo (acima FIND_PROJECTS) com o valuenome do atributo (portanto, se este foi o meu código que eu teria que escrevê-lo como @Query(value = FIND_PROJECTS, nativeQuery = true), etc.
smeeb
172

Você pode usar projeções do Spring Data JPA (doc) . No seu caso, crie uma interface:

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

e adicione o seguinte método ao seu repositório

List<ProjectIdAndName> findAll();
mpr
fonte
11
Esta é uma solução limpa. pode ter um modelo de caldeira, mas a interface pode ser a classe interna da entidade. Tornando bastante limpo.
iceman
1
incrível, basta lembrar para não implementar a interface em seu Entidade ou não vai funcionar
alizelzele
1
para onde vai a interface projetada? em seu próprio arquivo ou pode ser incluído na interface pública que retorna todas as propriedades da entidade?
Micho Rizo
8
Esta solução não funciona ao estender o JpaRepository, alguém conhece uma solução alternativa?
alemão
4
Você não pode usar findAll (); pois entrará em conflito com o método JPARepositorys. Você precisa usar algo como List <ProjectIdAndName> findAllBy ();
Code_Mode 22/07/19
137

Não gosto particularmente da sintaxe (parece um pouco hacky ...), mas esta é a solução mais elegante que consegui encontrar (ela usa uma consulta JPQL personalizada na classe de repositório 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);

Então, é claro, você apenas precisa fornecer um construtor para Documentque aceite docId& filenamecomo construtor args.

jm0
fonte
9
(e pelo que verifiquei, você não precisa fornecer o nome completo da classe se "Documento" for importado - apenas o fiz dessa maneira, porque foi assim que foi feito no único exemplo que pude encontrar)
jm0
essa deve ser a resposta aceita. Funciona perfeitamente e realmente seleciona apenas os campos necessários.
Yonatan Wilkof
1
Os campos desnecessários também estão incluídos, mas com o valor 'nulo', esses campos ocupariam memória?
tagarela
sim, mas tão mínimo que, na grande maioria dos casos, seria realmente ridículo tentar resolver isso - stackoverflow.com/questions/2430655/… você teria que criar objetos leves especializados sem esses campos e apontá-los para o mesmo tabela? que IMO é indesejável quando se utiliza ORMs e aproveitando-los para seus relacionamentos ... hiper-otimização é talvez mais no reino de usar apenas alguns DSL consulta leve e mapeamento diretamente para DTOs, e mesmo assim eu acho que a redundância é desencorajado
jm0
2
jm0 não funcionou para mim sem o nome completo da classe, apesar de ter sido importado. Ele compilou com sucesso embora.
Heisenberg
20

Na minha situação, eu só preciso do resultado json, e isso funciona para mim:

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

no controlador:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;
}
Atal
fonte
2
você deve substituir Objectpor uma interface personalizada, conforme descrito por mpr. funciona perfeitamente
phil294
14

No meu caso, criei uma classe de entidade separada sem os campos que não são obrigatórios (apenas com os campos necessários).

Mapeie a entidade para a mesma tabela. Agora, quando todas as colunas são necessárias, eu uso a entidade antiga, quando apenas algumas colunas são necessárias, eu uso a entidade lite.

por exemplo

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

Você pode criar algo como:

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

Isso funciona quando você sabe que as colunas devem ser buscadas (e isso não vai mudar).

não funcionará se você precisar decidir dinamicamente as colunas.

Sachin Sharma
fonte
Oi sachin, tenho uma dúvida se vou criar a entidade como você mencionou acima. quando o JPA será executado e tentará criar uma tabela com o nome do usuário. qual entidade usará.
User3364549
3
nunca crie uma tabela com o JPA, crie suas tabelas manualmente no db, use o JPA para mapear o mundo relacional para o mundo do objeto.
Sachin Sharma #
Por que você não pode usar a herança aqui?
deadbug 19/07/19
8

Eu acho que a maneira mais fácil pode ser usar QueryDSL , que vem com o Spring-Data.

Usando a sua pergunta, a resposta pode 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)); 
}}

O gerente da entidade pode ser conectado automaticamente e você sempre trabalhará com objetos e classes sem usar a linguagem * QL.

Como você pode ver no link, a última opção parece, quase para mim, mais elegante, ou seja, usar o DTO para armazenar o resultado. Aplique ao seu exemplo que será:

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

Definindo o 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
fonte
5

Com as versões mais recentes do Spring, é possível fazer o seguinte:

Se não estiver usando a consulta nativa, isso pode ser feito da seguinte maneira:

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

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

Usando a consulta nativa, o mesmo pode ser feito como abaixo:

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

Para detalhes, verifique os documentos

jombie
fonte
4

Na minha opinião, esta é uma ótima solução:

interface PersonRepository extends Repository<Person, UUID> {

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

e usá-lo assim

void someMethod(PersonRepository people) {

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

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
Evgeni Atanasov
fonte
Por que não retornar Lista <T> em vez de coleção ?!
Abdullah Khan
@AbdullahKhan porque o resultado pode nem sempre ter um pedido.
Ravi Sanwal 08/04/19
4

Usando o Spring Data JPA, existe uma disposição para selecionar colunas específicas do banco de dados

---- No DAOImpl ----

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

---- No Repo ----

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

Funcionou 100% no meu caso. Obrigado.

SR Ranjan
fonte
2

Você pode usar o JPQL:

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

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

ou você pode usar a consulta sql nativa.

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

É possível especificar nullcomo valor do campo no 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
fonte
2

Você pode aplicar o código abaixo na sua classe de interface de repositório.

entityname significa o nome da tabela do banco de dados como projetos. E Lista significa que Projeto é a classe Entidade em seus Projetos.

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

Usando consulta nativa:

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

Você pode usar a resposta sugerida por @jombie e:

  • coloque a interface em um arquivo separado, fora da classe da entidade;
  • use consulta nativa ou não (a escolha depende de suas necessidades);
  • não substitua o findAll()método para esse fim, mas use o nome de sua escolha;
  • lembre-se de retornar um Listparametrizado com sua nova interface (por exemplo List<SmallProject>).
foxbit
fonte