java - कस्टम वर्ग के लिए हाइबरनेट क्वेरी के मानचित्रण के परिणाम?




spring hibernate (6)

एक प्रश्न के बाद मैंने कल पोस्ट किया: कस्टम हाइबरनेट क्वेरी से POJO वर्ग को कैसे आबाद किया जाए?

क्या कोई मुझे एक उदाहरण दिखा सकता है कि हाइबरनेट में निम्न एसक्यूएल कोड कैसे करें, और परिणाम सही तरीके से प्राप्त करें?

एसक्यूएल:

select firstName, lastName
from Employee

यदि मैं हाइबरनेट में संभव हो तो क्या करना चाहूंगा, परिणाम को अपने स्वयं के आधार वर्ग में रखना है:

class Results {
    private firstName;
    private lastName;
    // getters and setters
}

मेरा मानना ​​है कि यह JPA ( EntityManager का उपयोग करके) में संभव है, लेकिन मुझे यह पता नहीं चला है कि इसे Hibernate ( SessionFactory और Session का उपयोग करके) में कैसे करना है।

मैं हाइबरनेट को बेहतर ढंग से सीखने की कोशिश कर रहा हूं, और यहां तक ​​कि यह "सरल" क्वेरी यह जानने के लिए भ्रमित करने वाली साबित हो रही है कि हाइबरनेट किस रूप में परिणाम लौटाता है, और परिणामों को अपने (आधार) वर्ग में कैसे मैप किया जाए। तो DAO दिनचर्या के अंत में, मैं करूँगा:

List<Results> list = query.list();

Results List (मेरा आधार वर्ग) वापस करना।


YMMV लेकिन मैंने पाया है कि मुख्य कारक यह है कि आप एसक्यूएल "एएस" कीवर्ड के साथ अपने चयन खंड में हर क्षेत्र को उर्फ ​​करना सुनिश्चित करें। मुझे कभी भी उपनामों के आसपास के उद्धरणों का उपयोग नहीं करना पड़ा है। इसके अलावा, आपके चयनित खंड में आपके डेटाबेस में वास्तविक स्तंभों के मामले और विराम चिह्न का उपयोग किया जाता है और उपनामों में आपके POJO में फ़ील्ड के मामले का उपयोग किया जाता है। यह मेरे लिए हाइबरनेट 4 और 5 में काम कर चुका है।

@Autowired
private SessionFactory sessionFactory;

...

String sqlQuery = "SELECT firstName AS firstName," +
        "lastName AS lastName from Employee";

List<Results> employeeList = sessionFactory
        .getCurrentSession()
        .createSQLQuery(sqlQuery)
        .setResultTransformer(Transformers.aliasToBean(Results.class))
        .list();

यदि आपके पास एक से अधिक टेबल हैं, तो आप एसक्यूएल में टेबल एलियासेस का भी उपयोग कर सकते हैं। "विभाग" नाम की एक अतिरिक्त तालिका के साथ यह विरोधाभासी उदाहरण POJO फ़ील्ड नामों में ऊंट मामले के साथ डेटाबेस फ़ील्ड नामों में अधिक पारंपरिक लोअर केस और अंडरस्कोर का उपयोग करता है।

String sqlQuery = "SELECT e.first_name AS firstName, " +
        "e.last_name AS lastName, d.name as departmentName" +
        "from Employee e, Department d" +
        "WHERE e.department_id - d.id";

List<Results> employeeList = sessionFactory
        .getCurrentSession()
        .createSQLQuery(sqlQuery)
        .setResultTransformer(Transformers.aliasToBean(Results.class))
        .list();

आपको एक कंस्ट्रक्टर का उपयोग करना होगा और एचक्यूएल में नए का उपयोग करना होगा। मैं आपको इस प्रश्न से लिया गया कोड उदाहरण देता हूं: हाइबरनेट HQL createQuery () सूची () सीधे मॉडल के लिए डाली जाती है

class Result {
    private firstName;
    private lastName;
    public Result (String firstName, String lastName){
      this.firstName = firstName;
      this.lastName = lastName;
   }
}

तो आपका hql

select new com.yourpackage.Result(employee.firstName,employee.lastName) 
from Employee  

और आपका जावा (हाइबरनेट का उपयोग करके)

List<Result> results = session.createQuery("select new com.yourpackage.Result(employee.firstName,employee.lastName) from Employee").list();

यदि आपके पास एक देशी क्वेरी है, तो यहां सभी उत्तर हाइबरनेट के नए संस्करणों के लिए पदावनत विधियों का उपयोग करते हैं, इसलिए यदि आप 5.1+ उपयोग कर रहे हैं तो यह जाने का तरीका है:

