JPA: วิธีแปลงชุดผลลัพธ์คิวรีแบบเนทีฟเป็นคอลเลกชันคลาส POJO


174

ฉันกำลังใช้ JPA ในโครงการของฉัน

ฉันมาถึงคิวรีที่ฉันต้องทำการเข้าร่วมในตารางที่ห้า ดังนั้นฉันจึงสร้างเคียวรีเนทีฟซึ่งส่งคืนห้าฟิลด์

ตอนนี้ฉันต้องการแปลงวัตถุผลลัพธ์เป็นคลาส Java POJO ซึ่งมีห้า Strings เหมือนกัน

มีวิธีใดใน JPA ที่จะส่งผลลัพธ์นั้นโดยตรงไปยังรายการวัตถุ POJO หรือไม่?

ฉันมาถึงวิธีแก้ปัญหาต่อไปนี้ ..

@NamedNativeQueries({  
    @NamedNativeQuery(  
        name = "nativeSQL",  
        query = "SELECT * FROM Actors",  
        resultClass = db.Actor.class),  
    @NamedNativeQuery(  
        name = "nativeSQL2",  
        query = "SELECT COUNT(*) FROM Actors",  
        resultClass = XXXXX) // <--------------- problem  
})  

ตอนนี้ที่นี่ใน resultClass เราจำเป็นต้องจัดเตรียมคลาสที่เป็นเอนทิตี JPA จริงหรือไม่? หรือเราสามารถแปลงเป็นคลาส JAVA POJO ที่มีชื่อคอลัมน์เดียวกันได้หรือไม่


ตรวจสอบคำตอบนี้ มันมีคำตอบที่สมบูรณ์: stackoverflow.com/a/50365522/3073945
Md. Sajedul Karim

เขาใช้ jpa ไม่ใช่ฤดูใบไม้ผลิ
เขา

คำตอบ:


103

JPA จัดเตรียมสิ่งSqlResultSetMappingที่อนุญาตให้คุณแมปสิ่งที่ส่งคืนจากเคียวรีดั้งเดิมของคุณไปยัง Entityหรือคลาสที่กำหนดเอง.

EDIT JPA 1.0 ไม่อนุญาตให้ทำการแมปไปยังคลาสที่ไม่ใช่เอนทิตี เฉพาะใน JPA 2.1 ConstructorResultถูกเพิ่มเข้ากับค่าที่ส่งคืนของคลาส java

นอกจากนี้สำหรับปัญหาของ OP ด้วยการนับก็น่าจะเพียงพอที่จะกำหนดการแมปชุดผลลัพธ์ด้วยอันเดียว ColumnResult


1
ขอบคุณสำหรับการตอบกลับ. ที่นี่เรากำลังทำการแมปผลลัพธ์ของเรากับเอนทิตีที่มีคลาสเอนทิตี tha java ด้วยคำอธิบายประกอบ "@EntityResult" และ "@FieldResult" ไม่เป็นไร. แต่ที่นี่ฉันต้องการความชัดเจนมากขึ้น จำเป็นต้องมีคลาสที่เรากำลังจับคู่กับผลลัพธ์หรือไม่นั้นต้องเป็นคลาสเอนทิตี JPA หรือเราสามารถใช้คลาส POJO แบบง่าย ๆ ซึ่งไม่ใช่การซื้อแบบเอนทิตีซึ่งมีตัวแปรที่จำเป็นทั้งหมดเป็นคอลัมน์ในชุดผลลัพธ์
Gunjan Shah

1
@GunjanShah: วิธีที่ดีที่สุดที่จะรู้ก็คือลองดู :) เช่นกันเอนทิตีเป็นเพียง pojo เดียวกันกับคำอธิบายประกอบบางอย่าง ตราบใดที่คุณไม่ได้พยายามที่จะทำแบบนั้น
เดนิส Tulskiy

2
เมื่อฉันลองทำสิ่งนี้ฉันพบข้อผิดพลาดว่าชั้นเรียนไม่ได้เป็นที่รู้จัก ฉันลงเอยด้วยการใช้stackoverflow.com/questions/5024533/วิธีการนี้แทนการพยายามใช้คิวรีดั้งเดิม
FGreg

2
@EdwinDalorzo: นั่นเหมาะสำหรับ jpa 1.0 ใน jpa 2.1 พวกเขาได้เพิ่มConstructorResultเป็นหนึ่งในพารามิเตอร์SqlResultSetMappingที่อนุญาตให้ใช้ pojo กับฟิลด์ทั้งหมดที่กำหนดไว้ในตัวสร้าง ฉันจะอัปเดตคำตอบ
Denis Tulskiy

