views:

75

answers:

2

Hi,

I use Hibernate to persist inherited objects but I got this message when I try to persist objects in database:

org.springframework.dao.InvalidDataAccessResourceUsageException: Could not execute JDBC batch update; SQL [update Widget set CONTAINER_ID=? where WIDGET_ID=?]; nested exception is org.hibernate.exception.SQLGrammarException: Could not execute JDBC batch update (...) Caused by: java.sql.BatchUpdateException: Table 'schema.widget' doesn't exist

Here is the classes I used to generate the table:

@Entity
@Table(name="CONTAINER")
public class Container {
     (...)
     private Set<Widget> widgets;

     @OneToMany(targetEntity = Widget.class)
     @JoinColumn(name="CONTAINER_ID", nullable=true)
     public Set<Widget> getWidgets() {
         return widgets;
     }

     public void setWidgets(Set<Widget> widgets) {
         this.widgets = widgets;
     }
}


@Entity
@Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class Widget {
     private long id;
     private int position;

     @Id
     @GeneratedValue(strategy = GenerationType.TABLE)
     @Column(name="WIDGET_ID")
     public long getId() {
         return id;
     }

     public void setId(long id) {
         this.id = id;
     }

     @Column(name="POSITION")
     public int getPosition() {
         return position;
     }

     public void setPosition(int position) {
         this.position = position;
     }
}


@Entity
@Table(name="TEXT_WIDGET")
public class TextWidget extends Widget {
     (...)
}


@Entity
@Table(name="IMAGE_WIDGET")
public class ImageWidget extends Widget {
     (...)
}

So it means that Hibernate is looking for the table 'widget' but it's not created and that make sense because I choose InheritanceType.TABLE_PER_CLASS option then only concrete classes have a table. In database, I can see container, text_widget and image_widget tables.

Then when I try to execute this code and save container, then I got the above error:

Set<Widget> widgets = new HashSet<Widget>();
widgets.add(textw1); // instance of TextWidget
widgets.add(imgw1); // instance of ImageWidget

Container container1 = new Container();
container1.setWidgets(widgets);

Thanks for your help!

A: 

You should only use @OneToMany(targetEntity = ...) with a concrete class, that's the only way Hibernate can be aware how to handle the instances.

In your example you are trying to save the Container and you know the concrete classes of the Widgets, but imagine if you are loading a already saved Container, how would Hibernate know where to find the widgets on the database?

You probably have to put 2 different lists of widgets on Container and create a transient property to merge them.

Vinny
+1  A: 

Your association must be bidirectional, as mentioned in the Hibernate documentation.

2.2.4.1. Table per class

This strategy has many drawbacks (esp. with polymorphic queries and associations) explained in the JPA spec, the Hibernate reference documentation, Hibernate in Action, and many other places. Hibernate work around most of them implementing this strategy using SQL UNION queries. It is commonly used for the top level of an inheritance hierarchy:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Flight implements Serializable { ... }            

This strategy supports one-to-many associations provided that they are bidirectional. This strategy does not support the IDENTITY generator strategy: the id has to be shared across several tables. Consequently, when using this strategy, you should not use AUTO nor IDENTITY.

This way, Hibernate will be able to create the appropriate foreign key column in each concrete widget table. Here is a mapping that actually works. For the Container:

@Entity
public class Container {
    @Id @GeneratedValue
    private long id;

    @OneToMany(targetEntity = Widget.class, mappedBy = "container", cascade = CascadeType.ALL)
    private Set<Widget> widgets = new HashSet<Widget>();

    public long getId() { return id; }

    public void setId(long id) { this.id = id; }

    public Set<Widget> getWidgets() { return widgets; }

    public void setWidgets(Set<Widget> widgets) { this.widgets = widgets; }

    public void addToWidgets(Widget widget) {
        this.getWidgets().add(widget);
        widget.setContainer(this);
    }

    public void removeFromWidgets(Widget widget) {
        this.getWidgets().remove(widget);
        widget.setContainer(null);
    }
}

And the abstract Widget class:

@Entity
@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class Widget {
    @Id @GeneratedValue(strategy = GenerationType.TABLE)
    private long id;

    private int position;

    @ManyToOne
    private Container container;

    public long getId() { return id; }

    public void setId(long id) { this.id = id; }

    public int getPosition() { return position; }

    public void setPosition(int position) { this.position = position; }

    public Container getContainer() { return container; }

    public void setContainer(Container container) { this.container = container; }
}

And the following test method (running inside a transaction) just passes:

@Test
public void testInsertContainer() {
    TextWidget textw1 = new TextWidget();
    ImageWidget imgw1 = new ImageWidget();

    Container container1 = new Container();
    container1.addToWidgets(textw1); // instance of TextWidget
    container1.addToWidgets(imgw1); // instance of ImageWidget

    session.persist(container1);
    session.flush();

    assertNotNull(textw1.getId());
    assertNotNull(imgw1.getId());
    assertNotNull(container1.getId());
}

And generates the following SQL:

21:59:57.964 [main] DEBUG org.hibernate.SQL - select next_hi from hibernate_unique_key for read only with rs
21:59:57.978 [main] DEBUG org.hibernate.SQL - update hibernate_unique_key set next_hi = ? where next_hi = ?
21:59:58.063 [main] DEBUG org.hibernate.SQL - null
21:59:58.125 [main] DEBUG org.hibernate.SQL - insert into Container (id) values (?)
Hibernate: insert into Container (id) values (?)
21:59:58.140 [main] TRACE org.hibernate.type.LongType - binding '98304' to parameter: 1
21:59:58.145 [main] DEBUG org.hibernate.SQL - insert into ImageWidget (container_id, position, id) values (?, ?, ?)
Hibernate: insert into ImageWidget (container_id, position, id) values (?, ?, ?)
21:59:58.164 [main] TRACE org.hibernate.type.LongType - binding '98304' to parameter: 1
21:59:58.165 [main] TRACE org.hibernate.type.IntegerType - binding '0' to parameter: 2
21:59:58.166 [main] TRACE org.hibernate.type.LongType - binding '32768' to parameter: 3
21:59:58.172 [main] DEBUG org.hibernate.SQL - insert into TextWidget (container_id, position, id) values (?, ?, ?)
Hibernate: insert into TextWidget (container_id, position, id) values (?, ?, ?)
21:59:58.187 [main] TRACE org.hibernate.type.LongType - binding '98304' to parameter: 1
21:59:58.188 [main] TRACE org.hibernate.type.IntegerType - binding '0' to parameter: 2
21:59:58.189 [main] TRACE org.hibernate.type.LongType - binding '32769' to parameter: 3

But keep in mind that the table per class strategy provides poor support for polymorphic relationships and might not be appropriate at all if you have many Widget child (this will result in huge SQL UNION).

Related question

Pascal Thivent