【问题标题】:Insert rows into a join table directly in JPA / JPQL?直接在 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 ,数据的新读取应该返回所有行。假设数据模型是一对多而不是单链接表一

标签: java jpa jpa-2.0 jpql spring-data-jpa


【解决方案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 不是最新的对象。
猜你喜欢
  • 2016-08-17
  • 1970-01-01
  • 2016-03-10
  • 2011-06-27
  • 1970-01-01
  • 1970-01-01
  • 2017-11-28
  • 2012-11-09
  • 2021-11-01
相关资源
最近更新 更多