4
ฉันเห็นความจริงอันขมขื่นอื่น ๆ : ConstructorResult สามารถแมปไปที่ POJO ได้ แต่ ConstructorResult นั้นจะต้องอยู่ในคลาส Entity ดังนั้น Entity ที่คุณไม่สามารถหลีกเลี่ยงได้ ... ดังนั้นความจริงที่ยิ่งใหญ่กว่า: คุณต้องการผลลัพธ์บางอย่างโดยไม่สนใจ เป็นคีย์หลัก - คุณยังต้องมี @Id ใน Entity ... ไร้สาระใช่มั้ย
Arnab Dutta

210

ฉันได้พบวิธีแก้ไขปัญหานี้สองสามข้อ

ใช้หน่วยงานที่แมป (JPA 2.0)

การใช้ JPA 2.0 นั้นไม่สามารถแมปเคียวรีดั้งเดิมกับ POJO ได้สามารถทำได้กับเอนทิตีเท่านั้น

ตัวอย่างเช่น

Query query = em.createNativeQuery("SELECT name,age FROM jedi_table", Jedi.class);
@SuppressWarnings("unchecked")
List<Jedi> items = (List<Jedi>) query.getResultList();

แต่ในกรณีนี้Jediต้องเป็นคลาสเอนทิตีที่แมป

อีกทางเลือกหนึ่งในการหลีกเลี่ยงคำเตือนที่ไม่ถูกตรวจสอบที่นี่คือการใช้เคียวรีเนทีฟที่มีชื่อ ดังนั้นถ้าเราประกาศเคียวรีดั้งเดิมในเอนทิตี

@NamedNativeQuery(
 name="jedisQry", 
 query = "SELECT name,age FROM jedis_table", 
 resultClass = Jedi.class)

จากนั้นเราสามารถทำได้:

TypedQuery<Jedi> query = em.createNamedQuery("jedisQry", Jedi.class);
List<Jedi> items = query.getResultList();

สิ่งนี้ปลอดภัยกว่า แต่เรายังถูก จำกัด ให้ใช้เอนทิตีที่แมป

การทำแผนที่ด้วยตนเอง

วิธีแก้ปัญหาที่ฉันทดลองเล็กน้อย (ก่อนการมาถึงของ JPA 2.1) กำลังทำแผนที่กับตัวสร้าง POJO โดยใช้การสะท้อนเล็กน้อย

public static <T> T map(Class<T> type, Object[] tuple){
   List<Class<?>> tupleTypes = new ArrayList<>();
   for(Object field : tuple){
      tupleTypes.add(field.getClass());
   }
   try {
      Constructor<T> ctor = type.getConstructor(tupleTypes.toArray(new Class<?>[tuple.length]));
      return ctor.newInstance(tuple);
   } catch (Exception e) {
      throw new RuntimeException(e);
   }
}

วิธีการนี้โดยทั่วไปใช้อาร์เรย์ tuple (ตามที่ส่งคืนโดยการสืบค้นดั้งเดิม) และแมปกับคลาส POJO ที่จัดไว้ให้โดยค้นหา Constructor ที่มีจำนวนฟิลด์เท่ากันและเป็นประเภทเดียวกัน

จากนั้นเราสามารถใช้วิธีการที่สะดวกสบายเช่น:

public static <T> List<T> map(Class<T> type, List<Object[]> records){
   List<T> result = new LinkedList<>();
   for(Object[] record : records){
      result.add(map(type, record));
   }
   return result;
}

public static <T> List<T> getResultList(Query query, Class<T> type){
  @SuppressWarnings("unchecked")
  List<Object[]> records = query.getResultList();
  return map(type, records);
}

และเราสามารถใช้เทคนิคนี้ดังนี้:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table");
List<Jedi> jedis = getResultList(query, Jedi.class);

JPA 2.1 ด้วย @SqlResultSetMapping

ด้วยการมาถึงของ JPA 2.1 เราสามารถใช้คำอธิบายประกอบ @SqlResultSetMapping เพื่อแก้ไขปัญหา

เราจำเป็นต้องประกาศการแมปชุดผลลัพธ์บางแห่งในเอนทิตี:

@SqlResultSetMapping(name="JediResult", classes = {
    @ConstructorResult(targetClass = Jedi.class, 
    columns = {@ColumnResult(name="name"), @ColumnResult(name="age")})
})

แล้วเราก็ทำ:

Query query = em.createNativeQuery("SELECT name,age FROM jedis_table", "JediResult");
@SuppressWarnings("unchecked")
List<Jedi> samples = query.getResultList();

แน่นอนในกรณีนี้Jediไม่จำเป็นต้องเป็นเอนทิตีที่แมป มันอาจเป็น POJO ปกติ

ใช้การแมป XML

ฉันเป็นหนึ่งในผู้ที่พบว่าการเพิ่มการ@SqlResultSetMappingบุกรุกเหล่านี้ในเอนทิตี้ของฉันและฉันไม่ชอบคำจำกัดความของการสืบค้นที่มีชื่อภายในเอนทิตีดังนั้นฉันก็ทำทั้งหมดนี้ในMETA-INF/orm.xmlไฟล์:

