Exception when using a #ResultSetMapping on a native SQL query with a Spring Data JPA Repository? - jpa

I'm currently building a native SQL query with Spring Data JPA that uses a #SqlResultSetMapping. The query looks filters the rows based on certain parameters and then returns all of the columns of an entity table plus additionally it calculates another column on the fly. Something like this:
#Entity
#NamedNativeQuery(
name = "Entity.searchBySearchParams",
resultSetMapping = "SearchResultMapping",
query = "select entity.*, (*calculation*) as anotherField from ..."
)
#SqlResultSetMapping(
name = "SearchResultMapping",
entities = #EntityResult(entityClass = Entity.class),
columns = #ColumnResult(name = "anotherField", type = double.class)
)
public class Entity { ... }
And then I'm trying to call this native, named query from my repository (which extends JpaRepository<T, Long>) like that:
#Query(nativeQuery = true, name = "Entity.searchBySearchParams")
List<Object[]> searchBySearchParams( ... lots of params ... );
Where I'm expecting searchBySearchParams(...)[0] to be of type Entity and searchBySearchParams(...)[1] of type Double.
Unfortunately I'm getting this exception when trying to call the above repository method:
org.springframework.dao.InvalidDataAccessApiUsageException: Cannot create TypedQuery for query with more than one return; nested exception is java.lang.IllegalArgumentException: Cannot create TypedQuery for query with more than one return
at org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExceptionIfPossible(EntityManagerFactoryUtils.java:374)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:257)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)
....
Which is weird since calling the query and applying my result set mapping directly by invoking the EntityManage#createNamedQuery method worked perfectly fine.
Am I missing something or should I post this to the Spring Data Jira? Neither JPA's nor Spring's documentation provided information for my case ...
More Background Info:
Actually the additional column anotherField is a transitive property on my Entity. I haven't found a way of mapping that column to the transitive property, which is why my repository method currently returns Object[].

Related

Spring JPA native query to '#IdClass' annotated table and getting "No Dialect mapping for JDBC type: 1111" [duplicate]

