How to use hasAnyRole like expression SpEl in #Query - java

I'm using Spring Security Expressions within #Query like this example:
#Query("select o from Pet o where o.owner.name like ?#{hasRole('ROLE_ADMIN') ? '%' : principal.username}")
If you have the role ADMIN, the query returns all the pets. But if you don't have this role, the query returns only Pet objects where the owner name is the same of the user authenticated name.
This works fine, but when I try to use hasAnyRole('ROLE_ADMIN','ROLE_OWNER'), the system returns an exception...
org.springframework.expression.spel.SpelEvaluationException: EL1004E:(pos 0): Method call: Method hasAnyRole(java.lang.String,java.lang.String) cannot be found on java.lang.Object[] type
at org.springframework.expression.spel.ast.MethodReference.findAccessorForMetho
...
In SecurityExpressionRoot is defined the method hasAnyRole:
public final boolean hasAnyRole(String... roles) {
return hasAnyAuthorityName(defaultRolePrefix, roles);
}

I have the same issue, the quick workaround is to write hasRole('ROLE_SUPER') or hasRole('ROLE_OWNER').
This exception is caused by Spring Data, which is not able to resolve methods with a variable number of arguments in SpEL, as far as I can see when debugging.
The ExtensionAwareEvaluationContextProvider method resolver doesn't match hasAnyRole(String[]).
I created https://jira.spring.io/browse/DATACMNS-1518.
EDIT: this issue has been fixed, I just tested latest snapshot and got hasAnyRole work.

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.

Using named parameters for method when I use #Query

