使用合并但不持久时,约束违例(NOT NULL)

时间:2015-04-13 20:57:45

标签: hibernate jpa entitymanager

使用JPA时,我遇到了一些意想不到的行为。

在我的数据模型中,我有两个实体,FooBarFooBar实际上有多对多关系 - 但BarFoo的每次出现都包含其他信息,这些信息已导致我们避免使用简单的JoinTable方法。因此,我们使用另一个实体FooBar来表示BarFoo的出现。这是一些例子。 (请注意,除了关系成员/方法之外,我已经省略了所有内容。)

@Entity
public class Foo
{
   @OneToMany(mappedBy = FooBar.FOO,
              orphanRemoval = true,
              fetch = FetchType.LAZY,
               cascade = { CascadeType.ALL })
   private Set<FooBar> fooBars = new HashSet<>();

   public void addFooBar(Collection<FooBar> fooBars)
   {
       Set<FooBar> previous = new HashSet<>(this.fooBars);
       this.fooBars.addAll(fooBars);
       for ( FooBar fooBar : fooBars )
       {
           if ( !previous.contains(fooBar) )
           {
               fooBar.setFoo(this);
           }
       }
   }

   public void removeFooBar(FooBar ... fooBars)
   {
       removeFooBar(Arrays.asList(fooBars));
   }

   public void removeFooBar(Collection<FooBar> fooBars)
   {
       Set<FooBar> previous = new HashSet<>(this.fooBars);
       this.fooBars.removeAll(fooBars);
       for ( FooBar fooBar : fooBars )
       {
           if ( previous.contains(fooBar) )
           {
               fooBar.setFoo(null);
           }
       }
   }

   public Set<FooBar> getFooBars()
   {
       return Collections.unmodifiableSet(fooBars);
   }
}

@Entity
public class Bar
{
   @OneToMany(mappedBy = FooBar.BAR,
              orphanRemoval = true,
              fetch = FetchType.LAZY,
              cascade = { CascadeType.ALL })
   private Set<FooBar> fooBars = new HashSet<>();


   public void addFooBar(FooBar ... fooBars)
   {
       addFooBar(Arrays.asList(fooBars));
   }

   public void addFooBar(Collection<FooBar> fooBars)
   {
       Set<FooBar> previous = new HashSet<>(this.fooBars);
       this.fooBars.addAll(fooBars);
       for ( FooBar fooBar : fooBars )
       {
           if ( !previous.contains(fooBar) )
           {
               fooBar.setBar(this);
           }
       }
   }

   public void removeFooBar(FooBar ... fooBars)
   {
       removeFooBar(Arrays.asList(fooBars));
   }

   public void removeFooBar(Collection<FooBar> fooBars)
   {
       Set<FooBar> previous = new HashSet<>(this.fooBars);
       this.fooBars.removeAll(fooBars);
       for ( FooBar fooBar : fooBars )
       {
           if ( previous.contains(fooBar) )
           {
               fooBar.setBar(null);
           }
       }
   }

   public Set<FooBar> getFooBars()
   {
       return Collections.unmodifiableSet(fooBars);
   }
}

@Entity
public class FooBar
{
    public static final String FOO = "foo";

    public static final String BAR = "bar";

    @ManyToOne(fetch = FetchType.LAZY,
               cascade = { CascadeType.PERSIST, CascadeType.MERGE },
               optional = false)
    @JoinColumn(name = FOO,
                nullable = false)
    private Foo foo;

    @ManyToOne(fetch = FetchType.LAZY,
               cascade = { CascadeType.PERSIST, CascadeType.MERGE },
               optional = false)
    @JoinColumn(name = BAR,
                nullable = false)
    private Bar bar;

    public Foo getFoo()
    {
        return foo;
    }

    public void setFoo(Foo foo)
    {
        // If we are no longer associated with a previous report, we must
        // remove ourselves from it.
        Foo previous = this.foo;
        if ( null != previous && !previous.equals(foo) )
        {
            previous.removeFooBar(this);
        }

        // Next, we need to set our own value.
        this.foo = foo;

        // Finally, we need to make sure that we're added to the new report
        if ( null != this.foo && !this.foo.equals(previous) )
        {
            this.foo.addFooBar(this);
        }
    }

    public Bar getBar()
    {
        return bar;
    }

    public void setBar(Bar bar)
    {
        // If we are no longer associated with a previous report, we must
        // remove ourselves from it.
        Bar previous = this.bar;
        if ( null != previous && !previous.equals(bar) )
        {
            previous.removeFooBar(this);
        }

        // Next, we need to set our own value.
        this.bar = bar;

        // Finally, we need to make sure that we're added to the new report
        if ( null != this.bar && !this.bar.equals(previous) )
        {
            this.bar.addFooBar(this);
        }
    }

}

当我有一个预先存在的FooBar时,我的问题就出现了,但是想要一个新的FooBar引用它们。目前,我们的DAO实现提供了使用dao.createOrUpdate的方法entityManager.merge。但是,此调用将因null检查违规而失败。但是,如果我手动访问实体管理器并使用entityManager.persist,我发现一切正常。这是一些测试代码:

public class Example
{
    private static final Logger LOG =     LoggerFactory.getLogger(Example.class);

