直接在 JPA / JPQL 中将行插入连接表?

Posted

技术标签:

【中文标题】直接在 JPA / JPQL 中将行插入连接表?【英文标题】:Insert rows into a join table directly in JPA / JPQL? 【发布时间】:2014-08-09 11:29:45 【问题描述】:

在 JPA 中有没有办法将行直接插入到连接表中?我有以下合同和附件实体:

public class Contract implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Version
    @Column(name = "version")
    private Integer version;

    private String number;
    private String volume;

    @ManyToMany
    @JoinTable(joinColumns = @JoinColumn(name = "contract_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "attachment_id", referencedColumnName = "id"))
    private List<Attachment> attachments;

Attachment实体:

public class Attachment implements Serializable 

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id")
    private Long id;

    @Version
    @Column(name = "version")
    private Integer version;

    private String name;

由于线程并发问题,我想直接在合约中添加附件,而不需要加载合约,将附件添加到列表中并持久化/保存合约,因为合约对象中没有数据正在更新本身。更详细一点,我有多个线程并行保存附件,如果我要在所有这些线程中保留合同,我需要进行某种并发控制以避免 OptimisticLockExceptions。鉴于 Contract 实体没有被修改(只有连接表),我不想陷入瓶颈,只直接更新连接表。

在 JPA 和/或 JPQL 中有没有办法将行直接插入连接表?工作流程是持久化附件,然后将适当的行插入到连接表中。

有没有办法从实体中检索 JPA 生成的连接表名称?

DDL:

CREATE TABLE `contract` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `number` varchar(255) DEFAULT NULL,
  `volume` varchar(255) DEFAULT NULL,
  `version` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;


CREATE TABLE `attachment` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `version` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
) ENGINE=InnoDB AUTO_INCREMENT=197 DEFAULT CHARSET=utf8;


CREATE TABLE `contract_attachment` (
  `contract_id` bigint(20) NOT NULL,
  `attachment_id` bigint(20) NOT NULL,
  UNIQUE KEY `UK_27094bpt88d7bfngq5v52jrs` (`attachment_id`),
  KEY `FK_i1ycj6ewd536bky08t0jjh15d` (`contract_id`),
  CONSTRAINT `FK_i1ycj6ewd536bky08t0jjh15d` FOREIGN KEY (`contract_id`) REFERENCES `contract` (`id`),
  CONSTRAINT `FK_27094bpt88d7bfngq5v52jrs` FOREIGN KEY (`attachment_id`) REFERENCES `attachment` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

【问题讨论】:

你当然可以只保留一个Attachment,但是由于数据池和缓存,在重新启动它之前,你的程序可能看不到这个信息,就像你直接插入一条记录一样使用另一个客户端进入数据库。 @ScaryWombat - 因此需要/理由使用 JPQL(通过实体管理器)来更新连接表。 在执行Attachment 插入调用entityManager.getEntityManagerFactory().getCache().evictAll() 以刷新缓存之后。 @ScaryWombat 但问题是我必须持久化 Contract 对象,否则连接表将不会更新。但是,如果我持久化 Contract 对象,它将更新其版本(即使 Contract 表本身没有任何更改),因此另一个线程将收到 OptimisticLockException,因为版本号不再匹配。这就是为什么我正在寻找一种方法来更新连接表而不修改合同表。 因为Contract 表中没有任何实际需要更新的内容,并且如果数据未缓存,那么如果Attachment 链接到Attachment ,数据的新读取应该返回所有行。假设数据模型是一对多而不是单链接表一 【参考方案1】:

看起来您需要将级联属性添加到 @ManyToMany 注释。使用 PERSIST 或 ALL。 但这真的不是很清楚的描述,你说你不想加载合约,但是你将如何在不加载的情况下持久/保存合约?

如果你真的不想加载合约而只保留附件,你需要在附件中添加@ManyToMany 关系。然后添加合约(你仍然需要加载它,但你不需要让它保持最新)。应该是这样的:

@ManyToMany
@JoinTable(joinColumns = @JoinColumn(name = "attachment_id", referencedColumnName = "id"), inverseJoinColumns =  @JoinColumn(name = "contract_id", referencedColumnName = "id"))
private List<Contract> contracts;

【讨论】:

我有几个线程并行保存附件。因此,我无法加载/保留 Contract 实体,否则将遇到 OptimisticLockExceptions。鉴于在 Contract 对象中没有更新实际数据(并且仅在连接表中),我希望直接更新连接表。将关系放在 Attachment 对象中也不起作用,因为 Attachment 是“与实体无关的”——这意味着它可以在多个不同的实体中重用(类似于 Contract),并且将关系放在 Attachment 中会使 Attachment 了解它的“父级” " ... ...因此需要/希望能够直接访问连接表。 我不确定拥有“实体无关”是否是一个好策略,你应该只在持久层上使用 JPA 实体,所以我认为添加第二方关系和有合同和其他实体。它们将被延迟加载。在更严格的情况下,您可以保护访问者。根据我的经验,您不需要更新映射的两侧,如果您的合同不是最新的,那么将此合同添加到附件并持久化附件一切都会正常工作。合同之后的 Ofcouse 不是最新的对象。

以上是关于直接在 JPA / JPQL 中将行插入连接表?的主要内容,如果未能解决你的问题,请参考以下文章

JPA JPQL简介

使用 JPQL/HQL 在 JPA 中订购连接获取的集合

JPQL 内连接

JPA:返回多个实体的查询

JPA 多对多JPQL查询语句怎么写?

JPA学习(JPA_JPQL)