在 Spring JPA 存储库中的删除 @Query 中使用 ?#{principal.username} 时如何修复语法错误?

How to fix syntax error when using ?#{principal.username} in a delete @Query in a Spring JPA repository?

我试图让用户只能访问他们自己的配置记录,但允许 ROLE_ADMIN 的用户访问任何记录。

我在 select 查询中使用 SpEL 表达式 ?#{principal.username} 时遇到问题,就像在 findByUid 方法中一样,工作正常,但删除查询中完全相同的表达式会导致语法错误。通过对 deleteByUid 方法使用注释掉的查询,我已将问题缩小到该特定表达式。 deleteByUid 上的注释掉的查询不会抛出语法错误,但当然会拒绝任何非ROLE_ADMIN 用户删除自己的配置记录的能力。

我还需要做一些其他的秘密歌舞来完成这项工作吗?


public interface WidgetConfigRepository extends JpaRepository<WidgetConfig,Long> {

    @Query("SELECT wc FROM WidgetConfig wc WHERE (wc.user.login = ?#{principal.username} OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true') AND wc.uid = :uid")
    WidgetConfig findByUid(@Param("uid") String uid);

    @Query("SELECT wc FROM WidgetConfig wc WHERE (wc.user.login = ?#{principal.username} OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true') AND wc.name LIKE LOWER(CONCAT('%',:name,'%'))")
    Page findLikeName(@Param("name") String name, Pageable pageable);

    @Override
    @Query("SELECT wc FROM WidgetConfig wc WHERE wc.user.login = ?#{principal.username} OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true'")
    Page findAll(Pageable pageable);

    @Query("SELECT wc FROM WidgetConfig wc WHERE (wc.user.login = ?#{principal.username} OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true') AND wc.type = :type")
    Page findAllByType(@Param("type") String type, Pageable pageable);

    @Modifying
    // @Query("DELETE FROM WidgetConfig wc WHERE ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true' AND wc.uid = :uid")
    @Query("DELETE FROM WidgetConfig wc WHERE (wc.user.login = ?#{principal.username} OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true') AND wc.uid = :uid")
    int deleteByUid(@Param("uid") String uid);

}

编辑:将 Hibernate 日志记录设置为 DEBUG 的日志输出

2017-08-17 11:03:52.845 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Enter: gov.nasa.gsfc.gmsec.gss.portal.web.rest.WidgetConfigResource.deleteWidgetConfig() with argument[s] = [dashboard-1502982221773]
2017-08-17 11:03:52.845 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.w.rest.WidgetConfigResource  : REST request to delete WidgetConfig : dashboard-1502982221773
2017-08-17 11:03:52.856 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.spi.AbstractTransactionImpl      : begin
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.j.internal.LogicalConnectionImpl   : Obtaining JDBC connection
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.j.internal.LogicalConnectionImpl   : Obtained JDBC connection
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.internal.jdbc.JdbcTransaction    : initial autocommit status: true
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.internal.jdbc.JdbcTransaction    : disabling autocommit
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Enter: gov.nasa.gsfc.gmsec.gss.portal.service.WidgetConfigService.deleteByUid() with argument[s] = [dashboard-1502982221773]
2017-08-17 11:03:52.857 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.s.i.WidgetConfigServiceImpl  : Request to delete WidgetConfig : dashboard-1502982221773
2017-08-17 11:03:52.863 DEBUG 8725 --- [nio-8080-exec-5] org.hibernate.SQL                        : delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?
Hibernate: delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?
2017-08-17 11:03:52.866 DEBUG 8725 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : could not prepare statement [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]

org.h2.jdbc.JdbcSQLException: Syntax error in SQL statement "DELETE FROM WIDGET_CONFIG CROSS[*] JOIN JHI_USER USER1_ WHERE (LOGIN=? OR ?='true') AND UID=? "; SQL statement:
delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=? [42000-194]
        at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)

(large stack trace left out for brevity)

