使用命名查询时的 ManyToMany NOT NULL 检查约束

Posted

技术标签:

【中文标题】使用命名查询时的 ManyToMany NOT NULL 检查约束【英文标题】:ManyToMany NOT NULL check constraint when using Named query 【发布时间】:2013-11-05 12:35:51 【问题描述】:

我有一个实体 (Layer),它映射了其他实体 (Member) 的列表。此列表可能没有条目/为null。然而,当我查询实体时,我从数据库中得到一个NOT NULL check constraint 错误。 它似乎连接到NamedQueries,因为如果我通过id 查询,我可以从数据库中读取实体。

@Entity
@NamedQueries(
    @NamedQuery(name="getChildLayers",-
                query = "SELECT la
                         FROM Layer la
                         WHERE la.parent = :parent AND la.deletedDate IS NULL"))
public class Layer extends CommonModel 
    /*... other field */
    @ManyToOne(fetch = FetchType.LAZY, targetEntity = Layer.class, optional = true)
    private Layer parent;

    @ManyToMany(fetch = FetchType.LAZY, targetEntity = MyUser.class)
    private List<MyUser> members;
    public List<MyUser> getMembers() 
        return members;
    
    public void setMembers(List<MyUser> members) 
        this.members = members;
    
    /*... other getters and setters */

我收到此错误:integrity constraint violation: NOT NULL check constraint; SYS_CT_10298 table: LAYER_MYUSER column: MEMBERS_ID

不过,我可以创建条目。

当我运行测试时,所有读取实体的测试都失败(但创建工作)。如果我在创建方法中添加以下行:

  layer.setMembers(new ArrayList<MyUser>());

然后测试成员交替的方法起作用(意思是,我可以创建一个Layer 并通过在列表中添加和删除元素来更改它的members)。

在我看来,只要Layer 没有Member,从数据库中读取实体就会失败。

我确实尝试将 @JoinColumn(nullable=true) 添加到该字段,但它没有任何改变。

我导入 javax.persistence 类。

关于如何访问变量的示例(在 LayerService 中)

// this method works as expected
public Layer getById(Long id) 
    Session s = sessionFactory.getCurrentSession();
    return (Layer)s.get(Layer.class, id);


// this does not.
public List<Layer> getChildren(Layer layer) 
    Query childrenQuery = sessionFactory.getCurrentSession().getNamedQuery("getChildLayers");
    childrenQuery.setParameter("parent", layer);

    return (List<Layer>) childrenQuery.list();


Jason Cs 回答后代码更改:

Layer

...
private final List<OCWUser> members = new ArrayList<>();
...
public void setMembers(List<OCWUser> members) 
    this.members.clear();
    this.members.addAll(members);

问题依然存在。

【问题讨论】:

您是否手动创建表? @frostjogla 不,表格是自动创建的。 Hibernate 使用列“MEMBERS_ID BIGINT NOT NULL”和“MYUSER_ID BIGINT NOT NULL”docs.jboss.org/hibernate/orm/3.3/reference/en/html/… 创建“LAYER_MYUSER”表。尝试从列中手动删除“非空”约束。 @frostjogla 我宁愿不必手动修改数据库。必须有不同的方式。 【参考方案1】:

可以这么简单。我忘了加@JoinTable

@JoinTable(name = "LAYER_USER", joinColumns = @JoinColumn(nullable = true))

【讨论】:

【参考方案2】:

需要注意的重要一点是,您不应将 this.members 替换为 setMembers 中的另一个列表,除非您在致电 persist() 之前知道您正在这样做。相反,您需要清除 this.members 然后将所有指定的元素添加到其中。原因是 Hibernate 在 [反] 序列化实体时可以并且将使用其自己的代理/检测的集合类,而在覆盖集合类时您将其吹走。您应该将members 声明为final,并始终将其初始化为非空空List

参见示例(3.6 但仍然相关):http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/collections.html#collections-persistent,特别是:

请注意示例 7.2,“使用 @OneToMany 和 @JoinColumn”实例变量部分被初始化为 HashSet 的实例。这是初始化集合的最佳方式 新实例化(非持久)实例的值属性。 当你使实例持久化时,通过调用persist(),Hibernate 实际上会用 Hibernate 自己的实例替换 HashSet Set 的实现。

只要你以这种方式处理集合字段,任何数量的奇怪事情都可能发生。


此外,一般来说,在以这种方式访问​​集合时,您要小心声明不变量等,因为很容易创建两个在内部引用同一集合的 Layers,因此对其中一个的操作会影响传入集合上的其他或外部操作会影响层,例如以下代码的行为可能不像您希望的那样:
List<MyUser> u = new ArrayList<MyUser>();
Layer a = new Layer();
Layer b = new Layer();

u.add(...);
a.setMembers(u);
b.setMembers(u);
u.clear();

此外,当您 persist() 那里的其中一个层时,Hibernate 用它自己的集合类覆盖该字段,然后行为会随着对象不再引用同一个集合而改变:

// not only did u.clear() [possibly undesirably] affect a and b above, but:
session.persist(a);
u.add(...); // ... now it only affects b.

【讨论】:

好的,我会解决的。你有一些官方文档的链接吗? 是的;我已经更新了文档链接。我链接到 3.6,但对于您使用的任何版本的 Hibernate,它将位于同一部分。此外(更新)例外情况是,如果您知道要在 之前设置集合成员,则在新对象上调用 persist() 抱歉,我只有这些了。 顺便说一句,是的,您的修改测试可能会发生变化,但是您在使用Layer 时没有明确说明您的不变量。请参阅我在上面的答案中添加的内容。你真的想要在层之间共享内部集合实例吗?如果您这样做,您的行为在您持久化并重新加载对象后发生变化(因为它们将不再共享对同一集合的引用)。你真的应该重新考虑一下。 感谢您的支持。我想通了(我忘了@JoinTable)。

以上是关于使用命名查询时的 ManyToMany NOT NULL 检查约束的主要内容,如果未能解决你的问题,请参考以下文章

用于 ManyToMany 的 Symfony Doctrine 重命名表

Hibernate 命名查询 - 连接 3 个表

Spring Data JPA:查询ManyToMany,如何从映射类中获取数据?

spring jpa ManyToMany 理解和使用

django - manytomany 上的查询过滤器为空

Django ManyToMany 查询交集