Wiosenne JPA wybierając określone kolumny


146

Używam Spring JPA do wykonywania wszystkich operacji na bazie danych. Jednak nie wiem, jak wybrać określone kolumny z tabeli w Spring JPA?

Na przykład:
SELECT projectId, projectName FROM projects



Idea stojąca za tym, że JPA nie szuka określonych pól, polega na tym, że koszt (pod względem wydajności) jest taki sam, aby sprowadzić jedną kolumnę lub wszystkie kolumny z jednego wiersza tabeli.
Desorder

7
@Desorder - koszt nie zawsze jest taki sam. Prawdopodobnie nie jest to wielka sprawa dla prostszych, prymitywnych typów danych, ale powodem, dla którego znalazłem się na tej stronie, jest to, że zauważyłem, że proste zapytanie „lista dokumentów” działa wolno. Ta jednostka ma kolumnę BLOB (potrzebuje jej do przesyłania / przechowywania plików) i podejrzewam, że jest powolna, ponieważ ładuje BLOB-y do pamięci, mimo że nie są one wymagane do wyświetlania dokumentów.
jm0

@ jm0 O ile pamiętasz, ile tabel miało kolumny typu BLOB?
Desorder

1
@Desorder to była tylko jedna tabela, ale wykonywałem funkcję "list" (multirow - wyświetla wszystkie dokumenty utworzone przez podany identyfikator). Jedynym powodem, dla którego zauważyłem ten problem, było to, że to proste zapytanie o listę trwało kilka sekund, podczas gdy bardziej złożone zapytania w innych tabelach zachodziły niemal natychmiast. Kiedy zdałem sobie sprawę, wiedziałem, że będzie to coraz bardziej cierpieć w miarę dodawania wierszy, ponieważ Spring JPA ładuje każdy BLOB do pamięci, nawet jeśli nie są używane. Znalazłem przyzwoite rozwiązanie dla danych wiosennych (zamieszczone poniżej), ale myślę, że mam jeszcze lepsze, które jest czystą adnotacją JPA, napiszę tmrw, jeśli
zadziała

Odpowiedzi:


75

Możesz ustawić nativeQuery = truew @Queryadnotacji z Repositoryzajęć w ten sposób:

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

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

Pamiętaj jednak, że mapowanie będziesz musiał wykonać samodzielnie. Prawdopodobnie łatwiej jest po prostu użyć zwykłego odwzorowania, chyba że naprawdę potrzebujesz tylko tych dwóch wartości:

public List<Project> findAll()

Prawdopodobnie warto przyjrzeć się również dokumentacji Spring Data .


5
nie ma potrzeby natywnych zapytań. Należy ich unikać, ponieważ niszczą one zalety JPQL. zobacz odpowiedź Atals.
phil294