<named-native-query name="GetAllJedi" result-set-mapping="JediMapping">
    <query>SELECT name,age FROM jedi_table</query>
</named-native-query>

<sql-result-set-mapping name="JediMapping">
        <constructor-result target-class="org.answer.model.Jedi">
            <column name="name" class="java.lang.String"/>
            <column name="age" class="java.lang.Integer"/>
        </constructor-result>
    </sql-result-set-mapping>

และนั่นคือคำตอบทั้งหมดที่ฉันรู้ สองอันสุดท้ายเป็นวิธีเหมาะสมที่สุดหากเราสามารถใช้ JPA 2.1


1
Sidenote: ฉันเพิ่งใช้แนวทาง JPA 2.0 กับการพึ่งพา JPA2.1 และล้มเหลว ดังนั้นอาจนี้เข้ากันไม่ได้ลง ...
membersound

1
คุณหมายถึง "ที่ไหนในเอนทิตี้ของ" Pojo ของฉันไม่ใช่ JPA Entity ฉันไม่สามารถประกาศ @SqlResultSetMapping ใน POJO ของฉันได้หรือไม่ ฉันสนใจโซลูชั่น JPA 2.1 โปรดแม่นยำมากกว่านี้หน่อย
Alboz

3
@Alboz @SqlResultSetMappingต้องอยู่ในเอนทิตีเพราะนั่นคือสิ่งที่ JPA กำลังอ่านข้อมูลเมตา คุณไม่สามารถคาดหวังให้ JPA ตรวจสอบ POJO ของคุณได้ เอนทิตีที่คุณวางการแมปนั้นไม่เกี่ยวข้องบางทีอาจเกี่ยวข้องกับผลลัพธ์ POJO ของคุณมากขึ้น อีกทางเลือกหนึ่งการทำแผนที่สามารถแสดงใน XML เพื่อหลีกเลี่ยงการมีเพศสัมพันธ์กับนิติบุคคลที่ไม่เกี่ยวข้องทั้งหมด
Edwin Dalorzo

1
เป็นไปได้ไหมที่นวกรรมิกจะใช้คลาสที่มีคลาสซ้อนอยู่?
chrismarx

5
ถ้าใช้ JPA 2.1 พร้อม@SqlResultSetMappingมันอาจจะเป็นที่น่าสังเกตว่าJediระดับจะต้องมีทุกหาเรื่องคอนสตรัคและ@ColumnResultคำอธิบายประกอบอาจต้องtypeแอตทริบิวต์เพิ่มไปยังแปลงซึ่งอาจจะไม่ได้โดยปริยาย (ฉันต้องการที่จะเพิ่ม type = ZonedDateTime.classสำหรับบางคอลัมน์)
เกล็น

11

ใช่ด้วย JPA 2.1 มันง่ายมาก คุณมีบันทึกย่อที่มีประโยชน์มาก มันทำให้ชีวิตคุณง่ายขึ้น

ขั้นแรกให้ประกาศเคียวรีดั้งเดิมของคุณจากนั้นจึงจับคู่ชุดผลลัพธ์ (ซึ่งกำหนดการแมปข้อมูลที่ส่งคืนโดยฐานข้อมูลไปยัง POJO ของคุณ) เขียนคลาส POJO ของคุณเพื่ออ้างถึง (ไม่รวมที่นี่เพื่อความกะทัดรัด) สุดท้าย แต่ไม่ท้ายสุด: สร้างวิธีใน DAO (ตัวอย่าง) เพื่อเรียกคิวรี สิ่งนี้ใช้ได้กับฉันในแอป dropwizard (1.0.0)

ก่อนอื่นประกาศคิวรีดั้งเดิมในคลาสเอนทิตี:

@NamedNativeQuery (
name = "domain.io.MyClass.myQuery",
query = "Select a.colA, a.colB from Table a",
resultSetMapping = "mappinMyNativeQuery")   // must be the same name as in the SqlResultSetMapping declaration

ภายใต้คุณสามารถเพิ่มการประกาศการแมปชุดผลลัพธ์:

@SqlResultSetMapping(
name = "mapppinNativeQuery",  // same as resultSetMapping above in NativeQuery
   classes = {
      @ConstructorResult( 
          targetClass = domain.io.MyMapping.class,
          columns = {
               @ColumnResult( name = "colA", type = Long.class),  
               @ColumnResult( name = "colB", type = String.class)
          }
      )
   } 
)

ในภายหลังใน DAO คุณสามารถอ้างถึงแบบสอบถามเป็น

public List<domain.io.MyMapping> findAll() {
        return (namedQuery("domain.io.MyClass.myQuery").list());
    }

แค่นั้นแหละ.


คำตอบที่ดี แต่ฉันคิดว่าคุณพลาดวงเล็บหลังคำอธิบายประกอบ @ColumnResult แรก
mwatzer

