繁体   English   中英

JPA 和 H2 数据库的树数据结构

[英]Tree Data Structure with JPA and H2 Databse

我有一个表示树数据结构的实体 class。

使用H2 数据库引擎v1.4.200 时,我可以毫无问题地保留实体。

但是,当迁移到H2的最新版本 (v2.1.214) 时,它会因为违反primary key而抛出异常。 似乎是将parent项和root与实体一起保存(请参阅下面的例外情况)。

知道H2从 v1.4.200 到 v2.1.214 有什么变化可能会影响实体的持久性吗?

节点 java

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Transient;

@Entity
public class Node {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "parent_id")
    @JsonIgnore
    private Node parent;
    
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "root_id")
    @JsonIgnore
    public Node root;

    @Transient
    public List<Node> children = new ArrayList<>();
}

NodeController.java

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/node")
public class NodeController {

    private NodeService nodeService;

    public NodeController(NodeService nodeService) {
        this.nodeService = nodeService;
    }

    @PostMapping("/save")
    public ResponseEntity<?> create(@RequestBody Node node) {
        Node entity = nodeService.save(node);
        return new ResponseEntity<>(entity, HttpStatus.CREATED);
    }

}

例外

o.h.engine.jdbc.spi.SqlExceptionHelper:
    Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.NODE(ID) ( /* key:1 */ CAST(1 AS BIGINT), 'Node 1', NULL, NULL)"; SQL statement:
    insert into node (id, name, parent_id, root_id) values (default, ?, ?, ?) [23505-214]

.m.m.a.ExceptionHandlerExceptionResolver:
    Resolved [org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["PRIMARY KEY ON PUBLIC.NODE(ID) ( /* key:1 */ CAST(1 AS BIGINT), 'Node 1', NULL, NULL)"; SQL statement:<EOL>insert into node (id, name, parent_id, root_id) values (default, ?, ?, ?) [23505-214]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement]

标识列有一个生成器,可以为每个新行生成值。 如果标识列声明为默认生成,则可以为其指定一个自己的值,在这种情况下不使用生成器。 如果指定值来自生成值的范围,则它可能与此生成器生成的值冲突。 例如,

CREATE TABLE TEST(
    ID BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
    VAL INTEGER
);
INSERT INTO TEST(ID, VAL) VALUES (1, 10);
INSERT INTO TEST(ID, VAL) VALUES (DEFAULT, 20); // <- generator also returns 1

为避免这种情况,您应该避免将自定义值插入到这些列中,始终指定DEFAULT或不要将这些列包含到INSERT命令的列列表中。 如果这不是一个选项,您需要在此类插入后自行调整生成器:

INSERT INTO TEST(ID, VAL) VALUES (1, 10);
INSERT INTO TEST(ID, VAL) VALUES (2, 11);
// ...
ALTER TABLE TEST ALTER COLUMN ID RESTART WITH (SELECT MAX(ID) FROM TEST) + 1;

您还可以为标识列指定一个自定义间隔(例如GENERATED BY DEFAULT AS IDENTITY(START WITH 1000) ,并使用此间隔之外的值来插入自定义值。


历史上不受支持的 H2 数据库版本不会出现此问题,因为它们在插入自定义值后错误地执行了生成器的自动调整,此行为是从 MySQL 的自动增量列中复制的。

H2 2.*.* 实现 SQL 标准中的标识列,并且不会自行调整其生成器(不包括某些为了与其他数据库系统兼容而执行此类调整的兼容模式)。

自动调整实际上是不安全的,因为具有有限访问权限的用户可以仅插入一个特殊值就耗尽身份列的生成器,并用生成的值中断后续插入。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM