当您保留一个新实体或使用 Spring Data JPA 更新现有实体时,您可能已经意识到您始终在执行相同的 SQL 语句,该语句设置该实体映射的所有列。如果您只更新其属性之一,情况也是如此。

这是Hibernate提供的性能优化,Hibernate是Spring Data JPA默认使用的JPA实现。Hibernate试图避免检查实体的哪些属性已更改并为它们生成特定的SQL语句。相反,它在启动时为每个实体类生成 1 个 SQL UPDATE 和 1 个 SQL INSERT 语句,并在每次插入或更新操作中重用它。

一遍又一遍地重用相同的语句可以改进 Hibernate 的工作。但它也会产生一些副作用。如果仅更改大型实体类的 1 个属性,则这些语句会产生开销。如果需要审核对数据库表执行的所有更改,它们也会导致问题。在这些情况下,最好让 Hibernate 为每个操作生成一个新的 SQL INSERT 或 UPDATE 语句。

标准行为

但在我向您展示如何执行此操作之前,让我们快速浏览一下默认行为。在这里,您可以看到一个简单的ChessPlayer实体,该实体存储每个玩家的名字、姓氏出生日期id属性映射主键,其值由数据库序列生成。

@Entity
public class ChessPlayer {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;

private String firstName;
 
private String lastName;

private LocalDate birthDate;

...
}

我准备了一个标准存储库,它只扩展了Spring Data JPA的JpaRepository,并且没有添加任何自定义查询或其他功能。

public interface ChessPlayerRepository extends JpaRepository<ChessPlayer, Long> { }

我准备了一个测试用例,它可以在不设置他的出生日期属性的情况下保留一个新的ChessPlayer。在下一步中,我将修复名字中的拼写错误。这将触发一个额外的 SQL 更新语句。

ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);

p.setFirstName("Thorben");

正如您在日志输出中看到的,Hibernate执行了一个SQL INSERT和一个UPDATE语句来设置Chess_Player表的所有列。这包括设置为 null 的birth_date列。

11:33:15.505 DEBUG 19836 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:33:15.514 DEBUG 19836 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:33:15.547 DEBUG 19836 - – [           main] org.hibernate.SQL                        : insert into chess_player (birth_date, first_name, last_name, id) values (?, ?, ?, ?)
11:33:15.557 DEBUG 19836 - – [           main] org.hibernate.SQL                        : update chess_player set birth_date=?, first_name=?, last_name=? where id=?

冬眠@DynamicInsert

Spring Data JPA 充当 Hibernate 之上的一层。因此,您可以在实体类上使用Hibernate的所有专有映射注释。

如果要在持久化新实体对象时动态生成 SQL INSERT 语句,则需要使用 Hibernate 专有@DynamicInsert注释来批注实体类。

@Entity
@DynamicInsert
public class ChessPlayer {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;

private String firstName;
 
private String lastName;

private LocalDate birthDate;
 
...
}

然后,当您执行与以前相同的测试用例时,您将在日志输出中看到 Hibernate 仅使用在新实体对象上设置的属性动态生成 SQL INSERT 语句。

ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);

p.setFirstName("Thorben");

Hibernate只设置SQL INSERT语句中的idfirst_namelast_name列,而不设置birth_date列。Hibernate排除了该列,因为我在Spring Data JPA的存储库上调用save方法之前没有在测试用例中设置它。

11:37:20.374 DEBUG 7448 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:37:20.386 DEBUG 7448 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:37:20.427 DEBUG 7448 - – [           main] org.hibernate.SQL                        : insert into chess_player (first_name, last_name, id) values (?, ?, ?)
11:37:20.435 DEBUG 7448 - – [           main] org.hibernate.SQL                        : update chess_player set birth_date=?, first_name=?, last_name=? where id=?

但是 SQL UPDATE 语句仍会更新ChessPlayer实体类映射的所有列。如果要更改它,还需要使用 @DynamicUpdate 注释实体类。

冬眠@DynamicUpdate

与上一节中描述的@DynamicInsert注解一样,@DynamicUpdate注解告诉Hibernate为每个更新操作生成特定的SQL UPDATE语句。执行此操作时,Hibernate会检测哪些属性已更改,并且仅将这些属性包含在SQL语句中。

在下面的示例中,我使用 Hibernate 的@DynamicInsert@DynamicUpdate注释注释了ChessPlayer实体。

@Entity
@DynamicInsert
@DynamicUpdate
public class ChessPlayer {

@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_seq")
@SequenceGenerator(name = "player_seq", sequenceName = "player_sequence")
private Long id;

private String firstName;
 
private String lastName;

private LocalDate birthDate;
 
...
}

让我们执行与前面示例中相同的测试用例。

ChessPlayer p = new ChessPlayer();
p.setFirstName("Torben");
p.setLastName("Janssen");
chessPlayerRepository.save(p);

p.setFirstName("Thorben");

正如您在日志输出中看到的,Hibernate现在生成了特定的SQL INSERT和UPDATE语句。

11:39:45.177 DEBUG 13832 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:39:45.185 DEBUG 13832 - – [           main] org.hibernate.SQL                        : select nextval ('player_sequence')
11:39:45.214 DEBUG 13832 - – [           main] org.hibernate.SQL                        : insert into chess_player (first_name, last_name, id) values (?, ?, ?)
11:39:45.224 DEBUG 13832 - – [           main] org.hibernate.SQL                        : update chess_player set first_name=? where id=?

我们已经在上一节中讨论了 INSERT 语句,因此让我们专注于更新操作。

在测试用例中,我只更改了firstName属性的值。Hibernate在对该实体对象执行脏检查时认识到了这一点。基于此,Hibernate随后生成了一个SQL UPDATE语句,该语句仅更改first_name列中的值。

结论

Spring Data JPA充当JPA实现之上的一层。在大多数情况下,这就是休眠。当您持久化或更新实体对象时,Spring Data JPA 将该操作委托给 JPA 实现。因此,所有写入操作的处理和 SQL 语句的生成取决于您的 JPA 实现及其功能。

默认情况下,Hibernate不会为每个实体对象生成特定的SQL INSERT或UPDATE语句。相反,它在应用程序启动时为每个实体类生成 1 个 INSERT 和 1 个 UPDATE 语句,并在所有插入或更新操作中重用它们。这减少了这些操作的开销,但也更改了数据库中的太多列。

如果这是一个问题,您可以使用 @DynamicInsert 和 @DynamicUpdate 来批注实体类。这些专有注释告诉Hibernate为每个实体对象动态生成SQL INSERT或UPDATE语句。执行此操作时,请记住,您不会免费获得此功能,也无法针对特定用例激活或停用它。要生成特定的 UPDATE 或 INSERT 语句,Hibernate 需要检测哪些属性发生了变化,并根据此信息生成新的 SQL 语句。这会减慢此实体类对象的所有插入或更新操作。