I'm working on a Spring JPA Application, using MySQL as database. I ensured that all spring-jpa libraries, hibernate and mysql-connector-java is loaded.
I'm running a mysql 5 instance. Here is a excerpt of my application.properties file:
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.MySQL5Dialect
spring.datasource.url=jdbc:mysql://localhost/mydatabase
spring.datasource.username=myuser
spring.datasource.password=SUPERSECRET
spring.datasource.driverClassName=com.mysql.jdbc.Driver
When executing an integration test, spring startsup properly but fails on creating the hibernate SessionFactory, with the exception:
org.hibernate.MappingException: No Dialect mapping for JDBC type: 1111
I think my dialects should be Mysql5Dialect, I also tried the one explicitly stating InnoDB, and the two dialect options which don't indicate the version 5. But I always end up with the same 'No Dialect mapping for JDBC type: 1111' message.
My application.properties file resides in the test/resources source folder. It is recognized by the JUnit Test runner (I previously got an exception because of an typo in it).
Are the properties I'm setting wrong? I couldn't find some official documentation on these property names but found a hint in this stackoverflow answer: https://stackoverflow.com/a/25941616/1735497
Looking forward for your answers, thanks!
BTW The application is already using spring boot.
I got the same error because my query returned a UUID column. To fix that I returned the UUID column as varchar type through the query like "cast(columnName as varchar)", then it worked.
Example:
public interface StudRepository extends JpaRepository<Mark, UUID> {
#Modifying
#Query(value = "SELECT Cast(stuid as varchar) id, SUM(marks) as marks FROM studs where group by stuid", nativeQuery = true)
List<Student> findMarkGroupByStuid();
public static interface Student(){
private String getId();
private String getMarks();
}
}
Here the answer based on the comment from SubOptimal:
The error message actually says that one column type cannot be mapped to a database type by hibernate.
In my case it was the java.util.UUID type I use as primary key in some of my entities. Just apply the annotation #Type(type="uuid-char") (for postgres #Type(type="pg-uuid"))
There is also another common use-case throwing this exception. Calling function which returns void. For more info and solution go here.
I got the same error, the problem here is UUID stored in DB is not converting to object.
I tried applying these annotations #Type(type="uuid-char") (for postgres #Type(type="pg-uuid") but it didn't work for me.
This worked for me. Suppose you want id and name from a table with a native query in JPA. Create one entity class like 'User' with fields id and name and then try converting object[] to entity we want. Here this matched data is list of array of object we are getting from query.
#Query( value = "SELECT CAST(id as varchar) id, name from users ", nativeQuery = true)
public List<Object[]> search();
public class User{
private UUID id;
private String name;
}
List<User> userList=new ArrayList<>();
for(Object[] data:matchedData){
userList.add(new User(UUID.fromString(String.valueOf(data[0])),
String.valueOf(data[1])));
}
Suppose this is the entity we have
Please Check if some Column return many have unknow Type in Query .
eg : '1' as column_name can have type unknown
and 1 as column_name is Integer is correct One .
This thing worked for me.
Finding the column that triggered the issue
First, you didn't provide the entity mapping so that we could tell what column generated this problem. For instance, it could be a UUID or a JSON column.
Now, you are using a very old Hibernate Dialect. The MySQL5Dialect is meant for MySQL 5. Most likely you are using a newer MySQL version.
So, try to use the MySQL8Dialect instead:
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
Adding non-standard types
In case you got the issue because you are using a JSON column type, try to provide a custom Hibernate Dialect that supports the non-standard Type:
public class MySQL8JsonDialect
extends MySQL8Dialect{
public MySQL8JsonDialect() {
super();
this.registerHibernateType(
Types.OTHER, JsonStringType.class.getName()
);
}
}
Ans use the custom Hibernate Dialect:
<property
name="hibernate.dialect"
value="com.vladmihalcea.book.hpjp.hibernate.type.json.MySQL8JsonDialect"
/>
If you get this exception when executing SQL native queries, then you need to pass the type via addScalar:
JsonNode properties = (JsonNode) entityManager
.createNativeQuery(
"SELECT properties " +
"FROM book " +
"WHERE isbn = :isbn")
.setParameter("isbn", "978-9730228236")
.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("properties", JsonStringType.INSTANCE)
.getSingleResult();
assertEquals(
"High-Performance Java Persistence",
properties.get("title").asText()
);
Sometimes when you call sql procedure/function it might be required to return something. You can try returning void: RETURN; or string (this one worked for me): RETURN 'OK'
If you have native SQL query then fix it by adding a cast to the query.
Example:
CAST('yourString' AS varchar(50)) as anyColumnName
In my case it worked for me.
In my case, the issue was Hibernate not knowing how to deal with an UUID column. If you are using Postgres, try adding this to your resources/application.properties:
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQL9Dialect
Another simple explanation might be that you're fetching a complex Type (Entity/POJO) but do not specify the Entity to map to:
String sql = "select yourentity.* from {h-schema}Yourentity yourentity";
return entityManager.createNativeQuery(sql).getResultList();
simply add the class to map to in the createNativeQuery method:
return entityManager.createNativeQuery(sql, Yourentity.class).getResultList();
In my case the problem was that, I forgot to add resultClasses attribute when I setup my stored procedure in my User class.
#NamedStoredProcedureQuery(name = "find_email",
procedureName = "find_email", resultClasses = User.class, //<--I forgot that.
parameters = {
#StoredProcedureParameter(mode = ParameterMode.IN, name = "param_email", type = String.class)
}),
This also happens when you are using Hibernate and returning a void function. AT least w/ postgres. It doesnt know how to handle the void. I ended up having to change my void to a return int.
If you are using Postgres, check that you don't have a column of type Abstime. Abstime is an internal Postgres datatype not recognized by JPA. In this case, converting to Text using TO_CHAR could help if permitted by your business requirements.
if using Postgres
public class CustomPostgreSqlDialect extends PostgreSQL94Dialect{
#Override
public SqlTypeDescriptor remapSqlTypeDescriptor(SqlTypeDescriptor sqlTypeDescriptor)
{
switch (sqlTypeDescriptor.getSqlType())
{
case Types.CLOB:
return VarcharTypeDescriptor.INSTANCE;
case Types.BLOB:
return VarcharTypeDescriptor.INSTANCE;
case 1111://1111 should be json of pgsql
return VarcharTypeDescriptor.INSTANCE;
}
return super.remapSqlTypeDescriptor(sqlTypeDescriptor);
}
public CustomPostgreSqlDialect() {
super();
registerHibernateType(1111, "string");
}}
and use
<prop key="hibernate.dialect">com.abc.CustomPostgreSqlDialect</prop>
For anybody getting this error with an old hibernate (3.x) version:
do not write the return type in capital letters. hibernate type implementation mapping uses lowercase return types and does not convert them:
CREATE OR REPLACE FUNCTION do_something(param varchar)
RETURNS integer AS
$BODY$
...
This is for Hibernate (5.x) version
Calling database function which return JSON string/object
For this use unwrap(org.hibernate.query.NativeQuery.class).addScalar() methods for the same.
Example as below (Spring & Hibernate):
#PersistenceContext
EntityManager em;
#Override
public String getJson(String strLayerName) {
String *nativeQuery* = "select fn_layer_attributes(:layername)";
return em.createNativeQuery(*nativeQuery*).setParameter("layername", strLayerName).**unwrap(org.hibernate.query.NativeQuery.class).addScalar**("fn_layer_attributes", **new JsonNodeBinaryType()**) .getSingleResult().toString();
}
Function or procedure returning void cause some issue with JPA/Hibernate, so changing it with return integer and calling return 1 at the end of procedure may solved the problem.
SQL Type 1111 represents String.
If you are calling EntityManager.createNativeQuery(), be sure to include the resulting java class in the second parameter:
return em.createNativeQuery(sql, MyRecord.class).getResultList()
After trying many proposed solutions, including:
https://stackoverflow.com/a/59754570/349169 which is one of the solutions proposed here
https://vladmihalcea.com/hibernate-no-dialect-mapping-for-jdbc-type/
it was finally this one that fixed everything with the least amount of changes:
https://gist.github.com/agrawald/adad25d28bf6c56a7e4618fe95ee5a39
The trick is to not have #TypeDef on your class, but instead have 2 different #TypeDef in 2 different package-info.java files. One inside your production code package for your production DB, and one inside your test package for your test H2 DB.