// Note this is a org.hibernate.query.NativeQuery NOT Query.
NativeQuery query = getCurrentSession()
                .createNativeQuery(
                        "SELECT {y.*} , {x.*} from TableY y left join TableX x on x.id = y.id");


// This maps the results to entities. 
query.addEntity("x", TableXEntity.class);
query.addEntity("y", TableYEntity.class);

query.list()

लेखन (हाइबरनेट के साथ काम करने वाले इस प्रकार की चुनौतियाँ मौजूद हैं)

  1. कस्टम क्वेरी
  2. वैकल्पिक पैरामीटर के साथ कस्टम क्वेरी
  3. कस्टम वर्ग के लिए हाइबरनेट कस्टम क्वेरी परिणाम मैपिंग।

मैं कस्टम EntityRepository इंटरफ़ेस के बारे में नहीं कह रहा हूँ जो स्प्रिंगबूट पर JpaRepository का विस्तार करता है जिसे आप @Query के साथ custom Query लिख सकते हैं - यहाँ आप वैकल्पिक params के साथ क्वेरी नहीं लिख सकते हैं जैसे कि param null है इसे query string में appeart नहीं करें। और आप हाइबरनेट के मानदंड एपीआई का उपयोग कर सकते हैं, लेकिन प्रदर्शन के मुद्दे के कारण उनके प्रलेखन में इसकी सिफारिश नहीं की गई है ...

लेकिन मौजूद सरल और त्रुटि प्रवण और प्रदर्शन अच्छा तरीका ...

अपनी खुद की QueryService क्लास लिखें जो तरीके हैं जो स्ट्रिंग (पहली और दूसरी समस्या के लिए उत्तर) sql प्राप्त करेंगे और कस्टम क्लास (तीसरी समस्या) के लिए परिणाम देगा, यह किसी भी एसोसिएशन के साथ है @OneToMany, @ManyToOne ....

@Service
@Transactional
public class HibernateQueryService {

    private final Logger log = LoggerFactory.getLogger(HibernateQueryService.class);
    private JpaContext jpaContext;

    public HibernateQueryService(JpaContext jpaContext) {
        this.jpaContext = jpaContext;
    }

    public List executeJPANativeQuery(String sql, Class entity){
        log.debug("JPANativeQuery executing: "+sql);
        EntityManager entityManager = jpaContext.getEntityManagerByManagedType(Article.class);
        return entityManager.createNativeQuery(sql, entity).getResultList();
    }

/**
 * as annotation @Query -> we can construct here hibernate dialect 
 * supported query and fetch any type of data
 * with any association @OneToMany and @ManyToOne.....
 */
    public List executeHibernateQuery(String sql, Class entity){
        log.debug("HibernateNativeQuery executing: "+sql);
        Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
        return session.createQuery(sql, entity).getResultList();
    }

public <T> List<T> executeGenericHibernateQuery(String sql, Class<T> entity){
    log.debug("HibernateNativeQuery executing: "+sql);
    Session session = jpaContext.getEntityManagerByManagedType(Article.class).unwrap(Session.class);
    return session.createQuery(sql, entity).getResultList();
}


}

केस का उपयोग करें - आप क्वेरी परम के बारे में किसी भी प्रकार की स्थिति लिख सकते हैं

 @Transactional(readOnly = true)
    public List<ArticleDTO> findWithHibernateWay(SearchFiltersVM filter){

        Long[] stores = filter.getStores();
        Long[] categories = filter.getCategories();
        Long[] brands = filter.getBrands();
        Long[] articles = filter.getArticles();
        Long[] colors = filter.getColors();

        String query = "select article from Article article " +
            "left join fetch article.attributeOptions " +
            "left join fetch article.brand " +
            "left join fetch article.stocks stock " +
            "left join fetch stock.color " +
            "left join fetch stock.images ";

boolean isFirst = true;

        if(!isArrayEmptyOrNull(stores)){
            query += isFirst ? "where " : "and ";
            query += "stock.store.id in ("+ Arrays.stream(stores).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
            isFirst = false;
        }

        if(!isArrayEmptyOrNull(brands)){
            query += isFirst ? "where " : "and ";
            query += "article.brand.id in ("+ Arrays.stream(brands).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
            isFirst = false;
        }

        if(!isArrayEmptyOrNull(articles)){
            query += isFirst ? "where " : "and ";
            query += "article.id in ("+ Arrays.stream(articles).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
            isFirst = false;
        }

        if(!isArrayEmptyOrNull(colors)){
            query += isFirst ? "where " : "and ";
            query += "stock.color.id in ("+ Arrays.stream(colors).map(store -> store.toString()).collect(Collectors.joining(", "))+") ";
        }

        List<Article> articles = hibernateQueryService.executeHibernateQuery(query, Article.class);


      /**
        *  MapStruct [http://mapstruct.org/][1]
        */
        return articles.stream().map(articleMapper::toDto).collect(Collectors.toList());

    }

