简体   繁体   中英

@ElementCollection does not getting detached

My goal is to clone entity 'Product' with all its filters.

For example, I have an entity (getters and setters omitted for simplicity):

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @ElementCollection()
    private List<Filter> filters = new ArrayList<Filter>();
}

And embeddable class:

@Embeddable
public class Filter {
    @Column(length = 255, nullable = false)
    private String name;
    @Column(nullable = false)
    private long variant = -1;
}

Now, if I do:

entityManager.detach(product);
product.setId(null);
productService.save(product);

I will get a copy of product entity but with filters from original product. In meanwhile original product will end up with no filters at all..

Thats how filter's table rows looks like:

Before:

product_id; name; variant
217; "f2"; 86

After:

product_id; name; variant
218; "f2"; 86

I tried detach each filter from the list but it gives me error.

How can I make it copy filters with an entity?

Edit: Added full Product and Filter code:

package com.serhiy1.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.*;

import org.hibernate.annotations.LazyCollection;
import org.hibernate.annotations.LazyCollectionOption;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.SortableField;
import org.joda.time.DateTime;

import com.serhiy1.constraint.LocalePacker;

@Indexed
@Entity
@EntityListeners(ProductListener.class)
public class Product {
    public static final int PRICE_PER_ONE = 0;
    public static final int PRICE_PER_METER = 1;

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

    private Long code;

    private String name = "";

    private String grouping = "";

    @Field
    @Column(columnDefinition="text")
    private String title = "";

    @Field
    @Column(columnDefinition="text")
    private String intro = "";

    @Column(columnDefinition="text")
    private String content = "";

    @Field
    @Column(columnDefinition="text")
    private String contentHtml = "";

    private String locale = "en";

    private Long parentId = 0L;

    private DateTime time;

    private DateTime timeMod;

    private Long balanceRequired = 0L;

    private Integer index = 0;

    @Field(name = "price_sort")
    @SortableField(forField = "price_sort")
    private Double price = 0.0;

    private Integer pricePer;

    @Transient
    private long childrenCount = 0;
    @Transient
    private String image = "";
    @Transient
    private List<String> images = new ArrayList<String>();

    @ManyToOne(targetEntity = User.class)
    @JoinColumn(nullable = false, name = "user_id")
    @LazyCollection(LazyCollectionOption.FALSE)
    private User user;

    @ManyToOne(targetEntity = Product.class)
    @JoinColumn(nullable = true, name = "category_id")
    @LazyCollection(LazyCollectionOption.FALSE)
    private Product category;

    @ElementCollection()
    private List<Filter> filters = new ArrayList<Filter>();

    @ElementCollection()
    private List<Modifier> modifiers = new ArrayList<Modifier>();

    public Product() {
    }

    @Transient
    private String _title = "";
    @Transient
    private String _intro = "";
    @Transient
    private String _content = "";
    @Transient
    private String _contentHtml = "";

    public void pack(String locale, List<String> locales) {
        if(locale.contains("_")) return;
        title = LocalePacker.repack(locale, _title, title, locales);
        intro = LocalePacker.repack(locale, _intro, intro, locales);
        content = LocalePacker.repack(locale, _content, content, locales);
        contentHtml = LocalePacker.repack(locale, _contentHtml, contentHtml, locales);
    }
    public void unpack(String locale) {
        _title = LocalePacker.unpackStr(locale, title).getOrDefault(locale, "");
        _intro = LocalePacker.unpackStr(locale, intro).getOrDefault(locale, "");
        _content = LocalePacker.unpackStr(locale, content).getOrDefault(locale, "");
        _contentHtml = LocalePacker.unpackStr(locale, contentHtml).getOrDefault(locale, "");
    }
    public void copy(String landFrom, String landTo) {
        title = LocalePacker.copyLang(title, landFrom, landTo);
        intro = LocalePacker.copyLang(intro, landFrom, landTo);
        content = LocalePacker.copyLang(content, landFrom, landTo);
        contentHtml = LocalePacker.copyLang(contentHtml, landFrom, landTo);
    }

    public Modifier getModifier(String name) {
        for(Modifier m: modifiers) {
            if(m.getName().equals(name)) return m;
        }
        return null;
    }

    public Long getId() {
        return id;
    }

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

    public long getCode() {
        return code == null ? id : code;
    }

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

    public String getName() {
        return name;
    }

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

    public String getGrouping() {
        return grouping;
    }

