Spring JPA sélection de colonnes spécifiques

146

J'utilise Spring JPA pour effectuer toutes les opérations de base de données. Cependant, je ne sais pas comment sélectionner des colonnes spécifiques à partir d'une table dans Spring JPA?

Par exemple:
SELECT projectId, projectName FROM projects

user1817436
la source
3
voir ce stackoverflow.com/questions/12618489/…
Abhishek Nayak
L'idée derrière JPA ne recherchant pas de champs spécifiques est qu'il est le même coût (en termes d'efficacité) d'amener une colonne ou toutes les colonnes d'une ligne de la table.
Désordre le
7
@Desorder - le coût n'est pas toujours le même. Ce n'est probablement pas un gros problème pour les types de données plus simples et primitifs, mais la raison pour laquelle je me suis retrouvé sur cette page est parce que j'ai remarqué qu'une simple requête "liste de documents" fonctionnait lentement. Cette entité a une colonne BLOB (en a besoin pour le téléchargement / stockage de fichiers) et je soupçonne qu'elle est lente car elle charge les BLOB en mémoire même s'ils ne sont pas nécessaires pour répertorier les documents.
jm0
@ jm0 Autant que vous vous en souveniez, combien de tables avaient des colonnes BLOB?
Desorder
1
@Desorder c'était juste une table mais je faisais une fonction "list" (multirow - lister tous les documents créés par un identifiant donné). La seule raison pour laquelle j'ai remarqué ce problème était que cette simple requête de liste prenait plusieurs secondes, alors que des requêtes plus complexes sur d'autres tables se produisaient presque instantanément. Une fois que j'ai réalisé, je savais que cela souffrirait de plus en plus à mesure que des lignes seraient ajoutées, car Spring JPA charge chaque BLOB en mémoire même s'ils ne sont pas utilisés. J'ai trouvé une solution décente pour les données Spring (publiée ci-dessous) mais je pense que j'en ai une encore meilleure qui est une pure annotation JPA, je publierai tmrw si cela fonctionne
jm0

Réponses:

75

Vous pouvez définir nativeQuery = truedans l' @Queryannotation à partir d'une Repositoryclasse comme celle-ci:

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

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

Notez que vous devrez cependant faire le mappage vous-même. Il est probablement plus facile d'utiliser simplement la recherche mappée régulière comme celle-ci, sauf si vous n'avez vraiment besoin que de ces deux valeurs:

public List<Project> findAll()

Cela vaut probablement la peine de consulter également les documents de données Spring .

Durandal
la source
5
pas besoin de requêtes natives. Vous devriez éviter de les utiliser, car ils ruinent les avantages de JPQL. voir la réponse d'Atals.
phil294
1
Pour moi, j'ai dû qualifier le premier attribut (ci-dessus FIND_PROJECTS) avec le valuenom de l' attribut (donc si c'était mon code, j'aurais dû l'écrire comme @Query(value = FIND_PROJECTS, nativeQuery = true), etc.
smeeb
173

Vous pouvez utiliser des projections de Spring Data JPA (doc) . Dans votre cas, créez une interface:

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

et ajoutez la méthode suivante à votre référentiel

List<ProjectIdAndName> findAll();
mpr
la source
11
C'est une solution propre. il peut avoir un modèle de chaudière mais l'interface peut être la classe interne de l'entité. Rendre tout à fait propre.
iceman
1
génial, n'oubliez pas de ne pas implémenter l'interface sur votre entité ou cela ne fonctionnera pas
alizelzele
1
où va l'interface projetée? dans son propre fichier ou peut-il être inclus dans l'interface publique qui renvoie les propriétés complètes de l'entité?
Micho Rizo
8
Cette solution ne fonctionne pas lors de l'extension de JpaRepository, quelqu'un connaît une solution de contournement?
Allemand
4
Vous ne pouvez pas utiliser findAll (); car il va entrer en conflit avec la méthode JPARepositorys. Vous devez utiliser quelque chose comme List <ProjectIdAndName> findAllBy ();
Code_Mode
137

Je n'aime pas particulièrement la syntaxe (elle a l'air un peu hacky ...) mais c'est la solution la plus élégante que j'ai pu trouver (elle utilise une requête JPQL personnalisée dans la classe de référentiel 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);

Alors bien sûr, il vous suffit de fournir un constructeur pour Documentqui accepte docId& en filenametant que constructeur args.

jm0
la source
9
(et btw j'ai vérifié, vous n'avez pas besoin de fournir le nom de classe complet si "Document" est importé - juste l'avoir de cette façon parce que c'est ainsi que cela a été fait dans le seul échantillon que j'ai pu trouver)
jm0
cela devrait être la réponse acceptée. Cela fonctionne parfaitement et ne sélectionne vraiment que les champs nécessaires.
Yonatan Wilkof
1
Les champs inutiles sont également inclus, mais avec la valeur «null», ces champs occuperaient-ils de la mémoire?
gabbler
oui mais si minime que dans la grande majorité des cas, il serait vraiment ridicule d'essayer de concevoir autour de cela - stackoverflow.com/questions/2430655/... vous devriez créer des objets légers spécialisés sans ces champs et les faire pointer vers la même chose table? quelle IMO est indésirable lors de l'utilisation des ORM et de leur exploitation pour leurs relations ... l'hyper-optimisation est peut-être plus dans le domaine de l'utilisation d'un DSL de requête léger et d'un mappage directement vers les DTO, et même dans ce cas, je pense que la redondance est découragée
jm0
2
jm0 cela n'a pas fonctionné pour moi sans nom de classe pleinement qualifié, bien qu'il ait été importé. Il a cependant compilé avec succès.
Heisenberg
20

Dans ma situation, je n'ai besoin que du résultat json, et cela fonctionne pour moi:

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

dans le contrôleur:

@Autowired
private SchoolRepository schoolRepository;

@ResponseBody
@RequestMapping("getschoolidandname.do")
public List<Object> getSchool() {
    List<Object> schools = schoolRepository.getSchoolIdAndName();
    return schools;
}
Atal
la source
2
vous devez remplacer Objectpar une interface personnalisée comme décrit par mpr. fonctionne parfaitement
phil294
14

Dans mon cas, j'ai créé une classe d'entité distincte sans les champs qui ne sont pas obligatoires (uniquement avec les champs obligatoires).

Mappez l'entité sur la même table. Maintenant, lorsque toutes les colonnes sont requises, j'utilise l'ancienne entité, lorsque seules certaines colonnes sont requises, j'utilise l'entité lite.

par exemple

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

Vous pouvez créer quelque chose comme:

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

Cela fonctionne lorsque vous connaissez les colonnes à récupérer (et cela ne changera pas).

ne fonctionnera pas si vous devez décider dynamiquement les colonnes.

Sachin Sharma
la source
Salut sachin, j'ai un doute si je vais créer l'entité comme vous le mentionnez ci-dessus. lorsque JPA s'exécutera et il essaiera de créer une table avec le nom de l'utilisateur. quelle entité utilisera.
user3364549
3
ne créez jamais de table avec JPA, créez vos tables manuellement dans la base de données, utilisez JPA pour mapper le monde relationnel au monde des objets.
Sachin Sharma
Pourquoi ne pouvez-vous pas utiliser l'héritage ici?
deadbug
8

Je suppose que le moyen le plus simple est d'utiliser QueryDSL , fourni avec Spring-Data.

En utilisant votre question, la réponse peut être

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

Le gestionnaire d'entités peut être câblé automatiquement et vous travaillerez toujours avec des objets et des classes sans utiliser le langage * QL.

Comme vous pouvez le voir dans le lien, le dernier choix me semble, presque pour moi, plus élégant, c'est-à-dire utiliser DTO pour stocker le résultat. Appliquez à votre exemple qui sera:

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

Définition de ProjectDTO comme:

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
la source
5

Avec les nouvelles versions de Spring, on peut faire ce qui suit:

Si vous n'utilisez pas de requête native, cela peut être fait comme ci-dessous:

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

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

En utilisant la requête native, la même chose peut être faite comme ci-dessous:

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

Pour plus de détails, consultez la documentation

jombie
la source
4

À mon avis, c'est une excellente solution:

interface PersonRepository extends Repository<Person, UUID> {

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

et l'utiliser comme ça

void someMethod(PersonRepository people) {

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

  Collection<NamesOnly> aggregates =
    people.findByLastname("Matthews", NamesOnly.class);
}
Evgeni Atanasov
la source
Pourquoi ne pas renvoyer List <T> au lieu de collection?!
Abdullah Khan
@AbdullahKhan car le résultat peut ne pas toujours avoir d'ordre.
Ravi Sanwal
4

En utilisant Spring Data JPA, il est possible de sélectionner des colonnes spécifiques dans la base de données

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

Cela a fonctionné à 100% dans mon cas. Merci.

SR Ranjan
la source
2

Vous pouvez utiliser JPQL:

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

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

ou vous pouvez utiliser une requête SQL native.

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

Il est possible de spécifier nullcomme valeur de champ dans SQL natif.

@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
la source
2

Vous pouvez appliquer le code ci-dessous dans votre classe d'interface de référentiel.

entityname signifie le nom de votre table de base de données, comme les projets. Et List signifie que Project est la classe Entity dans vos projets.

@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
la source
0

Utilisation de la requête native:

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

Vous pouvez utiliser la réponse suggérée par @jombie, et:

  • placez l'interface dans un fichier séparé, en dehors de la classe d'entité;
  • utiliser une requête native ou non (le choix dépendait de vos besoins);
  • ne remplacez pas la findAll()méthode à cet effet mais utilisez le nom de votre choix;
  • pensez à renvoyer un Listparamétré avec votre nouvelle interface (par exemple List<SmallProject>).
foxbit
la source