java.lang.ClassCastException: "CustomClass" cannot be cast to java.util.Map.

यह समस्या तब प्रकट होती है जब SQL क्वेरी में निर्दिष्ट कॉलम मैपिंग क्लास के कॉलम से मेल नहीं खाते।

इसकी वजह यह हो सकती है:

  • स्तंभ नाम या के गैर-मिलान आवरण

  • स्तंभ नाम मेल नहीं खा रहे हैं या

  • कॉलम क्वेरी में मौजूद है लेकिन कक्षा में गायब है।


select firstName, lastName from Employee

query.setResultTransformer(Transformers.aliasToBean(MyResults.class));

आप एक अपवाद के कारण हाइबरनेट 5 और हाइबरनेट 4 (कम से कम हाइबरनेट 4.3.6.Final) के साथ उपरोक्त कोड का उपयोग नहीं कर सकते हैं

java.lang.ClassCastException: com.github.fluent.hibernate.request.persistent.UserDto cannot be cast to java.util.Map
    at org.hibernate.property.access.internal.PropertyAccessMapImpl$SetterImpl.set(PropertyAccessMapImpl.java:102)

समस्या यह है कि हाइबरनेट कॉलम नामों के लिए उपनामों को ऊपरी मामले में परिवर्तित करता है - FIRSTNAME firstName FIRSTNAME बन जाता है। और यह ऐसी रणनीतियों का उपयोग करके DTO में getFIRSTNAME() , और सेटर setFIRSTNAME() नाम के साथ एक गेटर खोजने की कोशिश करता है।

    PropertyAccessStrategyChainedImpl propertyAccessStrategy = new PropertyAccessStrategyChainedImpl(
            PropertyAccessStrategyBasicImpl.INSTANCE,
            PropertyAccessStrategyFieldImpl.INSTANCE,
            PropertyAccessStrategyMapImpl.INSTANCE
    );

हाइबरनेट के मद्देनजर केवल PropertyAccessStrategyMapImpl.INSTANCE सूट, अच्छी तरह से। तो इसके बाद यह रूपांतरण (Map)MyResults करने की कोशिश करता है।

public void set(Object target, Object value, SessionFactoryImplementor factory) {
    ( (Map) target ).put( propertyName, value );
}

पता नहीं, यह एक बग या सुविधा है।

कैसे हल करें

उद्धरण के साथ उपनाम का उपयोग करना

public class Results {

    private String firstName;

    private String lastName;

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

String sql = "select firstName as \"firstName\", 
    lastName as \"lastName\" from Employee";

List<Results> employees = session.createSQLQuery(sql).setResultTransformer(
    Transformers.aliasToBean(Results.class)).list(); 

एक कस्टम परिणाम ट्रांसफार्मर का उपयोग करना

समस्या को हल करने का दूसरा तरीका - परिणाम ट्रांसफार्मर का उपयोग करना जो विधि के नाम के मामले को अनदेखा करता है ( getFirstName() को getFIRSTNAME() रूप में getFIRSTNAME() )। आप अपना खुद का लिख ​​सकते हैं या FluentHibernateResultTransformer उपयोग कर सकते हैं। FluentHibernateResultTransformer । आपको उद्धरण और उपनाम का उपयोग करने की आवश्यकता नहीं होगी (यदि आपके पास डीटीओ नामों के बराबर स्तंभ नाम हैं)।

बस परियोजना पृष्ठ से पुस्तकालय डाउनलोड करें (इसे अतिरिक्त जार की आवश्यकता नहीं है): fluent-hibernate

String sql = "select firstName, lastName from Employee";
List<Results> employees = session.createSQLQuery(sql)
        .setResultTransformer(new FluentHibernateResultTransformer(Results.class))
        .list();

इस ट्रांसफार्मर का उपयोग नेस्टेड अनुमानों के लिए भी किया जा सकता है: हाइबरनेट का उपयोग करके सेट किए गए फ्लैट परिणाम को कैसे बदलना है





hibernate