    public void setGrouping(String grouping) {
        this.grouping = grouping;
    }

    public String getTitle() {
        return _title;
    }

    public void setTitle(String title) {
        this._title = title;
    }

    public String getIntro() {
        return _intro;
    }

    public void setIntro(String intro) {
        this._intro = intro;
    }

    public String getContent() {
        return _content;
    }

    public void setContent(String content) {
        this._content = content;
    }

    public String getContentHtml() {
        return _contentHtml;
    }

    public void setContentHtml(String contentHtml) {
        this._contentHtml = contentHtml;
    }

    public String getLocale() {
        return locale;
    }

    public void setLocale(String locale) {
        this.locale = locale;
    }

    public long getParentId() {
        return parentId;
    }

    public void setParentId(long parentId) {
        this.parentId = parentId;
    }

    public DateTime getTime() {
        return time;
    }

    public void setTime(DateTime time) {
        this.time = time;
    }

    public DateTime getTimeMod() {
        return timeMod;
    }

    public void setTimeMod(DateTime timeMod) {
        this.timeMod = timeMod;
    }

    public long getBalanceRequired() {
        return balanceRequired == null ? 0L : balanceRequired;
    }
    public void setBalanceRequired(long balanceRequired) {
        this.balanceRequired = balanceRequired;
    }

    public Integer getIndex() {
        //return index == null ? 1000 : index;
        return index;
    }
    public void setIndex(Integer index) {
        this.index = index;
    }

    public double getPrice() {
        return price == null ? 0.0 : price;
    }
    public void setPrice(double price) {
        this.price = price;
    }

    public int getPricePer() {
        return pricePer == null ? PRICE_PER_METER : pricePer;
    }
    public void setPricePer(int pricePer) {
        this.pricePer = pricePer;
    }

    public long getChildrenCount() {
        return childrenCount;
    }
    public void setChildrenCount(long childrenCount) {
        this.childrenCount = childrenCount;
    }
    public String getImage() {
        return image;
    }
    public void setImage(String image) {
        this.image = image;
    }
    public List<String> getImages() {
        return images;
    }
    public void setImages(List<String> images) {
        this.images = images;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Product getCategory() {
        return category;
    }

    public void setCategory(Product category) {
        this.category = category;
    }

    public List<Filter> getFilters() {
        return filters;
    }

    public void setFilters(List<Filter> filters) {
        this.filters = filters;
    }

    public List<Modifier> getModifiers() {
        return modifiers;
    }

    public void setModifiers(List<Modifier> modifiers) {
        this.modifiers = modifiers;
    }

    public boolean isCategory() { return price < 0; }

    @Override
    public String toString() {
        return "Article{" +
                "id=" + id +
                '}';
    }
}

..

package com.serhiy1.model;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Embeddable;
import javax.persistence.Transient;

@Embeddable
public class Filter {
    @Column(length = 255, nullable = false)
    private String name;
    @Column(nullable = false)
    private long variant = -1;

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public long getVariant() {
        return variant;
    }
    public void setVariant(long variant) {
        this.variant = variant;
    }
}

I made a mini project trying to replicate your issue.

It is a String Boot project with H2 database and JPA (Hibernate implementation).

On startup, Hibernate creates 2 tables:

create table product (
   id bigint not null,
    primary key (id)
)

and

create table product_filters (
   product_id bigint not null,
    name varchar(255) not null,
    variant bigint not null
) 

On product with filters creation, both tables get inserted:

insert 
into
    product
    (id) 
values
    (1)

and

insert 
into
    product_filters
    (product_id, name, variant) 
values
    (1, "f1", 1) 

After:

entityManager.detach(product);
product.setId(null);
productService.save(product);

Hibernate issues:

delete 
from
    product_filters 
where
    product_id=1

which is normal, since filters is an ElementCollection therefore it is totally owned by the entity Product . On productService.save(product) Hibernate detects that filters collection is bound to another Product therefore deletes the old bound (from product_filter table) before creating a new one.

The only way to overcome the deletion is to recreate the collection:

    List<Filter> filters = new ArrayList<Filter>(); 
    filters.addAll(oldFilters);
    product.setFilters(filters);

To sum up, here is the solution:

// To trigger the fetch
List<Filter> filters = new ArrayList<Filter>(product.getFilters());
entityManager.detach(product);
product.setId(null);
product.setFilters(filters);
productService.save(product);

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