01 API版本
Spring Boot 4 自带了内置的版本支持,现在有四种方式:
URL参数
请求头中
查询参数
媒体类型
这个知道就好,需要用的时候可以方便使用。
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 的方法是:
创建一个Probe,这个Probe就是要查询的实例
将筛选条件填入想要查询的字段,不填入的保留null
将探针封装成一个Example对象
在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);
// 注意一定要搭配这个注解使用,才能开启方法级的权限
@EnableMethodSecurityPreAuthorize可以直接解引用全局命名空间中的变量,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。