简体   繁体   中英

Vaadin JPAContainer: ManytoOne relation with EmbeddedID

There are questions similar but not quite. In those cases (and in the JPAContainer examples) the Entity part of the ManyToOne relationship has a single key. In my case it is an embedded id.

My code is based on the Address Book example .

Here are the three entities:

@Entity
@Table(name = "tutorial")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Tutorial.findAll", query = "SELECT t FROM Tutorial t"),
    @NamedQuery(name = "Tutorial.findById", query = "SELECT t FROM Tutorial t WHERE t.tutorialPK.id = :id"),
    @NamedQuery(name = "Tutorial.findByTutorialTypeId", query = "SELECT t FROM Tutorial t WHERE t.tutorialPK.tutorialTypeId = :tutorialTypeId"),
    @NamedQuery(name = "Tutorial.findByMessage", query = "SELECT t FROM Tutorial t WHERE t.message = :message")})
public class Tutorial implements Serializable {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected TutorialPK tutorialPK;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 245)
    @Column(name = "message")
    private String message;
    @JoinColumn(name = "tutorial_type_id", referencedColumnName = "id", insertable = false, updatable = false)
    @ManyToOne(optional = false)
    private TutorialType tutorialType;

    public Tutorial() {
    }

    public Tutorial(TutorialPK tutorialPK) {
        this.tutorialPK = tutorialPK;
    }

    public Tutorial(TutorialPK tutorialPK, String message) {
        this.tutorialPK = tutorialPK;
        this.message = message;
    }

    public Tutorial(int id, int tutorialTypeId) {
        this.tutorialPK = new TutorialPK(id, tutorialTypeId);
    }

    public TutorialPK getTutorialPK() {
        return tutorialPK;
    }

    public void setTutorialPK(TutorialPK tutorialPK) {
        this.tutorialPK = tutorialPK;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public TutorialType getTutorialType() {
        return tutorialType;
    }

    public void setTutorialType(TutorialType tutorialType) {
        this.tutorialType = tutorialType;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (tutorialPK != null ? tutorialPK.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof Tutorial)) {
            return false;
        }
        Tutorial other = (Tutorial) object;
        if ((this.tutorialPK == null && other.tutorialPK != null) || (this.tutorialPK != null && !this.tutorialPK.equals(other.tutorialPK))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "games.jwrestling.server.game.db.persistence.Tutorial[ tutorialPK=" + tutorialPK + " ]";
    }

}

And the EmbeddedId class:

@Embeddable
public class TutorialPK implements Serializable {

    @Basic(optional = false)
    @NotNull
    @Column(name = "id")
    private int id;
    @Basic(optional = false)
    @NotNull
    @Column(name = "tutorial_type_id")
    private int tutorialTypeId;

    public TutorialPK() {
    }

    public TutorialPK(int id, int tutorialTypeId) {
        this.id = id;
        this.tutorialTypeId = tutorialTypeId;
    }

    public int getId() {
        return id;
    }

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

    public int getTutorialTypeId() {
        return tutorialTypeId;
    }

    public void setTutorialTypeId(int tutorialTypeId) {
        this.tutorialTypeId = tutorialTypeId;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (int) id;
        hash += (int) tutorialTypeId;
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof TutorialPK)) {
            return false;
        }
        TutorialPK other = (TutorialPK) object;
        if (this.id != other.id) {
            return false;
        }
        if (this.tutorialTypeId != other.tutorialTypeId) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "games.jwrestling.server.game.db.persistence.TutorialPK[ id=" + id + ", tutorialTypeId=" + tutorialTypeId + " ]";
    }

}

And another one:

@Entity
@Table(name = "tutorial_type")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "TutorialType.findAll", query = "SELECT t FROM TutorialType t"),
    @NamedQuery(name = "TutorialType.findById", query = "SELECT t FROM TutorialType t WHERE t.id = :id"),
    @NamedQuery(name = "TutorialType.findByType", query = "SELECT t FROM TutorialType t WHERE t.type = :type"),
    @NamedQuery(name = "TutorialType.findByDescription", query = "SELECT t FROM TutorialType t WHERE t.description = :description")})
public class TutorialType implements Serializable {

    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    @NotNull
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "TutorialTypeGen")
    @TableGenerator(name = "TutorialTypeGen", table = "jwrestling_id",
            pkColumnName = "tablename",
            valueColumnName = "last_id",
            pkColumnValue = "tutorial_type",
            allocationSize = 1,
            initialValue = 1)
    @Column(name = "id")
    private Integer id;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 45)
    @Column(name = "type")
    private String type;
    @Size(max = 245)
    @Column(name = "description")
    private String description;
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "tutorialType")
    private List<Tutorial> tutorialList;

    public TutorialType() {
    }

    public TutorialType(Integer id) {
        this.id = id;
    }

    public TutorialType(Integer id, String type) {
        this.id = id;
        this.type = type;
    }

    public TutorialType(String type, String desc) {
        this.type = type;
        this.description = desc;
    }

    public Integer getId() {
        return id;
    }

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

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @XmlTransient
    public List<Tutorial> getTutorialList() {
        return tutorialList;
    }

    public void setTutorialList(List<Tutorial> tutorialList) {
        this.tutorialList = tutorialList;
    }

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object object) {
        // TODO: Warning - this method won't work in the case the id fields are not set
        if (!(object instanceof TutorialType)) {
            return false;
        }
        TutorialType other = (TutorialType) object;
        if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "games.jwrestling.server.game.db.persistence.TutorialType[ id=" + id + " ]";
    }

}

I got the form set up and it works fine when I edit the items, but creating I get errors because the TutorialPK is null:

Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.2.v20151217-774c696): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Column 'tutorial_type_id' cannot be null