I am using spring-data-jpa, I want update something, I have annotated my method in PaySupplierSettleBillRepository as
public interface PaySupplierSettleBillRepository extends JpaRepository<PaySupplierSettleBillEntity, Long>,
JpaSpecificationExecutor<PaySupplierSettleBillEntity> {
#Modifying
#Query("update PaySupplierSettleBillEntity p set p.payTime=:payTime,p.paymentOrder=:paymentOrder, p.transferTime=:transferTime, p.transferBank=:transferBank, p.transferOrder=:transferOrder, p.operatorName=:operatorName, p.remark=:remark where p.orderNumber=:orderNumber")
int updatePayInfo(PaySupplierSettleBillEntity entity);
}
I am getting following exception while starting
Caused by: java.lang.IllegalStateException: Using named parameters for method public abstract xxxxxx
how I fix it ? thinks.
That is not how you write a #Query with named parameters. Take a look at the example from Spring documentation here (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.named-parameters).
If you want to provide an object as param, you can do something like this.
#Query("UPDATE Entity E SET E.name = :#{#entity.name}")
public void updateEntity(#Param("entity") Entity entity);
Caused by: java.lang.IllegalStateException: Using named parameters for method can be thrown when a method has redundant parameter which is not used in the #Query.
In your case, entity should be used in the SQL query like it's mentioned in the previous answer https://stackoverflow.com/a/56053250/5962766 or you should replace it with specifying necessary parameters one by one in the method signature.
Related: IllegalStateException with naming parameters in tomcat

Spring Data JPA aggregate functions on an empty resultset

I am working on a project involving Spring and JPA/Hibernate. The database driver used in my development environment is H2. My application has a page that displays statistics, one example of such a statistic is the average age of my users. However, when I try to fetch the average age using JPQL, I receive an exception
Result must not be null!
Assume for simplicity reasons that I store age as an integer on every User object (in my application this is of course not the case, but that's not important for my problem).
User model
#Entity
public class User implements Identifiable<Long> {
private int age;
// more fields and methods, irrelevant
}
User repository
#Repository
public interface UserRepository extends CrudRepository<User, Long> {
#Query("SELECT AVG(u.age) FROM #{#entityName} u")
long averageAge();
}
I cannot seem to figure out why calling UserRepository#averageAge(); is throwing the exception. I have tried replacing the function AVG in the query by COUNT and this behaves as expected. I have also tried to use an SQL query and setting nativeQuery = true in the annotation, yet to no avail. I can ofcourse solve it by fetching all the users and calculate the average age in plain Java, but this wouldn't be very efficient.
Stacktrace:
Caused by: org.springframework.dao.EmptyResultDataAccessException: Result must not be null!
at org.springframework.data.repository.core.support.MethodInvocationValidator.invoke(MethodInvocationValidator.java:102)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
at com.sun.proxy.$Proxy150.averageAge(Unknown Source)
at my.test.application.StatisticsRunner.run(StatisticsRunner.java:72)
at org.springframework.boot.SpringApplication.callRunner(SpringApplication.java:809)
... 30 more
Solved
The exception was caused by the fact that AVG() returns null when performed on an empty table. I fixed it by modifying the query (inspired by the answer to this question) as follows:
#Query("SELECT coalesce(AVG(u.age), 0) FROM #{#entityName} u")
long averageAge();
If you use Spring Data, and if your method returns null when Hibernate can't find a match, make sure you add #org.springframework.lang.Nullable to your method signature:
public interface SomeRepositoryCustom {
#org.springframework.lang.Nullable
public Thing findOneThingByAttr(Attribute attr) {
/* ...your logic here... */
}
}
This is because Spring Data checks the nullability of your method, and if the annotation is missing, it's going to enforce that you always need to return an object:
/* org.springframework.data.repository.core.support.MethodInvocationValidator */
#Nullable
#Override
public Object invoke(#SuppressWarnings("null") MethodInvocation invocation) throws Throwable {
/* ...snip... */
if (result == null && !nullability.isNullableReturn()) {
throw new EmptyResultDataAccessException("Result must not be null!", 1);
}
/* ...snip... */
I used Spring Boot version 2.1.1.RELEASE and Spring Data 2.1.4.RELEASE.
It seems that the EmptyResultDataAccessException exception is thrown when a result from a query was expected to have at least one row (or element) but none was returned.
Related documentation about this can be found here.
I would suggest to run the same query this attempts to run in order to further validate this theory. Now the good question's what to do with this.
You have two options. Either catch the EmptyResultDataAccessException exception at your calling point and handle it directly in there or alternatively you can have an ExceptionHandler which will be tasked with handling such exceptions.
Both ways of handling this, should be OK and you may choose between each depending on your scenario.
i am not complete sure, but i think the problem it is because of the type of return long, maybe you should use the Long wrapper, long does not allow null because it is a primitive, try to change to
#Query("SELECT AVG(u.age) FROM #{#entityName} u")
Long averageAge();

Spring JPA Query returns Null instead of List

I have an #Entity Video having a one-to-many relation with a List<Tag> tags as one of its fields. I use the following #Repository using Spring Data to get the most popular tags:
#Repository
public interface TagRepository extends CrudRepository<Tag, Integer>{
#Query("SELECT t FROM Tag t WHERE (SELECT SUM(v.views) FROM Video v WHERE t MEMBER OF v.tags) > 0")
public List<Tag> findMostViewedTags(int maxTags);
}
The Query is processed and considered valid by Spring, I tested the generated SQL vs my database locally and it returned 2 Tags. In my Code however, I receive the value Null when I call the method findMostViewedTags(100).
The Query lookup strategy is the default "CREATE_IF_NOT_FOUND".
If there are no results found, should the method return an empty list or Null? My desired behavior is to receive an empty list.
Why does the method call return Null instead of a List<Tag> with size() 2?
The normal behavior is indeed returning an empty list if no results are found. If a List<Object> is the return value of the method in the defined interface, the method should never return Null.
The problem is that a parameter is given to the method and is not used anywhere in the Query. For some reason Spring decides to return a Null in that case. Solution: remove the unused parameter or use the parameter in the Query.
I have experienced similar problem. The cause was that I was using Mockito and have not correctly mocked the data with when().

How to write Spring Data method name to retrieve all elements in a column?

Suppose I have the class:
#Entity
public class Bean {
#Id
private String beanId;
//other fields & setters and getters
}
And the corresponding Spring Data JPA repository, where I want to have in a List<String> all the beanIds.
#RepositoryDefinition(domainClass = Bean.class, idClass = String.class)
public interface BeanRepository {
#Query("select b.beanId from Bean b")
List<String> findAllBeanId();
}
As written above, everything works as expected; but this is a simple operation and I do not want to write a query explicitly. What should the name of the method be such that Spring Data can parse it and obtain the above mentioned query (or the same functionality). I have searched in both the reference documentation as two books I have on Spring Data. The above name (findAllBeanId) and others that I have tried (findBeanId, findBeanBeanId etc.) throw the following exception as root cause:
org.springframework.data.mapping.PropertyReferenceException: No property find found for type Trade
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:75)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:327)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:353)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:353)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:307)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:271)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:245)
at org.springframework.data.repository.query.parser.Part.<init>(Part.java:72)
at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:180)
at org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:260)
at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:240)
at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:68)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:57)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:90)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:162)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:68)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:279)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:147)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:153)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:43)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:142)
... 22 more
In the Spring docs: http://static.springsource.org/spring-data/jpa/docs/1.3.0.RELEASE/reference/html/jpa.repositories.html there is nothing about getting only particular column/property from entity by query generated from method name. So I think that currently it is not possible.
The code you showed works/should work as expected. It's simply not causing the exception you see :).
The exception is referring to a Trade, which seems to indicate that you have a repository for Trade somewhere which seems to refer to a missing property. The code you've shown is definitely not the one causing the exception. This can effectively not be the case as you're defining the query manually so that the query derivation mechanism doesn't even kick in for the repo you've shown.
I've pushed a test case for you to see this in action.

Categories

Resources