Весна JPA, вибираючи конкретні стовпці


146

Я використовую Spring JPA для виконання всіх операцій з базою даних. Однак я не знаю, як вибрати конкретні стовпці з таблиці Spring JPA?

Наприклад:
SELECT projectId, projectName FROM projects



Ідея, за якою JPA не шукає конкретних полів, полягає в тому, що вартість (однаково ефективна) однакова, щоб принести один стовпець або всі стовпці з одного рядка таблиці.
Десард

7
@ Порушення - вартість не завжди однакова. Можливо, це не велика справа для більш простого, примітивного типу типів даних, але причина, по якій я опинився на цій сторінці, полягає в тому, що я помітив простий запит "список документів", який працює повільно. У цього об'єкта стовпчик BLOB (потрібен він для завантаження / зберігання файлів), і я підозрюю, що це повільно, оскільки він завантажує BLOB в пам'ять, хоча вони не потрібні для переліку документів.
jm0

@ jm0 Наскільки ви пам'ятаєте, скільки таблиць мали стовпці BLOB?
Дезард

1
@ Порядок це була лише одна таблиця, але я виконував функцію "list" (multirow - перелічував усі документи, створені заданим ідентифікатором). Єдиною причиною я помітив цю проблему через те, що цей простий список запитів займав кілька секунд, тоді як складніші запити в інших таблицях відбуваються майже миттєво. Як тільки я зрозумів, я знав, що це буде страждати все більше і більше, оскільки додаються рядки, оскільки Spring JPA завантажує кожен BLOB в пам'ять, навіть якщо вони не використовуються. Я знайшов гідне рішення для даних Spring (розміщено нижче), але, думаю, у мене є ще кращий, який є чистою анотацією JPA, я опублікую tmrw, якщо це працює
jm0

Відповіді:


75

Ви можете встановити nativeQuery = trueв @Queryпримітці такий Repositoryклас:

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

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

Зверніть увагу, що вам доведеться зробити картування самостійно. Мабуть, простіше просто використовувати звичайний відображений на карті такий, як цей, якщо вам справді не потрібні лише ці два значення:

public List<Project> findAll()

Напевно, варто також переглянути документи Spring .


5
немає потреби в рідних запитах. Вам слід уникати їх використання, оскільки вони руйнують переваги JPQL. див. відповідь Аталів.
phil294

1
Для мене я повинен був кваліфікувати перший атрибут (вище FIND_PROJECTS) з valueім'ям атрибута (отже, якби це був мій код, я повинен був би записати його як @Query(value = FIND_PROJECTS, nativeQuery = true)і т.д.
smeeb

173

Ви можете використовувати проекції з Spring Data JPA (doc) . У вашому випадку створіть інтерфейс:

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

і додати наступний метод у ваше сховище

List<ProjectIdAndName> findAll();

11
Це чисте рішення. він може мати шаблон котла, але інтерфейс може бути внутрішнім класом сутності. Зробити це досить чисто.
iceman

1
приголомшливо, просто пам’ятайте, що не впроваджувати інтерфейс у вашій Entity, інакше це не спрацює
alizelzele

1
куди іде проектований інтерфейс? у власному файлі чи його можна включити в загальнодоступний інтерфейс, який повертає всі властивості сутності?
Мічо Різо

8
Це рішення не працює при розширенні JpaRepository, хтось знає вирішення?
Німецька

4
Ви не можете використовувати findAll (); оскільки це зіткнеться з методом JPARepositorys. Вам потрібно використовувати щось на зразок List <ProjectIdAndName> findAllBy ();
Code_Mode

137

Синтаксис мені особливо не подобається (це виглядає трохи прискіпливо ...), але це найелегантніше рішення, яке мені вдалося знайти (він використовує користувацький запит JPQL у класі репозиторію 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);

Тоді, звичайно, ви просто повинні надати конструктор , Documentякий приймає docIdі в filenameякості конструктора арг.