มีข้อผิดพลาดในรหัส แต่ง่ายต่อการแก้ไข ตัวอย่างเช่น: "resulSetMapping =" ควรเป็น "resultSetMapping ="
Zbyszek

3
ฉันเห็นความจริงที่ขมขื่นอีก: NamedNativeQuery & SqlResultSetMapping ต้องอยู่ในชั้น @Entity
Arnab Dutta

10

หากคุณใช้Spring-jpaนี่เป็นส่วนเสริมของคำตอบและคำถามนี้ โปรดแก้ไขข้อผิดพลาดนี้หากมี ฉันได้ใช้วิธีการสามวิธีในการบรรลุ "ผลลัพธ์Object[]การจับคู่กับ pojo" โดยยึดตามสิ่งที่ฉันต้องการในทางปฏิบัติ:

  1. JPA สร้างขึ้นในวิธีการก็เพียงพอแล้ว
  2. JPA ที่สร้างขึ้นในวิธีการนั้นไม่เพียงพอ แต่การปรับแต่งsqlด้วยมันEntityก็เพียงพอแล้ว
  3. อดีต 2 nativeQueryล้มเหลวและฉันต้องใช้ นี่คือตัวอย่าง pojo คาดว่า:

    public class Antistealingdto {
    
        private String secretKey;
    
        private Integer successRate;
    
        // GETTERs AND SETTERs
    
        public Antistealingdto(String secretKey, Integer successRate) {
            this.secretKey = secretKey;
            this.successRate = successRate;
        }
    }

วิธีที่ 1 : เปลี่ยน pojo เป็นอินเทอร์เฟซ:

public interface Antistealingdto {
    String getSecretKey();
    Integer getSuccessRate();
}

และที่เก็บ:

interface AntiStealingRepository extends CrudRepository<Antistealing, Long> {
    Antistealingdto findById(Long id);
}

วิธีที่ 2 : ที่เก็บ:

@Query("select new AntistealingDTO(secretKey, successRate) from Antistealing where ....")
Antistealing whatevernamehere(conditions);

หมายเหตุ: ลำดับพารามิเตอร์ของตัวสร้าง POJO จะต้องเหมือนกันทั้งในนิยาม POJO และ sql

วิธีที่ 3 : การใช้@SqlResultSetMappingและ@NamedNativeQueryในEntityฐานะที่เป็นตัวอย่างในคำตอบของเอ็ดวิน Dalorzo ของ

สองวิธีแรกจะเรียกใช้ตัวจัดการที่อยู่ตรงกลางจำนวนมากเช่นตัวแปลงที่กำหนดเอง ตัวอย่างเช่นAntiStealingกำหนด a secretKeyก่อนที่จะยืนยันตัวแปลงจะถูกแทรกเพื่อเข้ารหัส นี่จะส่งผลให้ 2 วิธีแรกคืนค่ากลับมาsecretKeyซึ่งไม่ใช่สิ่งที่ฉันต้องการ ในขณะที่วิธีที่ 3 จะเอาชนะตัวแปลงและกลับมาsecretKeyจะเหมือนกันกับที่เก็บไว้ (เข้ารหัส)


วิธีที่ 1 ไม่ต้องการสปริงและทำงานกับไฮเบอร์เนตแท้ๆ
Martin Vysny

@ MartinVysny ใช่ M1 คือ JPQL โครงการใด ๆ ที่ดำเนินการ JPQL ควรสนับสนุน ด้วยวิธีนี้ M2 อาจได้รับการสนับสนุนอย่างกว้างขวางเช่นกัน?
Tiina

8

ขั้นตอนการแกะที่ไม่สามารถทำได้เพื่อกำหนดผลลัพธ์ให้กับผู้ที่ไม่ใช่นิติบุคคล (ซึ่งเป็นถั่ว / POJO) ขั้นตอนดังต่อไปนี้

List<JobDTO> dtoList = entityManager.createNativeQuery(sql)
        .setParameter("userId", userId)
        .unwrap(org.hibernate.Query.class).setResultTransformer(Transformers.aliasToBean(JobDTO.class)).list();

การใช้งานสำหรับการใช้งาน JPA-Hibernate


ทราบว่าJobDTOควรจะมีการสร้างเริ่มต้น หรือคุณอาจนำหม้อแปลงของคุณไปAliasToBeanResultTransformerใช้
Lu55

4

