สปริง JPA เลือกคอลัมน์ที่เฉพาะเจาะจง


146

ฉันใช้ Spring JPA เพื่อดำเนินการฐานข้อมูลทั้งหมด อย่างไรก็ตามฉันไม่รู้วิธีเลือกคอลัมน์เฉพาะจากตารางใน Spring JPA

ตัวอย่างเช่น:
SELECT projectId, projectName FROM projects



แนวคิดเบื้องหลัง JPA ที่ไม่ได้มองหาเขตข้อมูลเฉพาะคือต้นทุน (ประสิทธิภาพฉลาด) เหมือนกันที่จะนำหนึ่งคอลัมน์หรือคอลัมน์ทั้งหมดจากแถวหนึ่งของตาราง
ลำดับ

7
@Desorder - ค่าใช้จ่ายไม่เหมือนกันเสมอไป อาจไม่ใช่เรื่องใหญ่สำหรับประเภทข้อมูลแบบดั้งเดิมที่เรียบง่าย แต่เหตุผลที่ฉันลงเอยในหน้านี้เป็นเพราะฉันสังเกตเห็นข้อความค้นหา "เอกสารรายการ" ที่เรียบง่ายกำลังทำงานช้า เอนทิตีนั้นมีคอลัมน์ BLOB (ต้องการสำหรับการอัปโหลด / จัดเก็บไฟล์) & ฉันสงสัยว่ามันช้าเพราะมันกำลังโหลด BLOBs ลงในหน่วยความจำแม้ว่าพวกเขาจะไม่จำเป็นสำหรับการแสดงรายการเอกสาร
jm0

@ jm0 เท่าที่คุณจำได้มีกี่คอลัมน์มีคอลัมน์ BLOB?
ยกเลิก

1
@ สั่งมันเป็นเพียงตารางเดียว แต่ฉันกำลังทำฟังก์ชั่น "list" (multirow - แสดงรายการเอกสารทั้งหมดที่สร้างโดย id ที่ระบุ) เหตุผลเดียวที่ฉันสังเกตเห็นปัญหานี้เป็นเพราะแบบสอบถามรายการง่าย ๆ นี้ใช้เวลาไม่กี่วินาทีในขณะที่แบบสอบถามที่ซับซ้อนมากขึ้นในตารางอื่น ๆ ที่เกิดขึ้นเกือบจะในทันที เมื่อฉันรู้ฉันรู้ว่ามันจะเพิ่มมากขึ้นเมื่อมีการเพิ่มแถวเนื่องจากสปริง JPA กำลังโหลด BLOB ทุกรายการลงในหน่วยความจำแม้จะไม่ได้ใช้ก็ตาม ฉันพบโซลูชันที่เหมาะสมสำหรับ Spring data (โพสต์ด้านล่าง) แต่ฉันคิดว่าฉันมีดียิ่งขึ้นซึ่งเป็นคำอธิบายประกอบ 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 เห็นคำตอบ atals
phil294

