HQL cannot query JSON data - mysql

This is a NativeSql work fine:
session.createSQLQuery(select json_length(fav_goods) from customer where id=1).uniqueResult()
But if I change it to HQL like this follow ,it well raised a Error
session.createQuery(select json_length(favGoods) from CustomerEntity where id=1).uniqueResult()
error
Caused by: org.hibernate.QueryException: No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode
\-[METHOD_CALL] MethodNode: '('
+-[METHOD_NAME] IdentNode: 'json_length' {originalText=json_length}
\-[EXPR_LIST] SqlNode: 'exprList'
\-[DOT] DotNode: 'customeren0_.fav_goods' {propertyName=favGoods,dereferenceType=PRIMITIVE,getPropertyPath=favGoods,path={synthetic-alias}.favGoods,tableAlias=customeren0_,className=cn.phyer.bishe.entity.CustomerEntity,classAlias=null}
+-[IDENT] IdentNode: '{synthetic-alias}' {originalText={synthetic-alias}}
\-[IDENT] IdentNode: 'favGoods' {originalText=favGoods}
[select json_length(favGoods) from cn.phyer.bishe.entity.CustomerEntity where id=?1]
at org.hibernate.QueryException.generateQueryException(QueryException.java:120)
at org.hibernate.QueryException.wrapWithQueryString(QueryException.java:103)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:220)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:155)
at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:600)
at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:709)
... 39 more
Caused by: org.hibernate.QueryException: No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode
\-[METHOD_CALL] MethodNode: '('
+-[METHOD_NAME] IdentNode: 'json_length' {originalText=json_length}
\-[EXPR_LIST] SqlNode: 'exprList'
\-[DOT] DotNode: 'customeren0_.fav_goods' {propertyName=favGoods,dereferenceType=PRIMITIVE,getPropertyPath=favGoods,path={synthetic-alias}.favGoods,tableAlias=customeren0_,className=cn.phyer.bishe.entity.CustomerEntity,classAlias=null}
+-[IDENT] IdentNode: '{synthetic-alias}' {originalText={synthetic-alias}}
\-[IDENT] IdentNode: 'favGoods' {originalText=favGoods}
At the entity class CustomerEntity,field fav_goods is been named as favGoods

JPQL (or HQL) does not support JSON Functions.
Please find all supported functions here:
https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html#hql-functions
You have to stick with SQL.

I had a similar problem and I had to use JSON_EXTRACT mysql function.
Extend the MySQL5Dialect class to register the SQL Function in Hibernate.
public class CustomMySQLDialect extends MySQL5Dialect {
public CustomMySQLDialect(){
super();
registerFunction(
"JSON_EXTRACT",
new StandardSQLFunction(
"JSON_EXTRACT",
StandardBasicTypes.STRING
)
);
}
}
Register the Custom MySQL Dialect in Hibernate cfg xml
<property name="hibernate.dialect">com.testsigma.specification.CustomMySQLDialect</property>
Use CriteriaQuery(JPQL) with SQL Function within Hibernate.
Root<EntityType> subRoot = criteriaBuilder.from(Entity.class);
subQuery.select(builder.function("JSON_EXTRACT", String.class, subRoot.get("jsonData"), builder.literal("$.\"jsonPathField\"")));
query1.where(root.get("jsonKey").in(subQuery));
Taken from https://vladmihalcea.com/hibernate-sql-function-jpql-criteria-api-query/
Working as of Hibernate 5.2.3.Final, Spring Data JPA 2.1.9.RELEASE

Related

Invalid JSON text in argument 2 - json_contains using Spring JPA

in my table items has a json column named tag. keeping data like ["tag1", "tag2"] .
i want to select from this table filter with the given tag.
in mysql command line, json_contains works.
select * from items where json_contains(tags, '"tag1"');
but how can i using it in Spring JPA?
#Query(value = "select * from items where json_contains(tags, ?1))", nativeQuery = true)
Page<ItemDO> list(String query, Pageable pageable);
got error
TRACE 21469 --- [ main] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [VARCHAR] - [desc]
WARN 21469 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1064, SQLState: 42000
ERROR 21469 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ') limit 10' at line 1
so how can i using json_contains withing Spring JPA?
There are two issues with your implementation:
There as an extra parenthesis at the end of SQL query ), look at what logs say. Instead, you should write it as follows:
#Query(value = "select * from items where json_contains(tags, ?1)", nativeQuery = true)
Having done that, you also need to wrap the method parameter (your query variable) inside double quotations, to exactly match what you tried in MySQL command line console. So, you will call the method as follows:
yourRepository.list("\"tag1\"", PageRequest.of(,10) );
Alternative solution
You may use Spring Data Specifications API to avoid native queries.
Thus, you could do the following:
#Repository
public interface ItemRepository extends JpaRepository<ItemDao, Integer> , JpaSpecificationExecutor<ItemDao> {
default Page<ItemDao> findItemsByJsonTagValue(String jsonTagValue, Pageable pageable){
return findAll((root, query, builder) -> {
final String CONTAINS_FUNCTION = "JSON_CONTAINS";
final String JSON_COLUMN_NAME = "tags" ;
final int TRUE_BIT = 1;
return builder.equal(
builder.function(
CONTAINS_FUNCTION,
String.class,
root.<String>get(JSON_COLUMN_NAME),
builder.literal(String.format("\"%s\"", jsonTagValue))),TRUE_BIT);
}, pageable);
}
}
And then somewhere in your code, you would call the method as follows :
itemRepository.findItemsByJsonTagValue("tag1", PageRequest.of(0 ,10));