ก่อนอื่นให้ประกาศคำอธิบายประกอบต่อไปนี้:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultEntity {
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeQueryResultColumn {
    int index();
}

จากนั้นใส่หมายเหตุประกอบ POJO ของคุณดังนี้:

@NativeQueryResultEntity
public class ClassX {
    @NativeQueryResultColumn(index=0)
    private String a;

    @NativeQueryResultColumn(index=1)
    private String b;
}

จากนั้นเขียนตัวประมวลผลคำอธิบายประกอบ:

public class NativeQueryResultsMapper {

    private static Logger log = LoggerFactory.getLogger(NativeQueryResultsMapper.class);

    public static <T> List<T> map(List<Object[]> objectArrayList, Class<T> genericType) {
        List<T> ret = new ArrayList<T>();
        List<Field> mappingFields = getNativeQueryResultColumnAnnotatedFields(genericType);
        try {
            for (Object[] objectArr : objectArrayList) {
                T t = genericType.newInstance();
                for (int i = 0; i < objectArr.length; i++) {
                    BeanUtils.setProperty(t, mappingFields.get(i).getName(), objectArr[i]);
                }
                ret.add(t);
            }
        } catch (InstantiationException ie) {
            log.debug("Cannot instantiate: ", ie);
            ret.clear();
        } catch (IllegalAccessException iae) {
            log.debug("Illegal access: ", iae);
            ret.clear();
        } catch (InvocationTargetException ite) {
            log.debug("Cannot invoke method: ", ite);
            ret.clear();
        }
        return ret;
    }

    // Get ordered list of fields
    private static <T> List<Field> getNativeQueryResultColumnAnnotatedFields(Class<T> genericType) {
        Field[] fields = genericType.getDeclaredFields();
        List<Field> orderedFields = Arrays.asList(new Field[fields.length]);
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].isAnnotationPresent(NativeQueryResultColumn.class)) {
                NativeQueryResultColumn nqrc = fields[i].getAnnotation(NativeQueryResultColumn.class);
                orderedFields.set(nqrc.index(), fields[i]);
            }
        }
        return orderedFields;
    }
}

ใช้กรอบงานข้างต้นดังนี้:

String sql = "select a,b from x order by a";
Query q = entityManager.createNativeQuery(sql);

List<ClassX> results = NativeQueryResultsMapper.map(q.getResultList(), ClassX.class);

ซึ่งแพคเกจคือBeanUtilsอะไรบ้าง?
Harish

4

วิธีที่ง่ายที่สุดคือการใช้เพื่อให้การคาดการณ์ มันสามารถแมปผลลัพธ์แบบสอบถามโดยตรงกับอินเทอร์เฟซและใช้งานง่ายกว่าการใช้ SqlResultSetMapping

ตัวอย่างที่แสดงด้านล่าง:

@Repository
public interface PeopleRepository extends JpaRepository<People, Long> {

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    List<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId);

    @Query(value = "SELECT p.name AS name, COUNT(dp.people_id) AS count " +
        "FROM people p INNER JOIN dream_people dp " +
        "ON p.id = dp.people_id " +
        "WHERE p.user_id = :userId " +
        "GROUP BY dp.people_id " +
        "ORDER BY p.name", nativeQuery = true)
    Page<PeopleDTO> findByPeopleAndCountByUserId(@Param("userId") Long userId, Pageable pageable);

}



// Interface to which result is projected
public interface PeopleDTO {

    String getName();

    Long getCount();

}

เขตข้อมูลจากอินเทอร์เฟซที่คาดการณ์ต้องตรงกับเขตข้อมูลในเอนทิตีนี้ มิฉะนั้นการแมปฟิลด์อาจผิดพลาด

นอกจากนี้หากคุณใช้SELECT table.columnสัญกรณ์กำหนดชื่อแทนชื่อที่ตรงกันจากเอนทิตีเสมอตามที่แสดงในตัวอย่าง


1
ข้อความค้นหาและการคาดคะเนแบบดั้งเดิมทำงานได้ไม่ดี
Kevin Rave

1
ฉันไม่สามารถทำให้การแมปภาคสนามทำงานได้ค่อนข้างถูกต้อง - ค่าส่วนใหญ่กลับมาเป็นโมฆะ
ayang

4

ในโหมดไฮเบอร์เนตคุณสามารถใช้รหัสนี้เพื่อจับคู่การสอบถามท้องถิ่นของคุณได้อย่างง่ายดาย

private List < Map < String, Object >> getNativeQueryResultInMap() {
String mapQueryStr = "SELECT * FROM AB_SERVICE three ";
Query query = em.createNativeQuery(mapQueryStr);
NativeQueryImpl nativeQuery = (NativeQueryImpl) query;
nativeQuery.setResultTransformer(AliasToEntityMapResultTransformer.INSTANCE);
List < Map < String, Object >> result = query.getResultList();
for (Map map: result) {
    System.out.println("after request  ::: " + map);
}
return result;}

2

ใช้ไฮเบอร์เนต:

@Transactional(readOnly=true)
public void accessUser() {
EntityManager em = repo.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u").addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE).addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE).addScalar("loginType", IntegerType.INSTANCE)
        .setResultTransformer(Transformers.aliasToBean(User2DTO.class));

    List<User2DTO> userList = q.list();
}

