由级联属性引起的分离实体错误

时间:2016-07-25 11:21:11

标签: java hibernate jpa

我是JPA和Hibernate的新手。我有两个实体,供应商和产品,如下所述。

@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;
    }
}

我的问题与@ManyToOne注释中属性nullable = false的使用有关。我希望用@ManyToOne注释的相应列不为null。但是,当我通过以下测试测试实体的持久性时:

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);
...
}

其中createOrUpdateEntity方法是DAO Bean对象的一部分

 @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;
    }

我收到错误:

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

编辑:实现这个example,它使用注释为nullable = false的@ManyToOne列,我意识到我的问题与此无关。如前面的服务器日志所示,问题是由Cascade类引起的。 SO中的一些solutions建议通过CascadeType.MERGE更改CascadeType.ALL。其他ones显示不同的策略。甚至一些ones告诉我只有关联的OneToMany端应该使用级联。坦率地说,我认为在关联的两侧使用CascadeType.ALL是最好的选择。但似乎我错了。虽然,正如我已经指出的那样,我是JPA和Hibernate的新手,我不明白这些推理。

2 个答案:

答案 0 :(得分:1)

您可以使用 optional 标记进行空值控制:

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

答案 1 :(得分:1)

实际上,使用此代码,我可以让您的示例起作用:

的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(简称Brockhaus

包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);
    }

}

使用此代码和映射,我在MySQL中得到2行,并建立了产品与供应商之间的关系。

但是,我并不了解您尝试使用供应商密钥列表实现的目标。目前,表PRODUCT_VENDORKEY保持空白。

您必须进一步描述您的背景。