概述
本教程将探讨在Spring Data JPA的衍生查询API中,findBy
和 findAllBy
方法命名约定的区别。
1. 引言
Spring Data JPA支持基于方法名称的衍生查询。这意味着如果我们使用特定的关键字在方法名中,就不需要手动指定查询语句。
find
和 By
关键字一起工作,根据规则生成一个搜索集合结果的查询。请注意,这两个关键字返回的结果形式都是集合,这可能导致对findAllBy
的使用产生混淆。在Spring Data文档中,并没有定义All
关键字(https://docs.spring.io/spring-data/jpa/reference/repositories/query-keywords-reference.html`_)。
接下来的章节我们将验证findBy
和 findAllBy
在Spring Data JPA中并无差异,并提供一种替代方法来查找单个结果而不是集合。
1.1 示例应用
首先,我们定义一个示例Spring Data应用。然后创建Player
实体类:
@Entity
public class Player {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private Integer score;
//all-arg and no-arg constructors
//overriden equals method
//getters and setters
}
接下来,创建扩展自JpaRepository
接口的PlayerRepository
接口:
@Repository
public interface PlayerRepository extends JpaRepository<Player, Long> {
}
1.2 findBy
查询示例
如前所述,findBy
关键字使用规则返回一个集合的结果。这个规则位于By
关键字之后。让我们在PlayerRepository
类中创建一个方法,衍生出一个查询,找到分数高于给定输入的所有玩家:
List<Player> findByScoreGreaterThan(Integer target);
Spring Data JPA会解析方法名称语法并转换成SQL语句来生成查询。让我们看看每个关键字的作用:
-
find
被翻译成select
语句。 -
By
被解析为where
子句。 -
Score
是表列名,应与Player
类中定义的相同。 -
GreaterThan
在查询中添加>
操作符,将score
字段与方法参数target
进行比较。
1.3 findAllBy
查询示例
与findBy
类似,我们在PlayerRepository
类中创建一个带有All
关键字的方法:
List<Player> findAllByScoreGreaterThan(Integer target);
这个方法的工作方式类似于findByScoreGreaterThan()
方法——唯一的区别是All
关键字。这个关键字只是一个命名约定,并不会为衍生查询增加任何功能,我们将在下一节中看到这一点。
2. findBy
vs. findAllBy
现在,让我们验证findBy
和findAllBy
之间的差异仅限于命名约定,并证明它们在功能上是相同的。
2.1 功能性差异
为了分析两者之间是否存在功能上的差异,让我们编写一个集成测试:
@RunWith(SpringRunner.class)
@SpringBootTest(classes = FindByVsFindAllByApplication.class)
public class FindByVsFindAllByIntegrationTest {
@Autowired
private PlayerRepository playerRepository;
@Before
public void setup() {
Player player1 = new Player(600);
Player player2 = new Player(500);
Player player3 = new Player(300);
playerRepository.saveAll(Arrays.asList(player1, player2, player3));
}
@Test
public void givenSavedPlayer_whenUseFindByOrFindAllBy_thenReturnSameResult() {
List<Player> findByPlayers = playerRepository.findByScoreGreaterThan(400);
List<Player> findAllByPlayers = playerRepository.findAllByScoreGreaterThan(400);
assertEquals(findByPlayers, findAllByPlayers);
}
}
注意:为了使测试通过,Player
实体必须重写equals()
方法,比较id
和score
字段。
两个方法返回的结果与assertEquals()
显示的一样,因此在功能上没有区别。
2.2 查询语法差异
为了完整起见,让我们比较由两种方法生成的查询的语法。为此,我们需要在application.properties
文件中添加以下行:
spring.jpa.show-sql=true
如果重新运行集成测试,两个查询应该会出现在控制台。这是findByScoreGreaterThan()
的衍生查询:
select
player0_.id as id1_0_, player0_.score as score2_0_
from
player player0_
where
player0_.score>?
而findAllByScoreGreaterThan()
的衍生查询:
select
player0_.id as id1_0_, player0_.score as score2_0_
from
player player0_
where
player0_.score>?
我们可以看到,生成的查询语法没有差异。因此,在代码风格上,findBy
和findAllBy
关键字的唯一区别在于我们选择采用哪种方式。我们可以使用任意一个,期望得到相同的结果。
3. 返回单个结果
我们已经确认了findBy
和findAllBy
之间的差异仅限于命名约定,两者都返回集合结果。如果我们改变接口,从这些可能返回多个结果的生成查询中获取单个结果,我们可能会遇到NonUniqueResultException
。
在这个部分,我们将查看find**F**irst
和find**T**op
关键字,以衍生出一个查询,返回单个结果。
**F**irst
和T**op
关键字应插入到find
和By
关键字之间,以查找存储的第一个元素。它们也可以与I**sGreaterThan
等条件关键字一起使用。让我们看一个例子,找到分数大于400的存储的第一个玩家。首先,我们在PlayerRepository
类中创建查询方法:
Optional<Player> findFirstByScoreGreaterThan(Integer target);
Top
关键字在功能上等于First
。它们之间的主要区别在于命名约定。因此,我们可以通过名为findTopByScoreGreaterThan()
的方法实现相同的结果。
然后,我们验证只获取了一个单一的结果:
@Test
public void givenSavedPlayer_whenUsefindFirst_thenReturnSingleResult() {
Optional<Player> player = playerRepository.findFirstByScoreGreaterThan(400);
assertTrue(player.isPresent());
assertEquals(600, player.get().getScore());
}
findFirstBy
查询使用limit
SQL运算符返回第一个匹配我们条件的存储元素,即具有id
=1和score
=600的Player
。
最后,让我们看看由我们的方法生成的查询:
select
player0_.id as id1_0_, player0_.score as score2_0_
from
player player0_
where
player0_.score>?
limit ?
查询几乎与findBy
和findAllBy
相同,只是在末尾添加了limit
运算符。
4. 结论
在这篇文章中,我们探索了Spring Data JPA中findBy
和findAllBy
关键字的相似之处。我们还研究了如何使用findFirstBy
关键字获取单个结果。如往常一样,源代码可在GitHub上获取。