    @Test
    public void failWithMerge()
    {
        // Make a Foo and a Bar that already exist.
        Long fooId;
        Long barId;
        try ( ExampleDAO dao = new ExampleDAO() )
        {
            EntityManager manager = dao.getEntityManager();
            manager.getTransaction().begin();

            Foo foo = new Foo();
            foo.setFooValue("This is a foo value");
            fooId = manager.merge(foo).getId();

            Bar bar = new Bar();
            bar.setBarValue("This is a bar value");
            barId = manager.merge(bar).getId();

            manager.getTransaction().commit();
        }

        // Make a new FooBar to associate them
        Long id;
        try ( ExampleDAO dao = new ExampleDAO() )
        {
            EntityManager manager = dao.getEntityManager();

            Foo foo = dao.getOne(Foo.class, fooId);
            Bar bar = dao.getOne(Bar.class, barId);

            FooBar fooBar = new FooBar();
            fooBar.setFoo(foo);
            fooBar.setBar(bar);

            LOG.info("============ MERGE ============");
            manager.getTransaction().begin();
            id = manager.merge(fooBar).getId();
            manager.getTransaction().commit();
        }
        finally
        {
            LOG.info("===============================");
        }
    }

    @Test
    public void worksWithPersist()
    {
        // Make a Foo and a Bar that already exist.
        Long fooId;
        Long barId;
        try ( ExampleDAO dao = new ExampleDAO() )
        {
            EntityManager manager = dao.getEntityManager();
            manager.getTransaction().begin();

            Foo foo = new Foo();
            foo.setFooValue("This is a foo value");
            fooId = manager.merge(foo).getId();

            Bar bar = new Bar();
            bar.setBarValue("This is a bar value");
            barId = manager.merge(bar).getId();

            manager.getTransaction().commit();
        }

        // Make a new FooBar to associate them
        Long id;
        try ( ExampleDAO dao = new ExampleDAO() )
        {
            EntityManager manager = dao.getEntityManager();

            Foo foo = dao.getOne(Foo.class, fooId);
            Bar bar = dao.getOne(Bar.class, barId);

            FooBar fooBar = new FooBar();
            fooBar.setFoo(foo);
            fooBar.setBar(bar);

            LOG.info("============ PERSIST ============");
            manager.getTransaction().begin();
            manager.persist(fooBar);
            id = fooBar.getId();
            manager.getTransaction().commit();
        }
        finally
        {
            LOG.info("=================================");
        }
    }
}

通过启用其他日志输出,我发现在使用merge时,它实际上是在尝试插入FooBar两次!以下是使用merge时的相关日志输出:

1609 [main] INFO my.example.Example - ============ MERGE ============
1610 [main] DEBUG org.hibernate.SQL - 
    insert 
    into
        FooBar
        (id, bar, foo, info, version) 
    values
        (default, ?, ?, ?, ?)
1610 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [1] as [BIGINT] - 1
1610 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [2] as [BIGINT] - 1
1610 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [3] as [VARCHAR] - <null>
1610 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [4] as [BIGINT] - 0
1612 [main] DEBUG org.hibernate.SQL - 
    insert 
    into
        FooBar
        (id, bar, foo, info, version) 
    values
        (default, ?, ?, ?, ?)
1612 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [1] as [BIGINT] - <null>
1613 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [2] as [BIGINT] - <null>
1613 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [3] as [VARCHAR] - <null>
1613 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [4] as [BIGINT] - 0
1614 [main] WARN org.hibernate.engine.jdbc.spi.SqlExceptionHelper - SQL     Error: -10, SQLState: 23502
1614 [main] ERROR org.hibernate.engine.jdbc.spi.SqlExceptionHelper -     integrity constraint violation: NOT NULL check constraint; SYS_CT_10097 table:     FOOBAR column: BAR
1616 [main] INFO my.example.Example - ===============================

与使用persist

产生的输出形成对比
1636 [main] INFO my.example.Example - ============ PERSIST ============
1636 [main] DEBUG org.hibernate.SQL - 
    insert 
    into
        FooBar
        (id, bar, foo, info, version) 
    values
        (default, ?, ?, ?, ?)
1636 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [1] as [BIGINT] - 2
1636 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [2] as [BIGINT] - 2
1637 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [3] as [VARCHAR] - <null>
1637 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [4] as [BIGINT] - 0
1642 [main] DEBUG org.hibernate.SQL - 
    update
        Foo 
    set
        fooValue=?,
        version=? 
    where
        id=? 
        and version=?
1642 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - This is a foo value
1643 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [2] as [BIGINT] - 1
1643 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [3] as [BIGINT] - 2
1643 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [4] as [BIGINT] - 0
1645 [main] DEBUG org.hibernate.SQL - 
    update
        Bar 
    set
        barValue=?,
        version=? 
    where
        id=? 
        and version=?
1646 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [1] as [VARCHAR] - This is a bar value
1646 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [2] as [BIGINT] - 1
1646 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [3] as [BIGINT] - 2
1646 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding     parameter [4] as [BIGINT] - 0
1646 [main] INFO my.example.Example - =================================

由于其他几个原因,我们决定在DAO实施中使用merge,我更愿意找到一种方法,使用merge使其正常工作。我希望你们中的一个人可以提供一些见解。

最后,对于它的价值,我们的JPA提供程序是Hibernate - 尽管你可以看到我在本例中仅限于JPA注释。我的测试在HSQLDB中运行,而我们的生产代码使用PostgreSQL。

谢谢,

柯蒂斯

更新

我认为我最终要做的是改变dao.createOrUpdate(使用merge)的实现。上面未包含的内容是FooBarFooBar都提供getId()方法。因此,我想我会这样做:

public <T extends PersistentObject> T createOrUpdate(T object)
{
    if ( null == object.getId() )
    {
        manager.persist(object);
        return object;
    }
    return manager.merge(object);
}

PersistentObject是所有对象的通用接口。)

0 个答案:

没有答案
相关问题