2
votes

I have a master table Blends and a table cards with one to one relation between them. While adding a card I am giving a drop down to select one blend, card have a column blend_id.

On adding a card I want to save the card in the table with blend_id value equals the id of blend selected from drop down.

I am using spring hibernate, this is the exception I am getting on submitting the card form :

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.terp.entity.BlendEntity
    org.hibernate.engine.ForeignKeys.getEntityIdentifierIfNotUnsaved(ForeignKeys.java:243)
    org.hibernate.type.EntityType.getIdentifier(EntityType.java:456)
    org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:265)
    org.hibernate.type.ManyToOneType.isDirty(ManyToOneType.java:275)
    org.hibernate.type.TypeHelper.findDirty(TypeHelper.java:295)
    org.hibernate.persister.entity.AbstractEntityPersister.findDirty(AbstractEntityPersister.java:3403)
    org.hibernate.event.def.DefaultFlushEntityEventListener.dirtyCheck(DefaultFlushEntityEventListener.java:520)
    org.hibernate.event.def.DefaultFlushEntityEventListener.isUpdateNecessary(DefaultFlushEntityEventListener.java:230)
    org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:154)
    org.hibernate.event.def.AbstractFlushingEventListener.flushEntities(AbstractFlushingEventListener.java:219)
    org.hibernate.event.def.AbstractFlushingEventListener.flushEverythingToExecutions(AbstractFlushingEventListener.java:99)
    org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:50)
    org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
    org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:383)
    org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:133)
    org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:656)
    org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    $Proxy26.addCard(Unknown Source)
    com.terp.controller.CardController.addCard(CardController.java:122)
    com.terp.controller.CardController$$FastClassByCGLIB$$43e17ccc.invoke(<generated>)
    net.sf.cglib.proxy.MethodProxy.invoke(MethodProxy.java:149)
    org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:617)
    com.terp.controller.CardController$$EnhancerByCGLIB$$4627162d_2.addCard(<generated>)
    sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    java.lang.reflect.Method.invoke(Unknown Source)
    org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:176)
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:427)
    org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:415)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:788)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:717)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
    org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:560)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:727)

From exception is seems that it wants to save the selected blend object, but I dont want to save blend as this is my master table, I just want its reference in my cards table.

These are the java classes :

BlendEntity.java

package com.terp.entity;

import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.Proxy;


@Entity
@Table(name="blends")
@Proxy(lazy=false)
public class BlendEntity {

    @Id
    @Column(name="id")
    @GeneratedValue
    private Integer id;
    private String name;
    private String code;

    @Temporal(TemporalType.TIMESTAMP)
    private Date created_at;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Date getCreated_at() {
        return created_at;
    }

    public void setCreated_at(Date created_at) {
        this.created_at = created_at;
    }

}

CardEntity.java

package com.terp.entity;

import java.util.Date;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.Proxy;
import org.hibernate.annotations.Sort;
import org.hibernate.annotations.SortType;
import org.springframework.transaction.annotation.Transactional;

@Entity
@Table(name="cards")
@Proxy(lazy=false)
public class CardEntity {

    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Integer id;

    @OneToOne
    @JoinColumn(name="division_id", referencedColumnName="id")  
    private DivisionEntity division;

    private Integer card_series;
    private String card_number;
    private Integer shade;

    @OneToOne
    @JoinColumn(name="unit_of_qty_id", referencedColumnName="id")  
    private UnitEntity unit;

    private Date card_date;
    private Date issue_date;


    private String cutomer_ref;
    private String hl_number;
    private String design_number;

    @OneToOne
    @JoinColumn(name="designWay_id", referencedColumnName="id")  
    private DesignWayEntity designWay;

/*  @OneToOne
    @JoinColumn(name="dyeingType_id", referencedColumnName="id")  
    private DyeingTypeEntity dyeingType;*/

    private String dyeingType;

    private Integer total_days;
    private Date order_date;
    private Date grey_date;
    private Date target_date;

    @OneToOne
    @JoinColumn(name="qualityBase_id", referencedColumnName="id")  
    private QualityBaseEntity qualityBase;

    @OneToOne
    @JoinColumn(name="openFor_id", referencedColumnName="id")  
    private OpenForEntity openFor;

    @OneToOne
    @JoinColumn(name="width_id", referencedColumnName="id")  
    private WidthEntity width;

    @OneToOne
    @JoinColumn(name="finishType_id", referencedColumnName="id")  
    private FinishTypeEntity finishType;

    @OneToOne
    @JoinColumn(name="blend_id", referencedColumnName="id")  
    private BlendEntity blend;