1
สำหรับฉันฉันต้องผ่านคุณสมบัติคุณสมบัติแรก (ด้านบนFIND_PROJECTS) ด้วยvalueชื่อคุณลักษณะ (ดังนั้นหากนี่เป็นรหัสของฉันฉันจะต้องเขียนเป็น@Query(value = FIND_PROJECTS, nativeQuery = true)ฯลฯ
smeeb

173

คุณสามารถใช้การประมาณการจากฤดูใบไม้ผลิข้อมูล JPA (doc) ในกรณีของคุณสร้างอินเตอร์เฟส:

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

และเพิ่มวิธีการต่อไปนี้ไปยังที่เก็บของคุณ

List<ProjectIdAndName> findAll();

11
นี่คือทางออกที่สะอาด มันอาจมีแม่แบบบอยเลอร์ แต่อินเทอร์เฟซสามารถเป็นชั้นในของเอนทิตี ทำให้มันค่อนข้างสะอาด
iceman

1
น่ากลัวเพียงจำไม่ได้ที่จะใช้อินเตอร์เฟซบน Entity หรือมันจะไม่ทำงาน
alizelzele

1
อินเทอร์เฟซที่คาดการณ์ไปอยู่ที่ไหน ในไฟล์ของตนเองหรือสามารถรวมอยู่ในส่วนต่อประสานสาธารณะที่ส่งกลับคุณสมบัติเอนทิตีแบบเต็มได้หรือไม่
Micho Rizo

8
โซลูชันนี้ใช้งานไม่ได้เมื่อขยาย JpaRepository มีใครรู้วิธีแก้ปัญหาหรือไม่
เยอรมัน

4
คุณไม่สามารถใช้ findAll (); เพราะมันจะปะทะกับวิธีการ JPARepository คุณต้องใช้บางอย่างเช่นรายการ <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
(และ btw ฉันตรวจสอบแล้วคุณไม่จำเป็นต้องระบุชื่อเต็ม class หากนำเข้า "เอกสาร" - เพียงแค่เป็นอย่างนั้นเพราะนั่นเป็นวิธีที่มันทำในตัวอย่างเดียวที่ฉันสามารถหาได้)
jm0

นี่ควรเป็นคำตอบที่ยอมรับได้ มันทำงานได้อย่างสมบูรณ์และเลือกเฉพาะเขตข้อมูลที่จำเป็นจริงๆ
Yonatan Wilkof

1
รวมฟิลด์ที่ไม่จำเป็นด้วย แต่ด้วยค่า 'null' ฟิลด์เหล่านั้นจะใช้หน่วยความจำหรือไม่
gabbler

ใช่ แต่น้อยมากที่ในกรณีส่วนใหญ่มันจะไร้สาระจริงๆที่จะลองทำสิ่งนี้ - stackoverflow.com/questions/2430655/…คุณจะต้องสร้างวัตถุที่มีน้ำหนักเบาเป็นพิเศษโดยไม่มีฟิลด์เหล่านี้และให้พวกมันชี้ไปที่เดียวกัน ตาราง? ซึ่ง IMO เป็นที่ไม่พึงประสงค์เมื่อใช้ ORMs และใช้ประโยชน์จากพวกเขาสำหรับความสัมพันธ์ของพวกเขา ... Hyper-เพิ่มประสิทธิภาพอาจจะมากขึ้นในขอบเขตของการใช้เพียงบาง DSL แบบสอบถามที่มีน้ำหนักเบาและการทำแผนที่โดยตรงกับ DTOs ที่และแม้แล้วฉันคิดว่าซ้ำซ้อนเป็นกำลังใจ
jm0

2
jm0 มันไม่ทำงานสำหรับฉันหากไม่มี classname ที่มีคุณสมบัติครบถ้วนแม้ว่าจะถูกนำเข้าแล้วก็ตาม มันรวบรวมได้สำเร็จแม้ว่า
ไฮเซนเบิร์ก

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

สิ่งนี้จะทำงานเมื่อคุณรู้จักคอลัมน์ที่จะดึงข้อมูล (และนี่จะไม่เปลี่ยนแปลง)

จะไม่ทำงานหากคุณต้องการตัดสินใจคอลัมน์แบบไดนามิก


สวัสดี sachin ฉันมีหนึ่งข้อสงสัยว่าฉันจะสร้างเอนทิตีเช่นที่คุณกล่าวถึงข้างต้น เมื่อ JPA จะทำงานและจะพยายามสร้างตารางด้วยชื่อผู้ใช้ เอนทิตีที่จะใช้
user3364549

3
ไม่เคยสร้างตารางด้วย JPA สร้างตารางของคุณด้วยตนเองใน db ใช้ JPA เพื่อแมปโลกเชิงสัมพันธ์กับโลกวัตถุ
Sachin Sharma

ทำไมคุณไม่สามารถใช้มรดกที่นี่ได้?
deadbug

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

ผู้จัดการเอนทิตีสามารถ Autowired และคุณจะทำงานกับวัตถุและ clases โดยไม่ต้องใช้ภาษา * 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 เวอร์ชั่นที่ใหม่กว่า One สามารถทำได้ดังนี้:

หากไม่ได้ใช้เคียวรีแบบดั้งเดิมคุณสามารถทำได้ดังนี้:

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 เพราะผลลัพธ์อาจไม่ได้มีคำสั่งเสมอไป
Ravi Sanwal

4

การใช้ Spring Data JPA มีข้อกำหนดเพื่อเลือกคอลัมน์เฉพาะจากฐานข้อมูล

---- ใน DAOImpl ----

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

---- ใน Repo ----

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

คุณสามารถใช้รหัสด้านล่างในคลาสอินเทอร์เฟซที่เก็บข้อมูลของคุณ

เอนทิตี้ชื่อหมายถึงชื่อตารางฐานข้อมูลของคุณเช่นโครงการ และรายการหมายถึง Project เป็นคลาส Entity ในโครงการของคุณ

@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()เมธอดเพื่อจุดประสงค์นี้ แต่ใช้ชื่อที่คุณเลือก
  • อย่าลืมส่งคืนListparametrized ด้วยอินเทอร์เฟซใหม่ของคุณ (เช่นList<SmallProject>)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.