Here are the related Vaadin code:

public final class TutorialEditor extends Window implements Button.ClickListener,
        FormFieldFactory {

    private final Item tutorialItem;
    private final Form editorForm;
    private final Button saveButton;
    private final Button cancelButton;

    public TutorialEditor(Item tutorialItem) {
        this.tutorialItem = tutorialItem;
        editorForm = new Form();
        editorForm.setFormFieldFactory(this);
        editorForm.setBuffered(true);
        editorForm.setImmediate(true);
        editorForm.setItemDataSource(tutorialItem, Arrays.asList("message",
                "tutorialType"));

        saveButton = new Button("Save", this);
        cancelButton = new Button("Cancel", this);

        editorForm.getFooter().addComponent(saveButton);
        editorForm.getFooter().addComponent(cancelButton);
        setSizeUndefined();
        setContent(editorForm);
        setCaption("New Tutorial");
    }

    @Override
    public void buttonClick(Button.ClickEvent event) {
        if (event.getButton() == saveButton) {
            editorForm.commit();
            fireEvent(new EditorSavedEvent(this, tutorialItem));
        } else if (event.getButton() == cancelButton) {
            editorForm.discard();
        }
        close();
    }

    @Override
    public Field<?> createField(Item item, Object propertyId, Component uiContext) {
        Field field = DefaultFieldFactory.get().createField(item, propertyId,
                uiContext);
        if ("tutorialType".equals(propertyId)) {
            field = new TutorialTypeSelector();
        } else if (field instanceof TextField) {
            ((TextField) field).setNullRepresentation("");
        }

        field.addValidator(new BeanValidator(Tutorial.class, propertyId
                .toString()));

        return field;
    }

    public void addListener(EditorSavedListener listener) {
        try {
            Method method = EditorSavedListener.class.getDeclaredMethod(
                    "editorSaved", new Class[]{EditorSavedEvent.class});
            addListener(EditorSavedEvent.class, listener, method);
        } catch (final java.lang.NoSuchMethodException e) {
            // This should never happen
            throw new java.lang.RuntimeException(
                    "Internal error, editor saved method not found");
        }
    }

    public void removeListener(EditorSavedListener listener) {
        removeListener(EditorSavedEvent.class, listener);
    }

    public static class EditorSavedEvent extends Component.Event {

        private final Item savedItem;

        public EditorSavedEvent(Component source, Item savedItem) {
            super(source);
            this.savedItem = savedItem;
        }

        public Item getSavedItem() {
            return savedItem;
        }
    }

    public interface EditorSavedListener extends Serializable {

        public void editorSaved(EditorSavedEvent event);
    }

And another one:

class TutorialTypeSelector extends CustomField<TutorialType> {

    private final JPAContainer<TutorialType> container;
    private final ComboBox type = new ComboBox();

    public TutorialTypeSelector() {
        container = JPAContainerFactory.make(TutorialType.class,
                "JWPUJNDI");
        setCaption("Type");
        type.setContainerDataSource(container);
        type.setItemCaptionPropertyId("type");
        type.addListener(new Property.ValueChangeListener() {
            @Override
            public void valueChange(
                    com.vaadin.data.Property.ValueChangeEvent event) {
                /*
                 * Modify the actual value of the custom field.
                 */
                if (type.getValue() == null) {
                    setValue(null, false);
                } else {
                    TutorialType entity = container
                            .getItem(type.getValue()).getEntity();
                    setValue(entity, false);
                }
            }
        });
    }

    @Override
    protected Component initContent() {
        CssLayout cssLayout = new CssLayout();
        cssLayout.addComponent(type);
        return cssLayout;
    }

    @Override
    public void setPropertyDataSource(Property newDataSource) {
        super.setPropertyDataSource(newDataSource);
        setTutorialType((TutorialType) newDataSource.getValue());
    }

    @Override
    public void setValue(TutorialType newValue) throws ReadOnlyException,
            Converter.ConversionException {
        super.setValue(newValue);
        setTutorialType(newValue);
    }

    private void setTutorialType(TutorialType type) {
        this.type.setValue(type != null ? type.getId() : null);
    }

    @Override
    public Class<? extends TutorialType> getType() {
        return TutorialType.class;
    }
}

Any idea on how to populate this field?

Update:

Error after using @MapsId

Exception [EclipseLink-46] (Eclipse Persistence Services - 2.6.2.v20151217-774c696): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: There should be one non-read-only mapping defined for the primary key field [tutorial.tutorial_type_id].
Descriptor: RelationalDescriptor(games.jwrestling.server.game.db.persistence.Tutorial --> [DatabaseTable(tutorial)])

Two ways. If using JPA 1.0, you will need to pull the value from the referenced tutorialType and manually add it to the tutorial.tutorialPK.tutorialTypeId. You didn't include the tutorialType entity, but if it's ID value is generated, you may need to persist it and flush before the value is assigned.

If using JPA 2.0, you can specify the @MapsId annotation in your entity, allowing JPA to set the tuturial.tutorialPK.tutorialTypeId value from the tutorial.tutorialType reference for you:

public class Tutorial implements Serializable {

    private static final long serialVersionUID = 1L;
    @EmbeddedId
    protected TutorialPK tutorialPK;
    @Basic(optional = false)
    @NotNull
    @Size(min = 1, max = 245)
    @Column(name = "message")
    private String message;
    @MapsId("tutorialTypeId")
    @ManyToOne(optional = false)
    private TutorialType tutorialType;

You will not be able to change the TutorialType associated to a tutorial once it is created though - the only option is to delete the existing one and create a new one with the new values.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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