37

I am trying to map native SQL result to my POJO. Here is the configuration. I am using spring.

<bean id="ls360Emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
    <property name="dataSource" ref="ls360DataSource" />
    <property name="jpaVendorAdapter" ref="vendorAdaptor" />         
    <property name="packagesToScan" value="abc.xyz"/>
    <property name="jpaProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</prop>
            <prop key="hibernate.max_fetch_depth">3</prop>
            <prop key="hibernate.jdbc.fetch_size">50</prop>
            <prop key="hibernate.jdbc.batch_size">10</prop>
            <prop key="hibernate.show_sql">true</prop>              
        </props>        
    </property>
</bean> 

Here is my Class

@SqlResultSetMapping(
    name="courseCompletionMapping", 
    classes = {
        @ConstructorResult(targetClass = CourseCompletion.class,
            columns={
                @ColumnResult(name = "StoreId", type = String.class),
                @ColumnResult(name = "ProductId", type = String.class),
                @ColumnResult(name = "UserName", type = String.class),
                @ColumnResult(name = "Score", type = Integer.class),
                @ColumnResult(name = "CompletionDate", type = Date.class)
             }
        )
    }
) 
@Entity
public class CourseCompletion {
    private String storeId;

    @Id
    private String productId;
    private String userName;
    private int score;
    private Date completionDate;

    public CourseCompletion() {
    }

    public CourseCompletion(String storeId, String productId, String userName, int score, Date completionDate) {
        this.storeId = storeId;
        this.productId = productId;
        this.userName = userName;
        this.score = score;
        this.completionDate = completionDate;
    }

    // getters and setters

Here how i am calling it

    Properties coursePropertiesFile = SpringUtil.loadPropertiesFileFromClassPath("course.properties");
    String queryString = coursePropertiesFile.getProperty("course.completion.sql");

    long distributorId = 1;
    String fromDate = "2009-09-22 00:00:00";
    String toDate = "2014-04-11 23:59:59";

     Query query = em.createNativeQuery(queryString, "courseCompletionMapping");

     //Query query = em.createNamedQuery("findAllEmployeeDetails");
     query.setParameter("distributorId", distributorId);
     query.setParameter("fromDate", fromDate);
     query.setParameter("toDate", toDate);