2017-08-17 11:03:52.892  WARN 8725 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 42000, SQLState: 42000
2017-08-17 11:03:52.892 ERROR 8725 --- [nio-8080-exec-5] o.h.engine.jdbc.spi.SqlExceptionHelper   : Syntax error in SQL statement "DELETE FROM WIDGET_CONFIG CROSS[*] JOIN JHI_USER USER1_ WHERE (LOGIN=? OR ?='true') AND UID=? "; SQL statement:
delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=? [42000-194]
2017-08-17 11:03:52.909 DEBUG 8725 --- [nio-8080-exec-5] o.h.jpa.spi.AbstractEntityManagerImpl    : Mark transaction for rollback
2017-08-17 11:03:52.923 ERROR 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Exception in gov.nasa.gsfc.gmsec.gss.portal.service.WidgetConfigService.deleteByUid() with cause = 'org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement' and exception = 'org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement'
2017-08-17 11:03:52.923 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.spi.AbstractTransactionImpl      : rolling back
2017-08-17 11:03:52.924 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.internal.jdbc.JdbcTransaction    : rolled JDBC Connection
2017-08-17 11:03:52.924 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.t.internal.jdbc.JdbcTransaction    : re-enabling autocommit
2017-08-17 11:03:52.924  INFO 8725 --- [nio-8080-exec-5] i.StatisticalLoggingSessionEventListener : Session Metrics {
    155229 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    349635 nanoseconds spent preparing 1 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    29111 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
}
2017-08-17 11:03:52.924 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.j.internal.LogicalConnectionImpl   : Releasing JDBC connection
2017-08-17 11:03:52.925 DEBUG 8725 --- [nio-8080-exec-5] o.h.e.j.internal.LogicalConnectionImpl   : Released JDBC connection
2017-08-17 11:03:52.925 ERROR 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Illegal argument: [dashboard-1502982221773] in gov.nasa.gsfc.gmsec.gss.portal.web.rest.WidgetConfigResource.deleteWidgetConfig()
2017-08-17 11:03:52.925 ERROR 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Exception in gov.nasa.gsfc.gmsec.gss.portal.web.rest.WidgetConfigResource.deleteWidgetConfig() with cause = 'org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement' and exception = 'org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement'
2017-08-17 11:03:52.944 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Enter: gov.nasa.gsfc.gmsec.gss.portal.web.rest.errors.ExceptionTranslator.processRuntimeException() with argument[s] = [java.lang.IllegalArgumentException: org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement]
2017-08-17 11:03:52.949 DEBUG 8725 --- [nio-8080-exec-5] g.n.g.g.g.p.aop.logging.LoggingAspect    : Exit: gov.nasa.gsfc.gmsec.gss.portal.web.rest.errors.ExceptionTranslator.processRuntimeException() with result = <500 Internal Server Error,gov.nasa.gsfc.gmsec.gss.portal.web.rest.errors.ErrorVM@3caeaf1c,{}>
2017-08-17 11:03:52.951  WARN 8725 --- [nio-8080-exec-5] .m.m.a.ExceptionHandlerExceptionResolver : Resolved exception caused by Handler execution: java.lang.IllegalArgumentException: org.springframework.dao.InvalidDataAccessResourceUsageException: could not prepare statement; SQL [delete from widget_config cross join jhi_user user1_ where (login=? or ?='true') and uid=?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement

编辑:这是最终起作用的

根据 Cepr0 的回答,我想到了这个:


    @Modifying
    @Query("DELETE FROM WidgetConfig wc WHERE (wc.user = (SELECT u FROM User u WHERE u.login = ?#{principal.username}) OR ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true') AND wc.uid = :uid")
    int deleteByUid(@Param("uid") String uid);

我仍然有点不明白为什么这行得通,但我原来的方法行不通。

也许这可以帮助(未测试):

delete from WidgetConfig wc 
where 
    wc.user = (
        select u from User u where u.id = ?1 and ( 
            u.login = ?#{principal.username} or 
            ?#{hasRole('ROLE_ADMIN') ? 'true' : 'false'} = 'true'
        )
    )

(而且我不确定 'true' 和 'false' 周围是否需要单引号...)