    private Float weight;

    @OneToOne
    @JoinColumn(name="merchant_id", referencedColumnName="id")  
    private MerchantEntity merchant;

    @OneToOne
    @JoinColumn(name="priority_id", referencedColumnName="id")  
    private PriorityEntity priority;

    @OneToMany(fetch = FetchType.EAGER, mappedBy="card", cascade = CascadeType.ALL)
    @Sort(type=SortType.NATURAL)
    private SortedSet<ShadeEntity> shades = new TreeSet<ShadeEntity>();

    private Float total_qty;

    @Transactional
    public SortedSet<ShadeEntity> getShades() {
        return shades;
    }

    @Transactional
    public void setShades(SortedSet<ShadeEntity> shades) {
        this.shades = shades;
    }

    @Temporal(TemporalType.TIMESTAMP)
    private Date created_at;

    public Integer getId() {
        return id;
    }

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

    public DivisionEntity getDivision() {
        return division;
    }

    public void setDivision(DivisionEntity division) {
        this.division = division;
    }


    public Integer getCard_series() {
        return card_series;
    }

    public void setCard_series(Integer card_series) {
        this.card_series = card_series;
    }

    public String getCard_number() {
        return card_number;
    }

    public void setCard_number(String card_number) {
        this.card_number = card_number;
    }

    public Integer getShade() {
        return shade;
    }

    public void setShade(Integer shade) {
        this.shade = shade;
    }



    public UnitEntity getUnit() {
        return unit;
    }

    public void setUnit(UnitEntity unit) {
        this.unit = unit;
    }

    public Date getCard_date() {
        return card_date;
    }

    public void setCard_date(Date card_date) {
        this.card_date = card_date;
    }

    public String getCutomer_ref() {
        return cutomer_ref;
    }

    public void setCutomer_ref(String cutomer_ref) {
        this.cutomer_ref = cutomer_ref;
    }

    public String getHl_number() {
        return hl_number;
    }

    public void setHl_number(String hl_number) {
        this.hl_number = hl_number;
    }

    public String getDesign_number() {
        return design_number;
    }

    public void setDesign_number(String design_number) {
        this.design_number = design_number;
    }

    public DesignWayEntity getDesignWay() {
        return designWay;
    }

    public void setDesignWay(DesignWayEntity designWay) {
        this.designWay = designWay;
    }




    public String getDyeingType() {
        return dyeingType;
    }

    public void setDyeingType(String dyeingType) {
        this.dyeingType = dyeingType;
    }

    public Date getOrder_date() {
        return order_date;
    }

    public void setOrder_date(Date order_date) {
        this.order_date = order_date;
    }

    public Date getGrey_date() {
        return grey_date;
    }

    public void setGrey_date(Date grey_date) {
        this.grey_date = grey_date;
    }

    public Date getTarget_date() {
        return target_date;
    }

    public void setTarget_date(Date target_date) {
        this.target_date = target_date;
    }

    public QualityBaseEntity getQualityBase() {
        return qualityBase;
    }

    public void setQualityBase(QualityBaseEntity qualityBase) {
        this.qualityBase = qualityBase;
    }

    public OpenForEntity getOpenFor() {
        return openFor;
    }

    public void setOpenFor(OpenForEntity openFor) {
        this.openFor = openFor;
    }

    public WidthEntity getWidth() {
        return width;
    }

    public void setWidth(WidthEntity width) {
        this.width = width;
    }

    public FinishTypeEntity getFinishType() {
        return finishType;
    }

    public void setFinishType(FinishTypeEntity finishType) {
        this.finishType = finishType;
    }

    public BlendEntity getBlend() {
        return blend;
    }

    public void setBlend(BlendEntity blend) {
        this.blend = blend;
    }


    public Float getWeight() {
        return weight;
    }

    public void setWeight(Float weight) {
        this.weight = weight;
    }



    public Integer getTotal_days() {
        return total_days;
    }

    public void setTotal_days(Integer total_days) {
        this.total_days = total_days;
    }

    public MerchantEntity getMerchant() {
        return merchant;
    }

    public void setMerchant(MerchantEntity merchant) {
        this.merchant = merchant;
    }

    public PriorityEntity getPriority() {
        return priority;
    }

    public void setPriority(PriorityEntity priority) {
        this.priority = priority;
    }

    public Float getTotal_qty() {
        return total_qty;
    }

    public void setTotal_qty(Float total_qty) {
        this.total_qty = total_qty;
    }

    public Date getCreated_at() {
        return created_at;
    }

    public void setCreated_at(Date created_at) {
        this.created_at = created_at;
    }

