Spring Data JPA and startsWith repository - java

I have the following Spring Data JPA repository:
#RepositoryRestResource(collectionResourceRel = "product", path = "product")
public interface ProductRepository extends PagingAndSortingRepository<Product, Integer> {
#RestResource(path = "nameStartsWith", rel = "nameStartsWith")
Page findByNameStartsWithOrderByNameDesc(#Param("name") String name, Pageable p);
}
The definition of the Product class is obvious and is a mapped JPA entity on a postgresql database.
It works pretty nice, but it has an annoying problem which I couldn't fix.
I suppose that spring translate this method definition in a sql query with the like operator that uses _ and % as wild cards. I'm afraid anyway that those character are not escaped when passed to this method, with the results that if I search for a product with a name that contains a _ it gets understood as "any character", and this is bad due to the naming convention my products use.
I need a way to escape the name parameter before it gets passed to the method, but the only way I could think of is implementing the method myself loosing all the magic of spring data. Is there a more elegant way to do this?
Thank you!
PS I'm using spring boot 1.4.0

You can wrap around a custom sql query around this method by using #Query annotation. Here is the relevant documentation.
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query

Related

Issue with JPA native queries saying No property found for type

I have a spring boot application using JPA native queries. These queries are located in a separate properties file. I have 3 queries called getSomething and getSomethingWithSomeParam and getSomethingWithOtherParam. When it was just getSomething, the endpoint worked fine. When I added getSomethingWithSomeParam endpoint/query, it also worked as expected.
However when I added getSomethingWithOtherParam, JPA started throwing this error:
IllegalArgumentException: Failed to create query for method ... DataAccessLayer.getSomethingWithOtherParam(...)! No Property getSomething found for type DTO!
This is what the data access layer looks like:
#Query(nativeQuery = true)
public List<String> getSomething();
#Query(nativeQuery = true)
public List<String> getSomethingWithSomeParam(#Param("some") String some);
#Query(nativeQuery = true)
public List<String> getSomethingWithOtherParam(#Param("other") String other);
Does JPA not allow multiple queries with similar names if they have the same number and type of parameters? Why is JPA getting confused in which query to map each query method to?
I think your code is not a problem because I have used this method many times.
Because methods are called statically from another layer, there is no problem defining methods and parameters in your code.
You may have set up server-side settings that cause errors. To do this, you can create a simple project to test this.

How to query only limited columns in Hibernate, and map them to a given POJO?

My project involves using GraphQL within a Spring Boot app. For demonstration purposes, here is my GraphQL schema:
type Company{
name: String,
parentOrganization: String,
flag:Int
}
I'm still learning Spring Boot and JPA, so I use spring-boot-starter-data-jpa for all the JPA, Hibernate, etc.
My problem is, when someone queries only for name and organization, Hibernate queries for all the columns and GraphQL picks the columns requested.
#Repository
#Transactional
public interface CompanyRepository extends JpaRepository<Company,Long> {
}
The above code doesn't really give me any flexibility in limiting the columns that are queried. I've tried using Hibernate's Criteria API as well, but whichever way I go, I get this error:
Unable to locate appropriate constructor on class [packagee.entity.company]. Expected arguments are: java.lang.String, java.lang.String [select new package.entity.Company(generatedAlias0.company, generatedAlias0.organization) from package.entity.Company as generatedAlias0]
Below is the code for my Criteria implementation:
public static List<Company> get(EntityManager em, List<String> fieldsAsked){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Company> cq = cb.createQuery(Company.class);
Root<Company> root = cq.from(Company.class);
List<Selection<?>> selectionList = new LinkedList<Selection<?>>();
for(String name: fieldsAsked){
selectionList.add(root.get(name));
}
cq.multiselect(selectionList);
return em.createQuery(cq).getResultList();
}
How do I get limited columns from Hibernate? I've seen many answers online that ask to make appropriate constructor in the entity class, but that's not really possible for me because my entity parameters are mostly Strings and I cant make constructors for all the permutations possible (because I'm using GraphQL, the control of what to query really goes to the end user of my project).
What should I do? Thanks in advance!
What you want to do is not really possible with Hibernate directly, but you can checkout Blaze-Persistence Entity-Views which also has a GraphQL integration that supports exactly what you are looking for. See https://persistence.blazebit.com/documentation/1.6/entity-view/manual/en_US/#graphql-integration
Here is a sample project that shows how you can use this: https://github.com/Blazebit/blaze-persistence/tree/master/examples/spring-data-graphql
Solution 1:
You can create a new DTO class which will be returned by your query.
The DTO class:
public class CompanyDTO(){
//fields,constructor
}
And in the repository:
#Query(value = "SELECT new com.example.dto.companyDTO" +
"(c.name,c.parentOrganization)" +
" FROM Company c")
List<CompanyDTO>findCompanySelectedColumns(PageRequest pageable);
Solution 2(clean solution):
You can use interface. Do not implement the interface.
interface customCustomer{
String getName();
String getParentOrganization();
}
In repository:
List<CustomCustomer>findAllByNameAndParentOrganization();

Retrieve a result from a stored procedure in a Java object [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.

QueryDSL: building a query from an entity

I've just started integrating QueryDSL into a Spring Boot project and I'm looking for a way to build a query out of an existing entity bean. Using #ModelAttribute it's nice and easy to pass in an entity via a GET request from the controller as long as the parameters align with the bean:
public Page<Company> getLogins(#ModelAttribute Company company, Pageable pageable, #RequestParam(value = "page", required = false) String pageNumber){
return companyService.findbyParameters(company,pageNumber);
}
And in the service class, I can use the BooleanBuilder to build up a query:
public Page<Company> findbyParameters(Company companySearch,String pageNumber){
QCompany company = QCompany.company;
BooleanBuilder builder = new BooleanBuilder();
if (companySearch.getEmail() != null && !companySearch.getEmail().equals("")){
builder.and(company.email.eq(companySearch.getEmail()));
}
if (companySearch.getCompanyName() != null && !companySearch.getCompanyName().equals("")){
builder.and(company.companyName.eq(companySearch.getCompanyName()));
}
//add other clauses...
return loginRepository.findAll(builder.getValue(),pageableService.getPageRequest(pageNumber));
}
..and this works fine. But it seems like an unnecessary amount of plumbing since I'll have to write similar, longwinded conditional code for each entity I'm working with. I reckon that reflection might be an option, but I'm not sure if QueryDSL has something built in that handles this situation. I've looked at the QueryDSL docs and nothing jumped out at me.
So is there a nice, tidy way of handling this situation without clogging up my service classes with boilerplate?
You can use Spring Data's QueryDSL integration. Basically, you extend the QueryDslPredicateExecutor in your repository interface and it add a findAll method that gets a QueryDSL Predicate and filter all the results based on that Predicate. You see more details here.
It turns out that the exact thing I was looking for is Spring Data's query by example API.
https://www.baeldung.com/spring-data-query-by-example
It lets you create a query by providing a sample entity and a matcher which defines things like case sensitivity, partial 'like' matching and so on.
It's very useful in limited situations, and can drastically reduce boilerplate query code; but when you want to query a more complex graph of data you'll want to use a different approach.

Counting query in spring-data-couchbase (N1QL)

I'm writing couchbase repository using Spring module and I'm trying to add my own implementation of count method using N1QL query:
public interface MyRepository extends CouchbaseRepository<Entity, Long> {
#Query("SELECT count(*) FROM default")
long myCount();
}
But it doesn't work:
org.springframework.data.couchbase.core.CouchbaseQueryExecutionException: Unable to retrieve enough metadata for N1QL to entity mapping, have you selected _ID and _CAS?
So my question is: how can I write counting query using spring-data-couchbase?
I cannot find anything about this in spring documentation. link
This exception happens because the #Query annotation was designed with the use-case of retrieving entities in mind. Projections to a scalar like count are uncovered corner cases as of RC1. Maybe I can think of some way of adding support for it through explicit boolean flag in the annotation?
Unfortunately I was unable to find a workaround. I was trying to come up with a custom repository method implementation but it appears support for it is broken in 2.0.0-RC1 :(
edit:
The use case of simple return types like long, with a SELECT that only uses a single aggregation, should work so this is a bug/improvement. I've opened ticket DATACOUCH-187 in the Spring Data JIRA.
#Query("SELECT count(*) , META(default).id as _ID, META(default).cas as _CAS FROM default")
Change your query to this one.
Use this query :
#Query("SELECT count(*) as count FROM #{#n1ql.bucket} WHERE #{#n1ql.filter} ")
long myCount();

Categories

Resources