Spring Boot 4 02 简单写写WEB

Spring Boot 4 02 简单写写WEB

2-5章以前没有注意的点记录

01 API版本

Spring Boot 4 自带了内置的版本支持,现在有四种方式:

  1. URL参数

  2. 请求头中

  3. 查询参数

  4. 媒体类型

这个知道就好,需要用的时候可以方便使用。

02 编写 null-safe 的应用

Spring Boot 4 开始全面采用JSpecify的注解机制,有如下几个注解

@NullMarked

这个表示默认值非空,可以加在包,类和模块上,表示该类的所有作用域都是非空的。简单的说,不允许有空值。

@NullUnmarked

这个表示默认值不是非空,即可能是空。主要是为了兼容性考虑。

@NonNull

这个表示某个方法或者具体变量不能是null,加在具体的参数上,如果是在@NullMarked 影响的范围内是多余的,但可读性更好。

@Nullable

这个明确指出某个值可以为空。

实际使用

实际使用的时候,@NullMarked 和@NullUnmarked 加在类或者包上,而@NonNull 和@Nullable 用于在整体的规则下的特殊例子。

@NullMarked package com.learningspringboot4;

这个表示com.learningspringboot4中所有类型默认都不再是空值。

泛型如果使用的时候,也可以标记在泛型参数上。

注意这些注解都是给集成开发环境看的,并不会真正影响代码。

Query BY Example

如果查询条件是动态的,可以向JPA提供一个对象,对象包含感兴趣的字段,忽略不需要的字段。

使用Query BY Example 的方法是:

  1. 创建一个Probe,这个Probe就是要查询的实例

  2. 将筛选条件填入想要查询的字段,不填入的保留null

  3. 将探针封装成一个Example对象

  4. 在Repository中编写自定义方法

VideoEntity probe = new VideoEntity(); 
probe.setName(partialName); 
probe.setDescription(partialDescription); 
probe.setTags(partialTags); 
Example<VideoEntity> example = Example.of(probe);

其中最后一行是最简单的例子,of的第二个参数可以传入规则,默认情况下,即文中的写法,是完全匹配所有非空字段。

如果想要实现按照字符串contain来查询所有字段,编写如下规则:

Example<VideoEntity> example = Example.of(probe,
  ExampleMatcher.matchingAll()
    .withIgnoreCase()
    .withStringMatcher(StringMatcher.CONTAINING));

matchingAll表示匹配所有非空字段,相当于And,如果匹配特定的字段,就用matchingAny(),就相当于Or。

withIgnoreCase() 是忽略大小写。

withStringMatcher() 会应用 CONTAINING 过滤器,在所有非null列上进行Like匹配。

在创建完Example对象后,就可以把这个对象交给JPA接口的方法进行查询。

03 自定义JPQL语句

可以直接通过@Query注解来编写JPQL语句,此时优先解析注解,而不是根据方法名称来进行查询。

使用这种方法就可以绕过JPA接口的所有内置查询方法,但有两个方法依然可以作为参数被解析,就是分页和排序,这是一种便利。

JPA的所有模块几乎都有对应的语句,因为是SQL所以写JPQL,实际上还有CQL,N1QL等等。

对于复杂的查询,可以在@Query中拼接字符串:

@Query("select v FROM VideoEntity v " 
    + "JOIN v.metrics m " 
    + "JOIN m.activity a " 
    + "JOIN v.engagement e " 
    + "WHERE a.views < :minimumViews " 
    + "OR e.likes < :minimumLikes")
List<VideoEntity> findVideosThatArentPopular( 
    @Param("minimumViews") Long minimumViews, 
    @Param("minimumLikes") Long minimumLikes);

相比直接编写原生SQL,这种方式具备可移植性。

这里的参数就不是位置参数了,而是通过命名直接和具体参数绑定,这样方法的参数位置也可以固定,更易于使用。

@Query(value="select * from VIDEO_ENTITY where NAME = ?1", nativeQuery=true) 
List<VideoEntity> findCustomWithPureSql(String name);

当JPQL语句也无法满足要求时,只需要在@Query中增加nativeQuery=true,就可以编写原生SQL代码,因为复杂的SQL代码使用ORM映射并不是一个好的方式,SQL会更加简单和易读。但要注意的是,原生SQL代码不支持动态排序,必须添加SORT子句自己进行排序,使用pageable参数也比较麻烦,因此轻量级的查询可以优先考虑更简易的方式。

04 方法级更灵活的权限控制

@PreAuthorize("#entity.username == authentication.name")
@Override
void delete(VideoEntity entity);


// 注意一定要搭配这个注解使用,才能开启方法级的权限
@EnableMethodSecurity

PreAuthorize可以直接解引用全局命名空间中的变量,SPEL表达式非常灵活。在 @PreAuthorize 的 SpEL 表达式中,除了你例子中的 #entity(方法参数)和 authentication,还可以直接使用以下几类内置变量和对象。

核心内置变量

这些是 Spring Security 在求值表达式时默认提供的根对象属性或特定变量:

  • authentication:当前存储在安全上下文中的 Authentication 对象。你可以通过它访问 name(用户名)、authorities(权限)等属性。

  • principal:直接访问 Authentication 对象中的 principal(主体)。通常它是 UserDetails 实例,因此你可以直接使用 principal.username 或 principal.enabled 等属性。

  • 方法参数(通过 # 引用):如你例子中的 #entity。只要参数名能被解析(通过 -parameters 编译参数、调试信息或 @P/@Param 注解),你就能在表达式中通过 #参数名 直接引用它。

  • returnObject:仅在 @PostAuthorize 或 @PostFilter 中可用。它代表方法执行后的返回值,你可以在表达式中引用它来做后续检查。

其他可用对象与函数

  • Spring Bean(通过 @ 引用):你可以通过 @beanName 的形式调用容器中任何 Bean 的方法。这在需要执行复杂逻辑时非常有用,例如 @PreAuthorize("@mySecurityService.check(#id)")。

  • 内置权限函数:Spring Security 提供了一系列返回布尔值的内置方法,如 hasRole('USER')、hasAnyRole(...)、hasAuthority(...)、isAuthenticated()、isAnonymous() 等,可以直接在表达式中调用。

补充说明

filterObject 是在 @PreFilter / @PostFilter 中使用的特殊变量,它代表正在被过滤的集合中的当前元素,不适用于 @PreAuthorize。

LICENSED UNDER CC BY-NC-SA 4.0
Comment