JPA: 如何提高一对多关系持久化的性能

3

我有两个类:

@Entity
@Table(name="folder")
public class Folder{
@Id
public String reference;
@OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true)
public List<Client> clients= new ArrayList<>();
public Date createDate;
}

还有第二个类:

@Entity
@Table(name = "client")
public class Client implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    public int id;
    public String name;
}

在我的数据库中,我创建了一个中间表。

 create table folder_clients (folder_ref varchar(20) not null, clients_id int not null)
    alter table folder_clients add constraint UK_clientid unique (clients_id)
    alter table folder_clients add constraint FK_idClient foreign key (clients_id) references client
    alter table folder_clients add constraint FK_refFolder foreign key (folder_ref) references folder

现在我有一个持久化文件夹的服务,因此自动持久化所有与之相关的客户端,并且这是通过文件夹储存库实现的:

 folder.getClients().add(client);
 folderRepository.save(folder);

一切都运行良好,但当我执行SQL Profiler时,发现它执行了许多语句,这会影响性能。

有没有更好的方法来改进我的代码,以减少Hibernate执行的语句数量并提高性能?

谢谢。


想要解释一下这行代码的作用 List<Client> clients= new ArrayList<>(); 如果 Client 不是一个实际的类? - XtremeBaumer
抱歉,这是一个错误。我进行了编辑,第二个类应该是客户端(Client)。 - METTAIBI
你需要插入/更新与文件夹连接的每个客户端。如果是插入操作,你无法减少语句数量。对于更新操作,取决于你要更新什么以及如何确定谁会被更新。我认为你在理解 SQL 可能性和不可能性方面存在一些问题。 - XtremeBaumer
原则上,您可以通过使用@JoinColumn来摆脱中间表。这将减少写入次数,但可能会(或可能不会,具体取决于您的用例)对查询性能和/或更新产生负面影响。 - crizzis
你为什么创建了一个中间表?能否添加 Sql Profiler 日志以显示生成的查询语句?你还可以设置 Hibernate 日志级别来检查生成的查询语句。 - O.Badr
2个回答

4
在这种情况下,客户端和文件夹之间存在多对多关联还是一对多关联?
如果是一对多关联,我建议您使用双向映射。因为在这种情况下,您不需要第三个表格。 因此(简言之),Hibernate 会生成更少的查询并且性能将提高。
@Entity
@Table(name="folder")
public class Folder {
    @Id
    private String reference;

    @OneToMany(mappedBy="folder", cascade = {CascadeType.ALL}, orphanRemoval = true)
    private List<Client> clients= new ArrayList<>();

    private Date createDate;

    //getters and setters
}


@Entity
@Table(name = "client")
public class Client implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "folder_id")
    private Folder folder;

    //getters and setters
}

请看这篇关于@OneToMany关系的精彩文章:https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/。但如果你的情况是多对多,请参考:https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/

1
你不需要使关系是双向的,就可以摆脱中间表,见上面我的评论。 - crizzis
为了避免中间表,是的,你是正确的。但是双向关联可以提高性能。请参阅此链接:https://vladmihalcea.com/2017/03/29/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/而且,这篇文章是关于性能的。 - Ady Junior
你是对的,我的错。值得注意的是,首先插入子实体,然后更新外键的行为是供应商特定的:Hibernate和EclipseLink遵循这种方法,而DataNucleus和OpenJPA则不遵循。 - crizzis

0

将一对多关系双向化可以帮助并且默认情况下是急切获取。在许多情况下,您需要急切获取,但是使类型获取变为急切获取只会执行n*m(n个父记录一对多和M是映射的许多子记录的数量)查询。

解决方案非常简单,只需在JPA存储库中添加实体图即可使用JPA 2.1

@Repository
public interface FolderRepo extends JpaRepository< Folder, Long> {

@EntityGraph(attributePaths = {"clients")
List<Folder> findAll();
}

这将创建“文件夹->客户端”双向映射之间的连接

如果您没有为这些实体类中的每个覆盖带有ID的equals方法,则需要在引用中进行更改,否则这将不起作用。 如果您已添加,则可以摆脱不需要的每个实体类中的复杂多级NamedEntityGraph。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接