2

รูปแบบเก่าโดยใช้ ResultSet

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = this.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    session.doWork(new Work() {
        @Override
        public void execute(Connection con) throws SQLException {
            try (PreparedStatement stmt = con.prepareStatement(
                    "SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
                ResultSet rs = stmt.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
                }
                System.out.println("");
                while (rs.next()) {
                    System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
                }
            }
        }
    });
}

1

เนื่องจากคนอื่น ๆ ได้พูดถึงวิธีแก้ปัญหาที่เป็นไปได้ทั้งหมดแล้วฉันกำลังแบ่งปันวิธีแก้ปัญหาของฉัน

ในสถานการณ์ของฉันกับPostgres 9.4ขณะที่ทำงานกับJackson,

//Convert it to named native query.
List<String> list = em.createNativeQuery("select cast(array_to_json(array_agg(row_to_json(a))) as text) from myschema.actors a")
                   .getResultList();

List<ActorProxy> map = new ObjectMapper().readValue(list.get(0), new TypeReference<List<ActorProxy>>() {});

ฉันแน่ใจว่าคุณสามารถค้นหาฐานข้อมูลอื่น ๆ ได้เหมือนกัน

รวมทั้ง FYI, JPA 2.0 ผลลัพธ์การสืบค้นพื้นฐานเป็นแผนที่


1

ไม่แน่ใจว่าสิ่งนี้ตรงกับที่นี่หรือไม่ แต่ฉันมีคำถามที่คล้ายกันและพบวิธีการแก้ปัญหา / ตัวอย่างง่ายๆสำหรับฉัน:

private EntityManager entityManager;
...
    final String sql = " SELECT * FROM STORE "; // select from the table STORE
    final Query sqlQuery = entityManager.createNativeQuery(sql, Store.class);

    @SuppressWarnings("unchecked")
    List<Store> results = (List<Store>) sqlQuery.getResultList();

ในกรณีของฉันฉันต้องใช้ส่วน SQL ที่กำหนดไว้ใน Strings ที่อื่นดังนั้นฉันจึงไม่สามารถใช้ NamedNativeQuery


ทันทีที่เราส่งคืนเอนทิตี ไม่มีอะไรแฟนซี ปัญหาคือเมื่อคุณพยายามแมปผลลัพธ์กับ POJO ที่ไม่มีการจัดการ
Olgun Kaya

1

รูปแบบเก่าโดยใช้ Resultset

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = this.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    session.doWork(new Work() {
        @Override
        public void execute(Connection con) throws SQLException {
            try (PreparedStatement stmt = con.prepareStatement(
                    "SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")) {
                ResultSet rs = stmt.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                for (int i = 1; i <= rsmd.getColumnCount(); i++) {
                    System.out.print(rsmd.getColumnName(i) + " (" + rsmd.getColumnTypeName(i) + ") / ");
                }
                System.out.println("");
                while (rs.next()) {
                    System.out.println("Found username " + rs.getString("USERNAME") + " name " + rs.getString("NAME") + " email " + rs.getString("EMAIL") + " passe " + rs.getString("PASSE") + " email " + rs.getInt("LOGIN_TYPE"));
                }
            }
        }
    });
}

1

เราได้แก้ไขปัญหาโดยใช้วิธีต่อไปนี้:

   //Add actual table name here in Query
    final String sqlQuery = "Select a.* from ACTORS a"
    // add your entity manager here 
    Query query = entityManager.createNativeQuery(sqlQuery,Actors.class);
    //List contains the mapped entity data.
    List<Actors> list = (List<Actors>) query.getResultList();

0

ดูตัวอย่างด้านล่างสำหรับการใช้ POJO เป็นเอนทิตีหลอกเพื่อดึงผลลัพธ์จากการสืบค้นดั้งเดิมโดยไม่ต้องใช้ SqlResultSetMapping ที่ซับซ้อน เพียงต้องการคำอธิบายประกอบสองรายการ @Enity ที่เปลือยเปล่าและหุ่นจำลอง @Id ใน POJO ของคุณ @Id สามารถใช้ได้กับทุกฟิลด์ที่คุณเลือกฟิลด์ @Id สามารถมีคีย์ที่ซ้ำกันได้ แต่ไม่ใช่ค่า Null

เนื่องจาก @Enity ไม่แม็พกับตารางฟิสิคัลดังนั้น POJO นี้จึงเรียกว่าเอนทิตีหลอก

สภาพแวดล้อม: eclipselink 2.5.0-RC1, jpa-2.1.0, mysql-connector-java-5.1.14

คุณสามารถดาวน์โหลดโครงการ maven สมบูรณ์ได้ที่นี่

แบบสอบถามแบบเนทีฟนั้นยึดตามฐานข้อมูลตัวอย่างของพนักงาน mysql db http://dev.mysql.com/doc/employee/en/employees-installation.html

