在使用Hibernate的Spring Data JPA的Web应用程序中,我们利用网页分页功能,为各种实体列表提供分页和排序功能。
@Controller
public class MyEntityController {
@RequestMapping(method = RequestMethod.GET)
public ModelAndView list(Pageable pageable) { ... }
}
@Configuration
public class MyWebMvcConfig extends WebMvcConfigurationSupport {
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
super.addArgumentResolvers(argumentResolvers);
argumentResolvers.add(new PageableArgumentResolver());
}
}
public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, String> {
Page<MyEntity> findByPropertyX(String propertyX, Pageable pagable);
}
这使得实体属性可以在渲染的HTML中以特殊的请求参数形式进行定义,其中
page.sort
值实际上匹配实体中要排序的属性。<table>
<thead>
<tr>
<th><a href="?page.sort=propertyX&page.sort.dir=asc">Property X</a></th>
<th><a href="?page.sort=propertyY&page.sort.dir=asc">Property Y</a></th>
</tr>
</thead>
<tbody>...</tbody>
</table>
这将生成类似以下的URL:
这会生成一个类似于以下的URL:
http://host/context-root/entities/?page.sort=propertyX&page.sort.dir=asc
问题在于用户可能会修改URL以使用无效的page.sort
属性,这些属性引用不存在的列/属性名称,或者更糟糕的是,使用无效的JPA查询字符导致语法无效。
例如,如果将URL修改为按“noSuchProperty”进行排序:
http://host/context-root/entities/?page.sort=noSuchProperty&page.sort.dir=asc
但是如果该属性不存在,将会抛出以下异常:
java.lang.IllegalArgumentException: No property noSuchProperty found for type class com.my.company.MyEntity
at org.springframework.data.repository.query.parser.Property.<init>(Property.java:76)
. . .
at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:86)
. . .
at $Proxy68.findByPropertyX(Unknown Source)
at com.my.company.MyEntityRepository.findByPropertyX(MyEntityRepository.java:17
同样地,如果将URL修改为无效的查询语法字符,例如 """:
http://host/context-root/entities/?page.sort=%22&page.sort.dir=asc
以下错误将会发生:
java.lang.StackOverflowError
java.util.regex.Pattern$GroupTail.match(Pattern.java:4227)
. . .
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
org.springframework.data.repository.query.parser.Property.create(Property.java:326)
(还有一种异常情况是,当存储库方法上显式定义了@Query时,会导致一个org.hibernate.QueryException)。
Spring Data JPA抽象出了排序、分页和处理这些参数的细节,但它似乎不能优雅地处理这些场景(即指定无效的排序参数)。
我们可以增加一些额外的自定义逻辑来验证排序属性是否确实存在于实体中;然而,我想知道是否有更清晰、更集中的方法来做到这一点,这样我们就不会失去Spring Data JPA抽象所带来的好处和简洁性。我们在整个应用程序中使用这个排序功能,涉及许多不同的实体,因此理想情况下,我们希望更多地采用通用的方法,而不是必须为每个实体页面请求显式定义或检查排序属性。
具体而言,我们实际上扩展了PageableArgumentResolver,接受了一个注释的排序默认值,在我们的控制器中提供(出于简单起见,未在代码示例中说明),因此我们希望只回退到此默认排序顺序,或者只回退到实体的默认排序顺序,而不是抛出异常。
一些想法和尝试...我可以使用QueryCreationListener拦截查询创建,并获取排序参数;但是,在那一点上我无法修改查询。或者,我可以扩展和使用自定义的PageableArgumentResolver(我们已经在这样做了)来获取排序参数;但是,在那一点上,我没有访问实体的能力,也无法确定该实体是否确实具有该名称的属性。我们可以显式地声明支持的属性;然而,同样,这会破坏在不需要特定或声明的实体知识的情况下集中自动处理此场景的想法。
是否有任何其他类型的拦截器或类似的结构,我可以利用它们集中验证分页可排序参数并在调用查询之前进行必要的修改?或者是否有任何类型的配置或方式,Spring可以自动处理这种情况,使其更优雅地处理无效的排序参数?