    public Date getIssue_date() {
        return issue_date;
    }

    public void setIssue_date(Date issue_date) {
        this.issue_date = issue_date;
    }

}

contraint in card table :

CONSTRAINT `fk_40` FOREIGN KEY (`blend_id`) REFERENCES `blends` (`id`),

In cards if i add cascade it is adding a new row in blends :

@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name="blend_id", referencedColumnName="id")  
private BlendEntity blend;
1
It seems that the BlendEntity assigned to the card doesn't have a valid id, so hibernate is saving this as a new row in blend (with the cascade type in your test). Check how are you setting the blend to the card entity.Leandro Carracedo
BlendEntity is not a required field, so if i am not selecting any thing from drop down for blend, the BlendEntity in card object have all null fields including id and there is no row in Blends table with null id, so this may be the problem. Any suggestion on how to handle this case?Naveen
If that's your scenario, i would add a nullable=true to the @JoinColumn annotation.Leandro Carracedo
adding nullable=true have no effect.Naveen
Also try to include @OneToOne(optional=true) for the Blend entity.Leandro Carracedo

1 Answers

5
votes

You have a persistence context problem. See What is Persistence Context . An unsaved transient instance is not in the persistence context.

TransientObjectException javadoc:

Thrown when the user passes a transient instance to a Session method that expects a persistent instance.

Two possible solutions:

  1. If the relationship is really @OneToOne, consider re-factoring the schema by defining the Blend columns in the Card table. Both @OneToOne#optional and @JoinColumn#nullable default to true. But, the intent of a @OneToOne relationship is that the target entity (row) is not shared by more than one referencing entity. Allowing a null Blend foreign key means that many Blends could be shared by no Card.

  2. Don't put @Transactional on entity methods. Best practice is to define @Transactional on service (and perhaps Dao) layers. Also, check your Service/Dao/Repository configuration and code, making sure that both entities are managed by the same persistence context, for example, by find()ing the previously persisted Blend entity that the blend ID refers to, and then assign it to the Card. To better understand how entities, entity managers, persistence contexts and transactions interact, I scraped the following bullet points from Pro JPA 2, 2nd Edition by Mike Keith and Merrick Schincariol. I am not associated with the authors.

    • Dao/Repository layer must use an JPA EntityManager for persistence.
    • An entity manager manages a persistence context, i.e. a set of persistent entity instances.
    • A persistent entity has both an in-memory object identity and a database identity defined by a database representation (row) uniquely identified by a (primary) key.

    • A transient entity does not have a database representation, whether (or not) the app has set the entity's " unique identity" field(s).

    • Java memory model is not transactional. A detached (or transient) object entity's state is not automagically synchronized with DB.

    • If the persistence context participates in a transaction, the in-memory state of the managed entities will get synchronized to the database.

    • A detached (transient) entity cannot be used with any entity manager operation that requires a managed instance.

    • The entity manager type determines the lifetime of a persistence context.

    • JPA defines 3 types of entity managers, and each requires different persistence context management:

    • Container managed EntityManager:

      • Transaction Scoped:

        • @PersistenceContext // defaults to type=PersistenceContextType.TRANSACTION
        • Uses a transaction as a way to track/propagate persistence contexts.
        • Is stateless; it can be safely stored on any Java EE component.
        • The container proxies the persistence provider's entity manager in order to manage its lifecycle.
        • When app calls an entity manager method, the container's entity manager proxy checks for an existing persistence context associated with the current transaction. If the proxy finds one, the entity manager will use the existing persistence context. If it doesn’t find one, the proxy creates a new persistence context and associates it with the transaction.
        • When the transaction ends, the persistence context "goes away," and all entities managed by the context become detached.
        • All container-managed entity managers in the same transaction must share the same propagated persistence context.
      • Extended Scope:

        • @PersistenceContext(type=PersistenceContextType.EXTENDED)
        • A single persistence context that is scoped to the life of a stateful session bean, potentially spanning multiple transactions.
        • Prevents entities from becoming detached when a single transaction ends.
    • Application managed EntityManager:

      • App manages the lifecycle of the entity manager; must call close().
      • Creates its own private persistence context that lasts until the entity manager is closed.
      • Automatically synchronized with the transaction if created when a transaction is active; else not.
      • Available in Java SE: EntityManagerFactory.createEntityManager().
      • Available in Java EE: @PersistenceUnit.
      • Supports unit testing w/o container.
      • App must explicitly close the EntityManagerFactory instance. A Java EE container will close the factory automatically.
      • Only type of entity manager that can be configured for resource-local transactions
      • can use JTA or resource local XA