当您保留一个新实体或使用 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语句中的id,first_name和last_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 语句。这会减慢此实体类对象的所有插入或更新操作。