1
Dla mnie musiałem zakwalifikować pierwszy atrybut (powyżej FIND_PROJECTS) z valuenazwą atrybutu (stąd gdyby to był mój kod, musiałbym go zapisać jako @Query(value = FIND_PROJECTS, nativeQuery = true)itp.
smeeb

172

Możesz użyć prognoz z Spring Data JPA (doc) . W twoim przypadku stwórz interfejs:

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

i dodaj następującą metodę do swojego repozytorium

List<ProjectIdAndName> findAll();

11
To jest czyste rozwiązanie. może mieć szablon kotła, ale interfejs może być klasą wewnętrzną jednostki. Robiąc to całkiem czystym.
iceman

1
super, po prostu pamiętaj, aby nie wdrażać interfejsu w swojej
Encji,

1
dokąd zmierza projektowany interfejs? we własnym pliku, czy może być uwzględniony w interfejsie publicznym, który zwraca pełne właściwości jednostki?
Micho Rizo

8
To rozwiązanie nie działa podczas rozszerzania JpaRepository, czy ktoś zna obejście?
niemiecki

4
Nie możesz użyć funkcji findAll (); ponieważ będzie kolidować z metodą JPARepositorys. Musisz użyć czegoś takiego jak List <ProjectIdAndName> findAllBy ();
Code_Mode

137

Nie podoba mi się szczególnie składnia (wygląda trochę hakersko ...), ale jest to najbardziej eleganckie rozwiązanie, jakie udało mi się znaleźć (używa niestandardowego zapytania JPQL w klasie repozytorium 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);

Wtedy oczywiście wystarczy dostarczyć konstruktor, Documentktóry akceptuje docId& filenamejako argumenty konstruktora.


9
(i przy okazji zweryfikowałem, że nie musisz podawać w pełni kwalifikowanej nazwy klasy, jeśli "Dokument" jest importowany - po prostu miałem to w ten sposób, ponieważ tak to zostało zrobione w jedynej próbce, jaką udało mi się znaleźć)
jm0

powinna to być akceptowana odpowiedź. Działa doskonale i naprawdę wybiera tylko niezbędne pola.
Yonatan Wilkof

1
Niepotrzebne pola również są uwzględnione, ale czy przy wartości „null” te pola zajmują pamięć?
gabbler

tak, ale tak minimalny, że w zdecydowanej większości przypadków byłoby naprawdę absurdalne próbowanie tego rozwiązania - stackoverflow.com/questions/2430655/ ... musiałbyś tworzyć specjalistyczne lekkie obiekty bez tych pól i wskazywać je na to samo stół? który IMO jest niepożądany podczas korzystania z ORMów i wykorzystywania ich do ich relacji ... hiperoptymalizacja jest może bardziej związana z używaniem tylko lekkich zapytań DSL i mapowania bezpośrednio na DTO, a nawet wtedy uważam, że nadmiarowość jest odradzana
jm0

2
jm0 nie działało dla mnie bez w pełni kwalifikowanej nazwy klasy, chociaż została zaimportowana. Skompilował się jednak pomyślnie.
Heisenberg

20

W mojej sytuacji potrzebuję tylko wyniku json, a to działa dla mnie:

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

w kontrolerze:

@Autowired
private SchoolRepository schoolRepository;

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

2
należy zastąpić Objectniestandardowym interfejsem opisanym przez mpr. działa bez zarzutu
phil294,

14

W moim przypadku utworzyłem oddzielną klasę encji bez pól, które nie są wymagane (tylko z polami, które są wymagane).

Zamapuj jednostkę na tę samą tabelę. Teraz, gdy wszystkie kolumny są wymagane, używam starej jednostki, gdy wymagane są tylko niektóre kolumny, używam jednostki lite.

na przykład

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

Możesz stworzyć coś takiego:

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

Działa to, gdy znasz kolumny do pobrania (i to się nie zmieni).

nie zadziała, jeśli musisz dynamicznie decydować o kolumnach.


Cześć Sachin, mam jedną wątpliwość, czy stworzę byt taki, jak wspomniałeś powyżej. kiedy JPA uruchomi się i spróbuje utworzyć tabelę z nazwą użytkownika. która jednostka będzie używać.
user3364549

3
nigdy nie twórz tabel za pomocą JPA, twórz tabele ręcznie w bazie danych, używaj JPA do mapowania świata relacyjnego na świat obiektów.
Sachin Sharma

Dlaczego nie możesz tutaj skorzystać z dziedziczenia?
deadbug

8

Wydaje mi się, że najłatwiejszym sposobem jest użycie QueryDSL , które jest dostarczane z Spring-Data.

Odpowiedź może brzmieć na Twoje pytanie

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

Menedżer encji może być Autowiredem i zawsze będziesz pracować z obiektami i klasami bez używania języka * QL.

Jak widać w linku ostatni wybór wydaje się, prawie dla mnie, bardziej elegancki, to znaczy użycie DTO do przechowywania wyniku. Zastosuj do swojego przykładu, który będzie:

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

Definiowanie ProjectDTO jako:

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

W nowszych wersjach Spring można wykonać następujące czynności:

Jeśli nie używasz zapytania natywnego, możesz to zrobić w następujący sposób:

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

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

Używając natywnego zapytania, można zrobić to samo, co poniżej:

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

Szczegółowe informacje można znaleźć w dokumentacji


4

Moim zdaniem to świetne rozwiązanie:

interface PersonRepository extends Repository<Person, UUID> {

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

i używać go w ten sposób

void someMethod(PersonRepository people) {

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

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

Dlaczego nie zwrócić List <T> zamiast kolekcji ?!
Abdullah Khan

@AbdullahKhan, ponieważ wynik może nie zawsze mieć porządek.
Ravi Sanwal

4

Przy użyciu Spring Data JPA istnieje możliwość wybrania określonych kolumn z bazy danych

---- W DAOImpl ----

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

---- W repozytorium ----

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

W moim przypadku zadziałało w 100%. Dzięki.


2

Możesz użyć JPQL:

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

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

lub możesz użyć natywnego zapytania sql.

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

2

Możliwe jest określenie nulljako wartości pola w natywnym sql.

@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

Możesz zastosować poniższy kod w klasie interfejsu repozytorium.

nazwa encji oznacza nazwę tabeli bazy danych, taką jak projekty. A Lista oznacza, że ​​projekt jest klasą Entity w Twoich projektach.

@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

Korzystanie z natywnego zapytania:

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

0

Możesz skorzystać z odpowiedzi sugerowanej przez @jombie i:

  • umieść interfejs w osobnym pliku, poza klasą encji;
  • użyj natywnego zapytania lub nie (wybór zależał od twoich potrzeb);
  • nie zastępuj findAll() metody w tym celu, ale użyj wybranej przez siebie nazwy;
  • pamiętaj, aby zwrócić Listparametr sparametryzowany z nowym interfejsem (np List<SmallProject>.).
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.