Hibernate, jpa and mysql

I'm having a small problem with my insert statements in Spring-data/jpa/hibernate/mysql/junit application. I created a small unit test to see if i can do a simple INSERT statement.
This is what i have:
#Test
public void testAddAssignment() {
Assignment assignment = new Assignment();
assignment.setCharacter("Z");
assignment.setType(1);
assignmentController.addAssignment(assignment);
assignment = assignmentController.findAssignmentById(16);
assertEquals(16, assignment.getId());
assertEquals(1, assignment.getType());
assertEquals("Z", assignment.getCharacter());
}
When i try to execute this I get the following error:
org.springframework.dao.InvalidDataAccessResourceUsageException: could not insert: [com.byronvoorbach.domain.Assignment]; SQL [insert into Assignment (CHARACTER, TYPE) values (?, ?)]; nested exception is org.hibernate.exception.SQLGrammarException: could not insert: [com.byronvoorbach.domain.Assignment]
And this is my addAssignment(assigment);
#Transactional
public Assignment addAssignment(Assignment assignment) {
return repository.save(assignment);
}
And this is my repository:
#Transactional(readOnly = true)
public interface AssignmentRepository extends JpaRepository<Assignment, Long> {
public List<Assignment> findByType(int type);
}
Since i never wrote an sql query I'm unable to find where the query gets messed up.
Does anyone have an idea?
(Let me know if you need more source code or my .xml files)
EDIT :
Thanks to JB Nizet and fmucar I was able to fix this problem. I changed the column names and now it works!! Apperantly there are a few limitations to column naming which I wasn't aware of.
Try using something other than character and type as column names, because these are probably reserved keywords in SQL.
CHARACTER and TYPE are keywords for sql.
Try not to use keywords as columns names. That may be the problem.

How to map a MySQL char(n) column to an instance variable using a JPA/Hibernate annotation?

I encounter a JPA/Hibernate mapping problem on a column "language" in a MySQL table whose type is char(7). In my entity, the code generated for the field is:
private String language;
this causes the following exception at runtime:
... 43 more
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: prosvetaPersistenceUnit] Unable to build EntityManagerFactory
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:911)
at org.hibernate.ejb.HibernatePersistence.createContainerEntityManagerFactory(HibernatePersistence.java:74)
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:225)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:308)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
... 58 more
Caused by: org.hibernate.HibernateException: Wrong column type in joo16_dev.jos_categories for column language. Found: char, expected: varchar(255)
at org.hibernate.mapping.Table.validateColumns(Table.java:283)
at org.hibernate.cfg.Configuration.validateSchema(Configuration.java:1313)
at org.hibernate.tool.hbm2ddl.SchemaValidator.validate(SchemaValidator.java:139)
at org.hibernate.impl.SessionFactoryImpl.<init>(SessionFactoryImpl.java:378)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1842)
at org.hibernate.ejb.Ejb3Configuration.buildEntityManagerFactory(Ejb3Configuration.java:902)
... 63 more
Thanks in advance !
JP
Try this:
#Column(name="language",columnDefinition="char(7)")
see if that works.
For me*Grails it's works: sqlType: "char" on mapping

hibernate criteria putting a contains / does not contain restriction on a set of elements