Spring JPA repository casting error when using JPQL

I have a PagingAndSorting JPA repository declared. I am using the #Query annotation.
I am getting an exception when I call the get() method on an Optional object from the findById(id) method of the repository.
The weird thing is it only happens when I use JPQL.
The code works if my query is native:
#Override
public BatchDto findById(String id) {
Optional<Batch> findResult = this.batchRepository.findById(id);
if (!findResult.isPresent()) return null;
Batch entity = findResult.get(); **<-------- Cast Exception Here**
BatchDto dto = this.mapper.toDto(entity, BatchDto.class);
List<BatchTransaction> transactions = entity.getTransactions();
dto.setTransactionDtos(mapper.toListDto(transactions, TransactionDto.class));
return dto;
}
Inspecting the findResult object with a breakpoint - I can see:
Optional[net.domain.data.batch#4b8bb6f]
when I have nativeQuery = true in the #Query annotation.
#Query(value = Sql.FindBatchById, nativeQuery = true)
Here is the query being used:
SELECT DISTINCT(B.batchNumber), COUNT(B.batchNumber) as TransactionCount FROM BATCH B WHERE B.batchReferenceNumber = :id GROUP BY B.batchNumber
However if I change it to JPQL and remove the nativeQuery=true attribute - the findResult is
Optional[[Ljava.lang.Object;#76e04327].
and I get a ClassCastException:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to net.domain.data.batch
So bottom line - this works when specify nativeQuery=true and fails when I try to use JPQL.
I would prefer not to specify nativeQuery as we will eventually port this db to Oracle.
First of all the query shown below doesn't return a single Batch instance. Since there are distinct and count aggregate functions, the query will return a List of aggregates.
To be able to read that statistics you can add appropriate method into the batchRepository. Something like this:
#Query("SELECT DISTINCT(B.batchNumber) as dist, COUNT(B.batchNumber) as cnt FROM BATCH B GROUP BY B.batchNumber")
List<Map<Long, Long>> findStatistics();
and then iterate through the list.
UPD
If the id parameter exactly guarantee that will return a single record, you can change a return type to a Map
#Query("SELECT DISTINCT(B.batchNumber) as dist, COUNT(B.batchNumber) as cnt FROM BATCH B WHERE B.batchReferenceNumber = :id GROUP BY B.batchNumber")
Map<Long, Long> findStatisticsById(#Param("id") Long id);

Spring JPA "PersistentEntity must not be null" with aggregate DTO

My summary DTO is throwing PersistentEntity must not be null! I'm using a summary DTO to return sum() and count() aggregates. I added custom queries to the source repository interface.
public interface SettlementHistoryRepository extends JpaRepository<SettlementHistory, String> {
#Query("SELECT new com.x.service.report.dto.CategoryAmountSummary(h.organizationName, count(h.id), sum(h.rebateAmount)) FROM SettlementHistory h WHERE h.fundDate BETWEEN :rangeStart AND :rangeEnd GROUP BY h.organizationName ORDER BY h.organizationName")
List<CategoryAmountSummary> summarizeRebateAmountByOrganizationWhereFundDateIsBetweenOrderByFundDate(
#Param("rangeStart") #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm'Z'") Date rangeStart,
#Param("rangeEnd") #DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm'Z'") Date rangeEnd
);
The method results in this error:
ERROR --- [nio-8080-exec-7] o.s.d.r.w.RepositoryRestExceptionHandler : PersistentEntity must not be null!
I would return the entity class if not for my need to have a count for each category. How else can I go about this?
In the end, Spring Data Rest didn't like the repository returning a different object type. To get around the problem I added a Rest Controller that, in turn, called the aggregation method on the repository. It was able to return the summary DTO without problems.

EclipseLink JPA Dynamic model and Criteria Query

i like to create a (crtieria-) query against a dynamic model but i always get the exception
No [EntityType] was found for the key class [demo.DynamicResult] in the Metamodel
at the last line
final CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Tuple> query = criteriaBuilder.createTupleQuery();
DynamicHelper helper = new DynamicHelper(JpaHelper.getServerSession(emf));
Class<? extends DynamicEntity> rootclass = helper.getType("demo.DynamicResult").getJavaClass();
query.from(rootclass);
The DynamicResult gets generated with following code
ServerSession serverSession = JpaHelper.getEntityManager(entityManager).getServerSession();
DynamicClassLoader dcl = DynamicClassLoader.lookup(serverSession);
JPADynamicHelper jpaDynamicHelper = new JPADynamicHelper(entityManager);
Class<?> dynamicResult = dcl.createDynamicClass("demo.DynamicResult");
JPADynamicTypeBuilder dynamicResultBuilder = new JPADynamicTypeBuilder(dynamicResult, null, "DynamicResult");
dynamicResultBuilder.addDirectMapping("id", String.class, "id");
//Some more addDirectMappings ....
dynamicResultBuilder.setPrimaryKeyFields("id");
type = dynamicResultBuilder.getType();
jpaDynamicHelper.addTypes(false, false, type);
What did I missed ?
Is this kind of query possible at all ?
Regards
You may be hitting https://bugs.eclipse.org/bugs/show_bug.cgi?id=429760 bug. There was a problem with copying newly created dynamic entity metadata into current session.
Check this bug and attached diffs. jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/tests/jpa/dynamic/simple/SimpleQueryTestSuite.java test suite contains test which does similar thing - creates dynamic entity and runs JPQL query against it within the same transaction (UnitOfWork).
I would try latest 2.7.0 or 2.6.1 (do not use .WAS) build from https://www.eclipse.org/eclipselink/downloads/nightly.php to see if it works fine for you.

JPA EntityManager TypedQuery -- Return a Different Class (ViewModel/DTO) than entity model?

Coming from a Entity Framework Background I can cast my ORM results to a class that contains a subset of the full back-end model's data.
I have a JAX-RS REST Service where I am usually returning something like
MyEntity result = em.createQuery(select e from MyEntity e ... blah blah blah).
I know I can do this:
Object result = em.createQuery(select e.Title, e.Version, e.Date from MyEntity e... blah blah blah).
But can I either a:
Cast my result to a separate class or B name my fields in my createquery such that they are named when returning my JSON?
For example in .Net Land I could do something like....
(select new {Title = e.Title, Version = e.Version})
and then cast this to another type. I tried using a typedquery and casting but I get a "Type X is incompatible with return type Y" type error.
My goal is to return a specific subset (view model/ DTO)of my information for consumption in a specific scenario.
E.g My model is huge and I don't want to return a large amount of the data every time.
Yes, creating non-entity types is possible, using JPA constructor expressions i.e. the NEW keyword:
List<DTO> dtos = em.createQuery("SELECT NEW com.example.DTO( o.title, o.version) FROM Entity o").getResultList();
The DTO must have a constructor with all the relevant fields.
There is no need for casting.