直接在 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 中将行插入连接表?的主要内容,如果未能解决你的问题,请参考以下文章