persistence.xml

<?xml version="1.0" encoding="UTF-8"?><persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.1" 
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="jpa-mysql" transaction-type="RESOURCE_LOCAL">
    <class>org.moonwave.jpa.model.pojo.Employee</class>
    <properties>
        <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/employees" />
        <property name="javax.persistence.jdbc.user" value="user" />
        <property name="javax.persistence.jdbc.password" value="***" />
        <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver" />
    </properties>
</persistence-unit>

Employee.java

package org.moonwave.jpa.model.pojo;

@Entity
public class Employee {

@Id
protected Long empNo;

protected String firstName;
protected String lastName;
protected String title;

public Long getEmpNo() {
    return empNo;
}
public void setEmpNo(Long empNo) {
    this.empNo = empNo;
}
public String getFirstName() {
    return firstName;
}
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
public String getLastName() {
    return lastName;
}
public void setLastName(String lastName) {
    this.lastName = lastName;
}   
public String getTitle() {
    return title;
}
public void setTitle(String title) {
    this.title = title;
}
public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("empNo: ").append(empNo);
    sb.append(", firstName: ").append(firstName);
    sb.append(", lastName: ").append(lastName);
    sb.append(", title: ").append(title);
    return sb.toString();
}
}

EmployeeNativeQuery.java

public class EmployeeNativeQuery {
private EntityManager em;
private EntityManagerFactory emf;

public void setUp() throws Exception {
    emf=Persistence.createEntityManagerFactory("jpa-mysql");
    em=emf.createEntityManager();
}
public void tearDown()throws Exception {
    em.close();
    emf.close();
}

@SuppressWarnings("unchecked")
public void query() {
    Query query = em.createNativeQuery("select e.emp_no as empNo, e.first_name as firstName, e.last_name as lastName," + 
            "t.title from employees e join titles t on e.emp_no = t.emp_no", Employee.class);
    query.setMaxResults(30);
    List<Employee> list = (List<Employee>) query.getResultList();
    int i = 0;
    for (Object emp : list) {
        System.out.println(++i + ": " + emp.toString());
    }
}

public static void main( String[] args ) {
    EmployeeNativeQuery test = new EmployeeNativeQuery();
    try {
        test.setUp();
        test.query();
        test.tearDown();
    } catch (Exception e) {
        System.out.println(e);
    }
}
}

1
ตั้งแต่คุณlistมีที่ถูกกล่าวหาว่ารายการEmployeeทำไมคุณสำหรับแต่ละวงวนมากกว่าชนิดObject? หากคุณเขียนลูปสำหรับแต่ละวงfor(Employee emp : list)แล้วคุณจะพบว่าคำตอบของคุณไม่ถูกต้องและเนื้อหาในรายการของคุณไม่ใช่พนักงานและคำเตือนที่คุณระงับนั้นมีวัตถุประสงค์เพื่อแจ้งเตือนคุณเกี่ยวกับข้อผิดพลาดที่อาจเกิดขึ้นนี้
Edwin Dalorzo

@SuppressWarnings ( "ไม่ จำกัด") จะใช้ในการเตือนปราบสำหรับ List<Employee> list = (List<Employee>) query.getResultList();เปลี่ยนfor (Object emp : list)ไปfor (Employee emp : list)จะดีกว่า แต่ไม่มีข้อผิดพลาดถ้าเก็บไว้เป็นตั้งแต่รายการเป็นตัวอย่างของObject emp List<Employee>ฉันเปลี่ยนรหัสในโครงการคอมไพล์ แต่ไม่ได้อยู่ที่นี่เพื่อให้ความคิดเห็นของคุณเกี่ยวข้องกับโพสต์ต้นฉบับ
Jonathan L

ปัญหาคือว่าแบบสอบถามของคุณไม่ได้ส่งคืนรายการของลูกจ้าง แต่เป็นอาร์เรย์ของวัตถุ คำเตือนที่ระงับของคุณซ่อนอยู่ ในช่วงเวลาที่คุณพยายามที่จะแปลงใด ๆ ให้เป็นพนักงานคุณจะได้รับข้อผิดพลาดข้อยกเว้นที่หล่อ
Edwin Dalorzo

ดูQuery query = em.createNativeQuery("select * ...", Employee.class);และ persistence.xml แบบสอบถามดั้งเดิมจะส่งคืนรายการของพนักงาน ฉันเพิ่งตรวจสอบและเรียกใช้โครงการโดยไม่มีปัญหา หากคุณตั้งค่า mysql ตัวอย่างพนักงาน db ในเครื่องคุณควรจะสามารถเรียกใช้โครงการได้เช่นกัน
Jonathan L

