0

I am newbie with JPA and Hibernate. I have the two entities, Vendor and Product, described below.

@Entity
public class Vendor extends BaseEntity
{
    private static final long serialVersionUID = 267250313080292374L;

    @NotNull
    private String name;

    @NotNull
    private String city;

    @OneToMany(mappedBy = "vendor", cascade = CascadeType.ALL)
    private List<Product> products = new ArrayList<Product>();

    public String getName() {
        return name;
    }

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

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public List<Product> getProducts() {
        return products;
    }

    public void setProducts(List<Product> products) {
        this.products = products;
    }
}

@Entity
public class Product extends BaseEntity
{
    private static final long serialVersionUID = -4676899182130380017L;

    @NotNull
    private String name;

    @Column(nullable = false, precision = 10, scale = 2)
    private double price;

    @NotNull
    private int quantity;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="VENDOR_ID", nullable = false)
    private Vendor vendor;

    public String getName() {
        return name;
    }

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

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getQuantity() {
        return quantity;
    }

    public void setQuantity(int quantity) {
        this.quantity = quantity;
    }

    public Vendor getVendor() {
        return vendor;
    }

    public void setVendor(Vendor vendor) {
        this.vendor = vendor;
    }
}

My problem is related to the usage of the attribute nullable = false in the @ManyToOne annotation. I want that the corresponding column annotated with @ManyToOne to be not null. However when I test the persitence of the entities with the following test:

public void testCreateEntity()
{

        // create vendor1, set name and city
        vendor1 = new Vendor();
        vendor1.setName("MediaMarkt");
        vendor1.setCity("Berlin");
        vendor1 = service.createOrUpdateEntity(vendor1);

        //  create product1, set name, price and quantity
        product1 = new Product();
        product1.setName("Samsung Galaxy S7");
        product1.setPrice(new Double("819.00"));
        product1.setQuantity(1);

        vendor1.getProducts().add(product1);
        product1.setVendor(vendor1);

        //  create product at service
        product1 = service.createOrUpdateEntity(product1);
...
}

where the createOrUpdateEntity method is part of a DAO Bean Object

 @PersistenceContext
 private EntityManager em;

    public <T extends BaseEntity> T createOrUpdateEntity(T entity)
    {
        // as we have no id yet, it must be freshly brewed
        if (entity.getId() == 0)
        {
            log.debug("createOrUpdateEntity::create:: {}", entity);
            this.em.persist(entity);
        }
        // if there is an id, we must have dealt with it before
        else
        {
            log.debug("createOrUpdateEntity::update:: {}", entity);
            this.em.merge(entity);
        }
        this.em.flush();

        return entity;
    }

I get the error:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: de.brockhaus.stock.entity.Stock
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:765)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:758)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener$1.cascade(JpaPersistEventListener.java:80)
at org.hibernate.engine.internal.Cascade.cascadeToOne(Cascade.java:398)
at org.hibernate.engine.internal.Cascade.cascadeAssociation(Cascade.java:323)
at org.hibernate.engine.internal.Cascade.cascadeProperty(Cascade.java:162)
at org.hibernate.engine.internal.Cascade.cascade(Cascade.java:111)
at org.hibernate.event.internal.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:425)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:249)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:178)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:121)
at org.hibernate.jpa.event.internal.core.JpaPersistEventListener.saveWithGeneratedId(JpaPersistEventListener.java:67)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:189)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:132)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:58)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:775)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753)
at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146)
... 138 more

EDIT: Implementing this example, which uses a @ManyToOne column annotated as nullable = false, I have realized that my problem is not related to this. Such as shown in the previous server log, the problem is being caused by the Cascade class. Some solutions in SO suggest to change CascadeType.ALL by CascadeType.MERGE. Other ones show different strategies. Even some ones tell that only the OneToMany side of the association should use cascade. Frankly, I thought that using CascadeType.ALL in both sides of the association was the best option. But it seems that I was wrong. Although, such as I have pointed out, I am newbie with JPA and Hibernate and I do not understand the reasonings.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
julianfperez
  • 1,726
  • 5
  • 38
  • 69

2 Answers2

1

You can use optional tag for null control:

@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name="VENDOR_ID")
private Vendor vendor;
hurricane
  • 6,521
  • 2
  • 34
  • 44
1

Actually, with this code, I can get your example to work:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>demo</groupId>
    <artifactId>hibernate4-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>hibernate4-demo</name>
    <url>http://maven.apache.org</url>

    <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <hibernate-core.version>4.3.11.Final</hibernate-core.version>
    </properties>

    <dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>${hibernate-core.version}</version>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>${hibernate-core.version}</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
        <scope>runtime</scope>
    </dependency>
    </dependencies>
    <build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.5.1</version>
            <configuration>
                <source>1.7</source>
                <target>1.7</target>
            </configuration>
        </plugin>
    </plugins>
    </build>
</project>

BaseEntity.java (curtesy of Brockhaus)

package demo.hibernate4;

import java.io.Serializable;
import java.util.Date;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;

@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @Version
    private long version;

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

    @Temporal(TemporalType.DATE)
    private Date creationDate;

    public BaseEntity() {
    //lazy
    }

    public Date getCreationDate() {
    return creationDate;
    }

    public void setCreationDate(Date creationDate) {
    this.creationDate = creationDate;
    }

    public long getId() {
    return id;
    }

}

GenericDAOBean.java

package demo.hibernate4;

import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

public class GenericDAOBean {

    private EntityManager em;

    @Override
    public <T extends BaseEntity> T createOrUpdateEntity(T entity) {
    if (entity.getId() == 0) {
        this.em.persist(entity);
    } else {
        this.em.merge(entity);
    }
    this.em.flush();

    return entity;
    }

    public void setEm(EntityManager em) {
        this.em = em;
    }

}

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1" xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
  <persistence-unit name="hibernate4-demo" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <properties>
      <property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/mysql?zeroDateTimeBehavior=convertToNull"/>
      <property name="javax.persistence.jdbc.password" value=""/>
      <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
      <property name="javax.persistence.jdbc.user" value="root"/>
      <property name="hibernate.cache.provider_class" value="org.hibernate.cache.NoCacheProvider"/>
      <property name="javax.persistence.schema-generation.database.action" value="none"/>
    </properties>
  </persistence-unit>
</persistence>

Main.java

package demo.hibernate4;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class Main {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hibernate4-demo");
        EntityManager em = emf.createEntityManager();
        GenericDAOBean service = new GenericDAOBean();
        service.setEm(em);

        EntityTransaction transaction = em.getTransaction();
        transaction.begin();

        // create vendor1, set name and city
        Vendor vendor1 = new Vendor();
        vendor1.setName("MediaMarkt");
        vendor1.setCity("Berlin");
        vendor1 = service.createOrUpdateEntity(vendor1);

        //  create product1, set name, price and quantity
        Product product1 = new Product();
        product1.setName("Samsung Galaxy S7");
        product1.setPrice(new Double("819.00"));
        product1.setQuantity(1);

        vendor1.getProducts().add(product1);
        product1.setVendor(vendor1);

        //  create product at service
        product1 = service.createOrUpdateEntity(product1);
        transaction.commit();

        System.exit(0);
    }

}

With this code and your mapping, I get 2 rows in MySQL and the relationship between the Product and the Vendor is established.

However, I don't understand what you're trying to achieve with your list of vendor keys. For the moment, the table PRODUCT_VENDORKEY stays empty.

You'll have to describe your context further.

Arthur Noseda
  • 2,534
  • 19
  • 28
  • The use of the list `vendor keys` was only to learn how the `@ElementCollection` annotation works. – julianfperez Jul 25 '16 at 17:24
  • In my solution I do not use any Transaction object, so I am not sure if it will work because in your main class the code for persisting the vendor and product objects is the same one than in my test. – julianfperez Jul 25 '16 at 17:31
  • You need a transaction one way or the other. It can be managed by the container if you use one, handled by Spring if you let Spring deal with those, or it is your responsibility. Perhaps if you showed the last part of your code we could get to the bottom of this? – Arthur Noseda Jul 25 '16 at 17:35
  • In the persistence.xml file, I am using this config: ` org.hibernate.ejb.HibernatePersistence` – julianfperez Jul 25 '16 at 19:30