【问题标题】:Hibernate ignoring nullability of ManyToMany join columnHibernate 忽略 ManyToMany 连接列的可空性
【发布时间】:2013-11-23 18:34:38
【问题描述】:

我有以下实体:

@Entity
@AttributeOverrides({
    @AttributeOverride(name="id", column=@Column(name="person_id))
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
@Table(name="people")
public class Person {
    @Column(name="name")
    private String name;

    @ManyToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="friend_person_id", nullable=true)
    private List<Person> friends;

    @ManyToMany(cascade=CascadeType.ALL)
    @JoinColumn(name="enemy_person_id", nullable=true)
    private List<Person> enemies;

    // constructor, getters/setters, etc.
}

简单地说,Person 有一个名字和一个朋友列表(他们也是所有人)以及一个敌人列表(更多人)。因此Person 与自己有 2 个多对多关系:1 个为朋友,1 个为敌人。然而,一个人可能没有朋友和/或没有敌人。因此,我希望这些字段可为空

这是我的PersonDAO,它公开了可用于将人员持久保存到数据库的公共方法:

// Inside PersonDAO
public void savePerson(Person person) {
    Session session = getDaoUtils().newSessionFactory().openSession();
    Transaction transaction = null;

    try {
        transaction = session.beginTransaction();

        // Execute the query.
        session.saveOrUpdate(person);

        transaction.commit();
    } catch(Throwable throwable) {
        transaction.rollback();
        throw new RuntimeException(throwable);
    } finally {
        session.close();
    }
}

最后,我有一个小测试脚本来测试这一切:

public class Tester {
    public static void main(String[] args) {
        Person tom = new Person("Tom", null, null); // Tom has no friends/enemies.
        Person bob = new Person("Bob", null, null); // Bob also has no friends/enemies.

        List<Person> friends = new ArrayList<Person>();
        List<Person> enemies = new ArrayList<Person>();

        friends.add(tom);
        enemies.add(bob);

        // Randy's friend is Tom. Randy's enemy is Bob.
        Person randy = new Person("Randy", friends, enemies);

        PersonDAO personDAO = new PersonDAO();

        personDAO.savePerson(randy);
    }
}

当我运行它时,我得到以下异常(针对 H2 DB):

INFO: HHH000010: On release of batch it still contained JDBC statements
Exception in thread "main" java.lang.RuntimeException: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at net.myuser.myapp.common.dal.dao.PersonDAO.savePerson(PersonDAO.java:38)
    at net.myuser.myapp.tools.personloader.Tester.main(Tester.java:25)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:129)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:136)
    at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:58)
    at org.hibernate.persister.collection.AbstractCollectionPersister.recreate(AbstractCollectionPersister.java:1256)
    at org.hibernate.action.internal.CollectionRecreateAction.execute(CollectionRecreateAction.java:58)
    at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:377)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:369)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:292)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:339)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:52)
    at org.hibernate.internal.SessionImpl.flush(SessionImpl.java:1234)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:404)
    at org.hibernate.engine.transaction.internal.jdbc.JdbcTransaction.beforeTransactionCommit(JdbcTransaction.java:101)
    at org.hibernate.engine.transaction.spi.AbstractTransactionImpl.commit(AbstractTransactionImpl.java:175)
    at net.myuser.myapp.common.dal.dao.PersonDAO.savePerson(PersonDAO.java:30)
    ... 1 more
Caused by: org.h2.jdbc.JdbcSQLException: NULL not allowed for column "FRIENDS_PERSON_ID"; SQL statement:
/* insert collection row net.myuser.myapp.common.dal.dto.Person.enemies */ insert into persons_persons (persons_person_id, enemies_word_id) values (?, ?) [23502-173]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:331)
    at org.h2.message.DbException.get(DbException.java:171)
    at org.h2.message.DbException.get(DbException.java:148)
    at org.h2.table.Column.validateConvertUpdateSequence(Column.java:295)
    at org.h2.table.Table.validateConvertUpdateSequence(Table.java:699)
    at org.h2.command.dml.Insert.insertRows(Insert.java:123)
    at org.h2.command.dml.Insert.update(Insert.java:86)
    at org.h2.command.CommandContainer.update(CommandContainer.java:79)
    at org.h2.command.Command.executeUpdate(Command.java:235)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:154)
    at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:140)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:133)
    ... 14 more

所以看起来 Hibernate 正在尝试将多对多关系存储到 1 个表中?无论哪种方式,我都无法弄清楚它在做什么。运行此命令后,我在数据库中看到了 persons_persons 表,但它的 friends_person_idenemies_person_id 列都是 NOT NULLABLE 所以它忽略了我的 nullable=true 设置。

有人可以帮我正确注释我的实体并帮我解决这个问题吗?提前致谢!

【问题讨论】:

  • JoinTable 是存储多对多关联所必需的。使用 JoinColumn 没有意义。为每个关联指定不同的连接表。并且那些连接表不应该有任何列可以为空,因为这也没有任何意义:你不能成为 null 的朋友(或敌人)。
  • 在没有 JoinTable 注释的情况下扩展前一个,Hibernate 会为您创建一个 3 列的连接表:id、friend_id、enemy_id:因此对于friends_id 的错误为 null,反之亦然。
  • 感谢@JBNizet (+1) - 所以我想我应该使用类似@JoinTable 的东西?您介意提供一个具体示例来说明如何针对我的给定情况使用注释吗?再次感谢!

标签: java hibernate orm many-to-many nullable


【解决方案1】:
@Entity
@Table(name = "people")
public class Person {

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

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "friends", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "friend_id"))
    private List<Person> friends;

    @ManyToMany(cascade = CascadeType.ALL)
    @JoinTable(name = "enemies", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "enemy_id"))
    private List<Person> enemies;

    public Person() {
        friends = new ArrayList<>();
        enemies = new ArrayList<>();
    }

    /**
     * 
     * Optionally force client classes through your add/remove methods if mutual
     * relationship should be maintained.
     * 
     * @return
     */
    public List<Person> getFriends() {
        return Collections.unmodifiableList(friends);
    }

    /**
     * Optionally force client classes through your add/remove methods if mutual
     * relationship should be maintained.
     * 
     * @return
     */
    public List<Person> getEnemies() {
        return Collections.unmodifiableList(enemies);
    }

    /**
     * Ensures mutual relationship set correctly.
     * 
     * @param person
     */
    public void addFriend(Person person) {
        friends.add(person);
        person.friends.add(this);
    }

    /**
     * Ensures mutual relationship set correctly.
     * 
     * @param person
     */
    public void addEnemy(Person person) {
        enemies.add(person);
        person.enemies.add(this);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-04-11
    • 2014-10-23
    • 1970-01-01
    • 2015-03-20
    • 2018-09-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多