โอ้ฉันเห็นสิ่งที่คุณหมายถึงตอนนี้ แต่ในกรณีนี้คำตอบของคุณไม่เป็นไปตามคำถามเพราะมันเกี่ยวกับการใช้ POJO ปกติเป็นวัตถุเป้าหมายและคำตอบของคุณใช้Employeeซึ่งฉันถือว่าเป็นเอนทิตี ไม่ใช่เหรอ
Edwin Dalorzo

0

หากคุณกำลังใช้งานสปริงคุณสามารถใช้งานได้ org.springframework.jdbc.core.RowMapper

นี่คือตัวอย่าง:

public List query(String objectType, String namedQuery)
{
  String rowMapper = objectType + "RowMapper";
  // then by reflection you can instantiate and use. The RowMapper classes need to follow the naming specific convention to follow such implementation.
} 

0

ใช้ไฮเบอร์เนต:

@Transactional(readOnly=true)
public void accessUser() {
    EntityManager em = repo.getEntityManager();
    org.hibernate.Session session = em.unwrap(org.hibernate.Session.class);
    org.hibernate.SQLQuery q = (org.hibernate.SQLQuery) session.createSQLQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u")
        .addScalar("username", StringType.INSTANCE).addScalar("name", StringType.INSTANCE)
        .addScalar("email", StringType.INSTANCE).addScalar("passe", StringType.INSTANCE)
        .addScalar("loginType", IntegerType.INSTANCE)
        .setResultTransformer(Transformers.aliasToBean(User2DTO.class));

    List<User2DTO> userList = q.list();
}

-1

วิธีง่ายๆในการแปลงแบบสอบถาม SQL เป็นคอลเลกชันคลาส POJO

Query query = getCurrentSession().createSQLQuery(sqlQuery).addEntity(Actors.class);
List<Actors> list = (List<Actors>) query.list();
return list;

-1

สิ่งที่คุณต้องมีคือ DTO พร้อมคอนสตรัคเตอร์:

public class User2DTO implements Serializable {

    /** pode ser email ou id do Google comecando com G ou F para Facebook */
    private String username;

    private String password;

    private String email;

    private String name;

    private Integer loginType;

    public User2DTO(Object...fields) {
        super();
        this.username = (String) fields[0];
        this.name = (String) fields[1];
        this.email = (String) fields[2];
        this.password = (String) fields[3];
        this.loginType = (Integer) fields[4];
    }

และเรียกมันว่า:

EntityManager em = repo.getEntityManager();
        Query q = em.createNativeQuery("SELECT u.username, u.name, u.email, 'blabla' as passe, login_type as loginType FROM users u");
        List<Object[]> objList = q.getResultList();
        List<User2DTO> ooBj = objList.stream().map(User2DTO::new).collect(Collectors.toList());

เพิ่มคอลัมน์ใหม่และรหัสจะแตก
จาน

-2

DTO Design Patternใช้ EJB 2.0มันถูกใช้ใน เอนทิตีถูกจัดการคอนเทนเนอร์ DTO Design Patternใช้สำหรับแก้ปัญหานี้ แต่มันอาจจะใช้ตอนนี้เมื่อแอปพลิเคชันได้รับการพัฒนาServer SideและClient Sideแยกต่างหาก DTOจะใช้เมื่อServer sideไม่ต้องการที่จะผ่าน / ผลตอบแทนที่มีคำอธิบายประกอบการEntityClient Side

ตัวอย่าง DTO:

PersonEntity.java

@Entity
public class PersonEntity {
    @Id
    private String id;
    private String address;

    public PersonEntity(){

    }
    public PersonEntity(String id, String address) {
        this.id = id;
        this.address = address;
    }
    //getter and setter

}

PersonDTO.java

public class PersonDTO {
    private String id;
    private String address;

    public PersonDTO() {
    }
    public PersonDTO(String id, String address) {
        this.id = id;
        this.address = address;
    }

    //getter and setter 
}

DTOBuilder.java

public class DTOBuilder() {
    public static PersonDTO buildPersonDTO(PersonEntity person) {
        return new PersonDTO(person.getId(). person.getAddress());
    }
}

EntityBuilder.java <- จำเป็นต้องใช้ mide

public class EntityBuilder() {
    public static PersonEntity buildPersonEntity(PersonDTO person) {
        return new PersonEntity(person.getId(). person.getAddress());
    }
}

4
ขอบคุณสำหรับคำตอบที่นี่ฉันไม่ต้องการรูปแบบ DTO ความต้องการของฉันคือไม่ซ่อนรายละเอียดคำอธิบายประกอบจากลูกค้า ดังนั้นฉันไม่จำเป็นต้องสร้าง POJO อีกหนึ่งรายการในแอปของฉัน ความต้องการของฉันคือการส่งชุดผลลัพธ์เป็น qa pojo ที่ไม่ใช่เอนทิตี JAVA แต่เป็นคลาส POJO แบบง่ายที่มีฟิลด์เดียวกันกับคอลัมน์ชุดผลลัพธ์
Gunjan Shah
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.