Hi I'm trying to migrate my old sql based query framework to be Hibernate Criteria based, currently I'm using Hibernate version 3.2. and mysql v 5.0.54
My question is I want to write a criteria that returns all the Beans that do not have a value in a contained set of elements.
A summary of the hibernate config file for the bean is:
<hibernate-mapping package="com.mydomain.beans">
<typedef class="com.mydomain.dao.util.HibernateAgentOptionType" name="agent-option"/>
<class name="Agent" table="agent">
...
<set name="agentOptions" table="agent_options" fetch="join" lazy="true">
<key column="agt_id"/>
<element column="identifier" type="agent-option"/>
</set>
</class>
</hibernate-mapping>
The class file is something like:
public class Agent {
...
public Set<Option> getAgentOptions() {
return agentOptions;
}
public void setAgentOptions(Set<Option> _agentOptions) {
this.agentOptions = _agentOptions;
}
public enum Option {
WEB_SITE_SYNDICATE ("web-site-syndicate"),
RECEIVE_PREMIUM_ENQUIRIES ("receive-premium-enquiries"),
DO_NOT_SYNDICATE_ADS_TO_THIRD_PARTIES ("do-not-syndicate-ads-to-third-parties")
}
}
I would like to retrieve all the agents who do not have the Option "DO_NOT_SYNDICATE_ADS_TO_THIRD_PARTIES" in their agentOptions
the sql that I was using for this previously was something like:
SELECT agt.id
FROM agent agt
WHERE agt.id NOT IN (
SELECT sub_agt.id FROM agent sub_agt
JOIN agent_options AS agt_options ON agt_options.agt_id = sub_agt.id
WHERE agt_options.identifier = 'do_not_syndicate_ads_to_third_parties'
)
I've tried this:
...
Criteria crit = getHibernateSession().createCriteria(Agent.class);
crit.addRestriction(Restriction.not(Restriction.in("agentOptions", noSyndicatedAds)));
...
Which spits out bad sql
I've also tried this using aliases:
...
crit.createAlias("agentOptions", "agentOption");
crit.add(Restrictions.not(Restrictions.in("agentOption", options)));
...
Which throws an exception:
org.hibernate.MappingException: collection was not an association: Agent.agentOptions
Finally I decided to try a brute sql Criterion:
...
crit.add(Restrictions.sqlRestriction(" {alias}.id not in " +
"(select agt_sub.id " +
"from agent agt_sub " +
"join agent_options as agent_options_sub on agent_options_sub.agt_id = agt_sub.id " +
"where agent_options_sub.identifier in (?)) ",
Agent.Option.DO_NOT_SYNDICATE_ADS_TO_THIRD_PARTIES.getIdentifier(),
new org.hibernate.type.StringType()
));
...
Which worked but seems a little bit ugly.
Does anyone know if there is a way using the std Criteria API without having to resort to sql.
Any help or suggestions would be very welcome. Even if they require upgrading to a more recent version of Hibernate.
Cheers
Simon
Structure your subquery as a DetachedCriteria, and then use your nested Restrictions to achieve a NOT IN. In your case:
DetachedCriteria subquery = DetachedCriteria.forClass(Agent.class);
subquery.createAlias("agentOptions", "agentOption");
subbquery.add(Restrictions.eq("identifier", Agent.Option.DO_NOT_SYNDICATE_ADS_TO_THIRD_PARTIES.getIdentifier());
subquery.setProjection(Projections.property("agt_id")); // only return one field
crit.add(Restrictions.not(Restrictions.in("agt_id", subquery)));

JPA native query for LONGTEXT field in a MySQL view results in error

I have the following JPA SqlResultSetMapping:
#SqlResultSetMappings({
#SqlResultSetMapping(name="GroupParticipantDTO",
columns={
#ColumnResult(name="gpId"),
#ColumnResult(name="gpRole"),
// #ColumnResult(name="gpRemarks")
}
)
Which is used like this:
StringBuilder sbQuery = new StringBuilder("Select ");
sbQuery.append(" gpId, ");
sbQuery.append(" gpRole, ");
// sbQuery.append(" gpRemarks ");
sbQuery.append(" FROM v_group_participants_with_details ");
Query query = em.createNativeQuery(sbQuery.toString(), "GroupParticipantDTO");
The view is like this:
DROP VIEW IF EXISTS `v_group_participants_with_details`;
CREATE VIEW `v_group_participants_with_details`
AS
SELECT
gp.id AS gpId,
gp.role AS gpRole,
gp.remarks AS gpRemarks
FROM GroupParticipation gp
;
The GroupParticipation table has the remarks column defined as LONGTEXT (I'm using Mysql 5.x)
Now for the problem:
When the remarks field is commented out from the query everything works perfectly, but if I try to include the remarks field in the query, I get the following error:
javax.persistence.PersistenceException: org.hibernate.MappingException:
No Dialect mapping for JDBC type: -1
at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException
(AbstractEntityManagerImpl.java:614)
at org.hibernate.ejb.QueryImpl.getResultList(QueryImpl.java:76)
What gives? How can I get a LONGTEXT column from a native query?
This problem is reported in HHH-1483 and HHH-3892. In short, Hibernate does not know, how to map a LONGVARCHAR column returned by a native query.
This problem is fixed in Hibernate 3.5.0+. For previous versions, a workaround would be to extend the MysqlDialect to register the correct Hibernate Type for a LONGVARCHAR:
import java.sql.Types;
import org.hibernate.Hibernate;
public class MyMySQL5Dialect extends org.hibernate.dialect.MySQL5Dialect {
public MyMySQL5Dialect() {
super();
// register additional hibernate types for default use in scalar sqlquery type auto detection
registerHibernateType(Types.LONGVARCHAR, Hibernate.TEXT.getName());
}
}