9
(і я переконався, що вам не потрібно вказувати повністю кваліфіковане ім'я класу, якщо "Document" імпортується - просто так було, тому що це було зроблено в єдиному зразку, який я зміг знайти)
jm0

це має бути прийнятою відповіддю. Це прекрасно працює і дійсно вибирає лише необхідні поля.
Йонатан Вілкоф

1
Непотрібні поля також включаються, але зі значенням 'null', чи будуть ці поля займати пам'ять?
габлер

так, але настільки мінімально, що в переважній більшості випадків було б дуже смішно намагатися розробити це - stackoverflow.com/questions/2430655/… вам доведеться робити спеціалізовані легкі об'єкти без цих полів і мати на них вказівку на те саме стіл? який IMO небажаний під час використання ORM та використання їх для їх відносин ... гіпер-оптимізація, можливо, більше в області простого використання легкого запиту DSL і відображення безпосередньо на DTO, і навіть тоді я думаю, що надмірність не перешкоджає
jm0

2
jm0 для мене це не спрацювало без повноцінного імені класу, хоча воно було імпортовано. Однак компіляція успішно пройшла.
Гейзенберг

20

У моїй ситуації мені потрібен лише результат json, і це працює для мене:

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

в контролері:

@Autowired
private SchoolRepository schoolRepository;

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

2
вам слід замінити Objectкористувальницький інтерфейс, як описано mpr. працює бездоганно
phil294

14

У моєму випадку я створив окремий клас сутності без полів, які не є обов'язковими (лише з необхідними полями).

Зіставте об’єкт в одній таблиці. Тепер, коли всі стовпці потрібні, я використовую стару сутність, коли потрібні лише деякі стовпці, я використовую сутність Lite.

напр

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

Ви можете створити щось на кшталт:

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

Це працює, коли ви знаєте, що стовпці потрібно отримати (і це не зміниться).

не працюватиме, якщо вам потрібно буде динамічно визначати стовпці.


Привіт сахіне, у мене є сумніви, чи буду створювати сутність, як ви згадували вище. коли запуститься JPA, і він спробує створити таблицю з іменем користувача. яку організацію використовуватиме.
користувач3364549

3
ніколи не створюйте таблицю за допомогою JPA, створюйте таблиці вручну в db, використовуйте JPA для відображення реляційного світу для об'єктного світу.
Сахін Шарма

Чому тут не можна скористатися спадщиною?
мертвий клоп

8

Я думаю, найпростішим способом може бути використання QueryDSL , який постачається із Spring-Data.

Користуючись своїм запитанням, відповідь може бути

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

Менеджер об'єктів може бути автоматичним, і ви завжди будете працювати з об'єктом і фразами без використання * QL мови.

Як ви бачите за посиланням, останній вибір здається, майже для мене, більш елегантним, тобто використання DTO для зберігання результату. Застосуйте до свого прикладу такий:

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

Визначення ProjectDTO як:

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

З новішими версіями Spring можна зробити наступне:

Якщо ви не користуєтесь власним запитом, це можна зробити так:

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

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

Використовуючи власний запит, можна виконати те ж, що нижче:

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

Детально перевірте документи


4

На мій погляд, це чудове рішення:

interface PersonRepository extends Repository<Person, UUID> {

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

і використовувати його так

void someMethod(PersonRepository people) {

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

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

Чому б не повернути Список <T> замість колекції ?!
Абдулла хан

@AbdullahKhan, оскільки результат може не завжди мати порядок.
Раві Санваль

4

Використовуючи Spring Data JPA, існує положення щодо вибору конкретних стовпців із бази даних

---- У DAOImpl ----

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

---- в Репо ----

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

У моєму випадку це спрацювало 100%. Дякую.


2

Ви можете використовувати JPQL:

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

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

або ви можете використовувати рідний запит sql.

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

2

Можна вказати nullяк значення поля у рідному 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

Ви можете застосувати наведений нижче код в класі інтерфейсу вашого сховища.

ім'я сутності означає назву таблиці вашої бази даних, як проекти. А "Список" означає, що проект - клас особи у ваших проектах.

@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

Використання власного запиту:

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

0

Ви можете використовувати відповідь, запропоновану @jombie, і:

  • розмістити інтерфейс в окремому файлі, поза класом сутності;
  • використовувати рідний запит чи ні (вибір залежав від ваших потреб);
  • не відміняйте findAll()метод для цієї мети, але використовуйте назву на ваш вибір;
  • не забудьте повернути Listпараметризований новий інтерфейс (наприклад List<SmallProject>).
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.