SqlResultSetMapping в класс POJO из выброса NamedNativeQuery ‘не удалось найти соответствующий конструктор’
Я сделал @NamedNativeQuery и прикрепил его к объекту «Доктор», к тому же объекту, к которому я прикрепил @SqlResultSetMapping , который берет столбцы результата запроса и сопоставляет их с конструктором специально созданного класса POJO. . Этот запрос также связан с методом JPA, который находится в репозитории того же объекта.
Однако я продолжаю получать сообщение об ошибке, что соответствующий конструктор не может быть найден, как если бы конструкторы @SqlResultSetMapping или POJO не синхронизированы. (трассировка стека внизу)
Моя сущность, @NamedNativeQuery и @SqlResultSetMapping :
Я попробовал выполнить запрос непосредственно в БД, и он дал ожидаемый результат, поэтому я просто пишу предложение select
@Entity @NamedNativeQuery( name = "Doctor.findFreeExaminationTimes", // name of the JPA method in entity's repository (definition below) query = "SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " + ". ", resultSetMapping = "freeTimesByDoctorId" // name of the mapping below ) @SqlResultSetMapping( name = "freeTimesByDoctorId", // result set mapping name classes = @ConstructorResult( targetClass = DoctorAvailabilityResponse.class, // my POJO class (definition below) columns = < // order and types are the same as in the select clause above and the POJO constructor below @ColumnResult(name = "onDate", type = java.sql.Date.class), @ColumnResult(name = "fromTime", type = java.sql.Time.class), @ColumnResult(name = "toTime",type = java.sql.Time.class) >) ) public class Doctor extends User
Класс POJO, который я упоминаю в @ConstructorResult в разделе ‘targetClass’, имеет конструктор с точным порядком, количеством и типом аргументов, указанных в ‘столбцах’.
Мой класс POJO, который должен быть сопоставлен с результатом запроса:
public class DoctorAvailabilityResponse < final private java.sql.Date onDate; final private java.sql.Time fromTime; final private java.sql.Time toTime; public DoctorAvailabilityResponse(java.sql.Date onDate, java.sql.Time fromTime, java.sql.Time toTime) < this.onDate = onDate; this.fromTime = fromTime; this.toTime = toTime; >// getters >
@RepositoryRestResource public interface DoctorRepository extends UserRepository < // JPA method mapped to the named native query above ListfindFreeExaminationTimes(@Param("doctorId") Long doctorId); >
Однако при тестировании этого метода JPA я получаю исключение с сообщением «не удалось найти соответствующий конструктор».
@SpringBootTest public class DoctorTests < @Autowired private DoctorRepository doctorRepository; private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); @Test public void shouldReturnDoctorAvailability() < // Exception thrown here ListfreeTimes = doctorRepository.findFreeExaminationTimes(4L); LOGGER.info(freeTimes.toString()); > >
Я не могу понять, почему это происходит. Есть ли способ вручную сопоставить этот набор результатов с POJO при сохранении метода репозитория JPA?
org.springframework.dao.InvalidDataAccessApiUsageException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse; nested exception is java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374) at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:256) at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528) . . Caused by: java.lang.IllegalArgumentException: Could not locate appropriate constructor on class : com.example.isaproj.isa_projekat_2019.Model.DTO.DoctorAvailabilityResponse at org.hibernate.loader.custom.ConstructorResultColumnProcessor.resolveConstructor(ConstructorResultColumnProcessor.java:92) at org.hibernate.loader.custom.ConstructorResultColumnProcessor.performDiscovery(ConstructorResultColumnProcessor.java:45) at org.hibernate.loader.custom.CustomLoader.autoDiscoverTypes(CustomLoader.java:494) at org.hibernate.loader.Loader.processResultSet(Loader.java:2333) at org.hibernate.loader.Loader.getResultSet(Loader.java:2289) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2045) at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2007) at org.hibernate.loader.Loader.doQuery(Loader.java:953) at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354) at org.hibernate.loader.Loader.doList(Loader.java:2810) at org.hibernate.loader.Loader.doList(Loader.java:2792) at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2624) at org.hibernate.loader.Loader.list(Loader.java:2619) at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338) at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2137) at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1134) at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:173) at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1526) at org.hibernate.query.Query.getResultList(Query.java:165) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:564) at org.springframework.orm.jpa.SharedEntityManagerCreator$DeferredQueryInvocationHandler.invoke(SharedEntityManagerCreator.java:409) at com.sun.proxy.$Proxy212.getResultList(Unknown Source) at org.springframework.data.jpa.repository.query.JpaQueryExecution$CollectionExecution.doExecute(JpaQueryExecution.java:126) at org.springframework.data.jpa.repository.query.JpaQueryExecution.execute(JpaQueryExecution.java:88) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.doExecute(AbstractJpaQuery.java:154) at org.springframework.data.jpa.repository.query.AbstractJpaQuery.execute(AbstractJpaQuery.java:142) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.doInvoke(RepositoryFactorySupport.java:618) at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:605) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:353) at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99) at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139) . 73 more
Проверка на вменяемость и альтернативный подход
Чтобы проверить работоспособность, я удалил @SqlResultSetMapping , и в этом случае запрос должен возвращать список значений ‘Object []’, а затем я протестировал каждое отдельное значение в этом массиве, чтобы проверить его тип, он показал мне, что типы — это то, что я предполагал, что они были java.sql.Date и java.sql.Time дважды, и все три из них были в ожидаемом порядке (Дата, Время, Время), что соответствует порядок параметров конструктора моего класса POJO.
Моя сущность и namedNativeQuery:
@Entity @NamedNativeQuery( name = "Doctor.findFreeExaminationTimes", query = "SELECT on_date AS onDate, LAG(to_time, 1, '00:00') OVER mojWindow AS fromTime, from_time AS toTime " + ". " ) public class Doctor extends User
Мой репозиторий с новым типом возврата:
@RepositoryRestResource public interface DoctorRepository extends UserRepository < List
@SpringBootTest public class DoctorTests < @Autowired private DoctorRepository doctorRepository; private final Logger LOGGER = LoggerFactory.getLogger(this.getClass()); @Test public void shouldReturnDoctorAvailability() < // Exception thrown here ListfreeTimes = doctorRepository.findFreeExaminationTimes(4L); freeTimes.stream().forEach((ft) -> < // Values are in expected order and of expected types String classNameOnDate = ft[0].getClass().toString(); // java.sql.Date String classNameFromTime = ft[1].getClass().toString(); // java.sql.Time String classNameToTime = ft[1].getClass().toString(); // java.sql.Time // I suppose the mapping mechanism is supposed to do something like this, but fails for some reason DoctorAvailabilityResponse dar = new DoctorAvailabilityResponse((Date)ft[0], (Time)ft[1], (Time)ft[2]); >); LOGGER.info(freeTimes.toString()); > >
Выполнение этого теста отлично работает, что якобы показывает, что проблема в @SqlResultSetMapping или в классе POJO.
Буду признателен за любые отзывы. Спасибо!
РЕШЕНИЕ
Мне пришлось изменить типы в @SqlResultSetMapping и в конструкторе моего класса POJO.
@SqlResultSetMapping( name = "freeTimesByDoctorId", classes = @ConstructorResult( targetClass = DoctorAvailabilityResponse.class, columns = < @ColumnResult(name = "onDate", type = String.class), @ColumnResult(name = "fromTime", type = String.class), @ColumnResult(name = "toTime",type = String.class) >) )
Изменен конструктор класса POJO
public DoctorAvailabilityResponse(String onDate, String fromTime, String toTime)
Однако это само по себе не решило мою проблему, поскольку я получил исключение гибернации, как упомянуто и решено в этом SO-вопросе. В соответствии с этим ответом я также изменил свой репозиторий и добавил дополнительную аннотацию.
@RepositoryRestResource public interface DoctorRepository extends UserRepository < @Query(nativeQuery = true) // This is added ListfindFreeExaminationTimes(@Param("doctorId") Long doctorId); >
Теперь все работает, хотя остается вопрос, почему @SqlResultSetMapping не преобразовал типы java.sql.* в конструктор в первую очередь.
1 ответ
@ConstructorResult не очень хорошо работает с типом java.sql.Date.class или java.sql.Time.class . Способ решить вашу проблему — использовать вместо этого String.class , а затем преобразовать строковые значения в дату / время в конструкторе DoctorAvailabilityResponse .
Типы java.sql.* не поддерживаются с отображением конструктора (@ConstructorResult), но не уверен, почему. Но если вместо этого вы используете сопоставление сущностей с @EntityResult/@FieldResult для сопоставления полей класса @Entity, оно должно работать с этими типами.
Java – Unable to locate appropriate constructor on class JPA
I do not understand why it is complaining about it is expecting all strings when it is recieving all strings.
I am running the query in intellij’s persistence tool.
@Query("SELECT NEW com.classes.applicant.ApplicantEntry(app.indSsn, app.indivName, " +"app.indAddrLocTx,app.indAddrCityNm,app.indAdrStateAb,app.indAddrZipCd, app.phoneNr,app.workPhoneNr) " +"FROM TApplicant app " +"WHERE app.indSsn = :ssn ") ApplicantEntry getApplicantEntry(@Param("ssn") String ssn);
Best Solution
Note for anyone using Lombok, the physical ordering of the fields in your class determines the order of your constructor parameters. The physical ordering of your class fields must match the ordering of the SELECT clause.
@AllArgsConstructor public class Thing < private String name; private Date birthday; >// not the same as. @AllArgsConstructor public class Thing
Related Solutions
Java – How to test a class that has private methods, fields or inner classes
Update:
Some 10 years later perhaps the best way to test a private method, or any inaccessible member, is via @Jailbreak from the Manifold framework.
@Jailbreak Foo foo = new Foo(); // Direct, *type-safe* access to *all* foo's members foo.privateMethod(x, y, z); foo.privateField = value;
This way your code remains type-safe and readable. No design compromises, no overexposing methods and fields for the sake of tests.
If you have somewhat of a legacy Java application, and you’re not allowed to change the visibility of your methods, the best way to test private methods is to use reflection.
Internally we’re using helpers to get/set private and private static variables as well as invoke private and private static methods. The following patterns will let you do pretty much anything related to the private methods and fields. Of course, you can’t change private static final variables through reflection.
Method method = TargetClass.getDeclaredMethod(methodName, argClasses); method.setAccessible(true); return method.invoke(targetObject, argObjects);
Field field = TargetClass.getDeclaredField(fieldName); field.setAccessible(true); field.set(object, value);
Notes:
1. TargetClass.getDeclaredMethod(methodName, argClasses) lets you look into private methods. The same thing applies for getDeclaredField .
2. The setAccessible(true) is required to play around with privates.
Java inner class and static nested class
Nested classes are divided into two categories: static and non-static. Nested classes that are declared static are simply called static nested classes. Non-static nested classes are called inner classes.
Static nested classes are accessed using the enclosing class name:
OuterClass.StaticNestedClass
For example, to create an object for the static nested class, use this syntax:
OuterClass.StaticNestedClass nestedObject = new OuterClass.StaticNestedClass();
Objects that are instances of an inner class exist within an instance of the outer class. Consider the following classes:
An instance of InnerClass can exist only within an instance of OuterClass and has direct access to the methods and fields of its enclosing instance.
To instantiate an inner class, you must first instantiate the outer class. Then, create the inner object within the outer object with this syntax:
OuterClass outerObject = new OuterClass() OuterClass.InnerClass innerObject = outerObject.new InnerClass();
For completeness note that there is also such a thing as an inner class without an enclosing instance:
class A < int t() < return 1; >static A a = new A() < int t() < return 2; >>; >
Here, new A() < . >is an inner class defined in a static context and does not have an enclosing instance.