     @SuppressWarnings("unchecked")
     List<CourseCompletion> courseCompletionList = query.getResultList();

But when it comes to line

List<CourseCompletion> courseCompletionList = query.getResultList();

I get an error that

Could not locate appropriate constructor on class : mypackage.CourseCompletion

Here is the query that i am trying

select d.DISTRIBUTORCODE AS StoreId, u.USERGUID AS ProductId, u.UserName,
    lcs.HIGHESTPOSTTESTSCORE AS Score, lcs.CompletionDate 
from VU360User u 
inner join learner l on u.ID = l.VU360USER_ID 
inner join LEARNERENROLLMENT le on le.LEARNER_ID = l.ID 
inner join LEARNERCOURSESTATISTICS lcs on lcs.LEARNERENROLLMENT_ID = le.ID 
inner join customer c on c.ID = l.CUSTOMER_ID 
inner join DISTRIBUTOR d on d.ID = c.DISTRIBUTOR_ID 
where d.ID = :distributorId 
and lcs.COMPLETIONDATE is not null 
and (lcs.COMPLETIONDATE between :fromDate and :toDate) 
and lcs.COMPLETED = 1

Why i am getting this error ?

Thanks

Basit
  • 8,426
  • 46
  • 116
  • 196

4 Answers4

101

This exception happens because JPA doesn't change column types returned from the database for native queries. Because of this, you have type mismatch. I'm not sure about which column causes this problem in your case (this depends on DBMS you use), but I would suspect you have BigInteger in the result set instead of Integer for score column. To be 100% sure, add a breakpoint to ConstructorResultColumnProcessor.resolveConstructor(Class targetClass, List<Type> types) and investigate. After you find a mismatch, change field type in your mapping class.

Another solution will be not to use @SqlResultSetMapping at all. As your CourseCompletion class is a managed entity, you should be able to map native query to it directly. See this question for more information.

Community
  • 1
  • 1
Zmicier Zaleznicenka
  • 1,894
  • 1
  • 19
  • 22
  • 12
    Thanks :). I have solved the problem. I debug the program and then found the score column is of double type. Then i simply did `@ColumnResult(name = "Score", type=Double.class)` and change score type to `Double`. – Basit Jul 17 '14 at 10:55
  • 4
    Thank a lot :) . It helped to debug. In my case issue was due to java.sql.Datetime. Hibernate is converting datetime from microsoft sql column to java.util.Datetime. After I change column type to java.util.Datetime, it got resolved. – Mahesh Vemula Dec 28 '17 at 18:23
  • 6
    Thank a lot :) . It helped to debug. In my case issue was due to java.sql.Timestamp. Hibernate is converting datetime from microsoft sql column to java.util.Date. After I change column type to java.util.Date, it got resolved. – whoami Jan 11 '18 at 05:32
  • This indeed is the correct explanation to the problem. I had similar issue where Eclipse won't let me look at the source code of the above mentioned class. Problem was MySQL returned timestamp, hibernate interpreted as Timestamp, but had to use Date in the code to make it work. Using Hibernate 5.4.0.Final – Sategroup Nov 15 '19 at 04:28
  • 1
    I need to change my field data type from "java.sql.Timestamp" to "java.util.Date" and it works. –  Jan 13 '21 at 18:01
  • Good hint. Helped me out realizing that my constructor arguments were out of order... – jediwompa Feb 19 '21 at 16:48
  • How do you inspect database result object before its mapped? (Intellij) – Radika Moonesinghe Apr 20 '22 at 12:23
  • "JPA doesn't change column types returned from the database for native queries" - SIGH! This is precisely when the `type` property of @ColumnResult should be used. – Dave Aug 19 '22 at 01:50
  • I had a problem with java.sql.Timestamp. The issue was resolved by setting a column of type java.time.LocalDateTime; – vi0 Jan 08 '23 at 02:58
  • @RadikaMoonesinghe In the request, you can remove the parameter responsible for mapping so that there is no mapping. And see the result of the query in the debugger. – vi0 Jan 08 '23 at 03:04
  • In hibernate 6.x.y, put a breakpoint in StandardJdbcValuesMapping#resolveAssemblers – shankulk Feb 24 '23 at 02:16
  • Thanks for pointing that out. Adding type for the @ColumnResult fixed it for me. – Tora Tora Tora Apr 19 '23 at 18:28
  • Into the `if ( ! areAssignmentCompatible( argumentTypes[i], types.get( i ).getReturnedClass() ) ) {` code they could have added a logger.warn, wouldnt have that much a cost but a huge benefit for us! or throw an exception with that comment... – Pipo May 03 '23 at 18:17
2

Adding a break point to the ConstructorResultColumnProcessor.resolveConstructor worked for me. My query had a rowNum selection and it's mapped as BigDecimal type. I was having the constructor with a BigInteger.

Worthwelle
  • 1,244
  • 1
  • 16
  • 19
sevvalai
  • 99
  • 2
  • The way following, by debug, the method "resolveConstructor" is ok and is possible to verify the correct class type BUT, in my case with boot2, jpa2 and hb all updates to the last version the problem was related to a bad sequence arguments declared into the Dto constructor class (wrote automatic by Eclipse). Hibernate compares the arguments by two indexed array, if the sequence isn't correct and exception will be raised. snipped: for( int i = 0; i < argumentTypes.length; i++ ) { if (!areAssignmentCompatible( argumentTypes[i], types.get(i).getReturnedClass() ) ) { /* failed */ }} – CptUnlucky Jan 08 '21 at 19:01
0

Thanks. Added debug point to ConstructorResultColumnProcessor.resolveConstructor method. Hibernate result type was BigIntegerType and DTO object return type was Integer. so allMatched flag was false. That's why It throws "Could not locate appropriate constructor on class" Thanks Ram

Ramesh
  • 11
0

This happened to me because for some reason I specified the constructor as private. The ConstructorResultColumnProcessor.resolveConstructor breakpoint helped me because I realized it never found my constructor.

drussey
  • 665
  • 4
  • 9