0

I have a problem with my hibernate casscade. I'm trying to persist a set of document. The data model is the following:

  • Corpus 1 --- n Document 1 --- n TextBlock n --- 1 Speaker n --- 1 Party

My scenario is the following:

SpeakerFacade sf = new SpeakerFacade();
TextBlockFacade tf = new TextBlockFacade();

Corpus corpus = new Corpus();

Document doc1 = new Document(corpus);
TextBlock tb1 = new TextBlock(new Speaker("David", "Müller", new Party("ASDF")), "TB1", doc1);
tf.createTextBlock(tb1);
TextBlock tb2 = new TextBlock(new Speaker("Benedikt", "Müller", new Party("JKLÖ")), "TB2", doc1);
tf.createTextBlock(tb2);
TextBlock tb3 = new TextBlock(sf.findPersonById(1), "TB3", doc1);
tf.createTextBlock(tb3);

So in the first block I create a new TextBlock. With the cascade the rest should be created too. In the second block I create another textblock within the same document. In the last block I create also antoher textblock but with the same speaker. But I constantly getting the following exception:

Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : de.uniba.speechanalyser.persist.model.Document.corpus -> de.uniba.speechanalyser.persist.model.Corpus

Here you can see my model classes (in short form):

Corpus Class

@Entity
public class Corpus implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "corpus_id")
private int id;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "corpus")
private List<Document> documentList;
}

Document Class

@Entity
public class Document implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "document_id")
private int id;

@ManyToOne(cascade = { CascadeType.MERGE },fetch= FetchType.EAGER)
private Corpus corpus;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "document")
private List<TextBlock> textBlockList;
}

TextBlock Class

@Entity
public class TextBlock implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "textblock_id")
private int id;

@Lob
String content;

@ManyToOne(cascade = { CascadeType.MERGE },fetch= FetchType.EAGER)
private Document document;

@ManyToOne(cascade = { CascadeType.MERGE },fetch= FetchType.EAGER)
private Speaker speaker;
}

Speaker Class

@Entity
@NamedQueries({
    @NamedQuery(name = "Speaker.findSpeakerByName", query = "select s from Speaker s where s.firstName = :firstName and s.lastName = :lastName") })
public class Speaker implements Serializable {

private static final long serialVersionUID = 1L;
public static final String FIND_BY_NAME = "Speaker.findSpeakerByName";

@Id
@GeneratedValue
@Column(name = "speaker_id")
private int id;

private String firstName;
private String lastName;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "speaker")
private List<TextBlock> textBlock;

@ManyToOne(cascade = { CascadeType.MERGE },fetch= FetchType.EAGER)
private Party party;
}

Party Class

@Entity
@NamedQueries({
@NamedQuery(name = "Party.findPartyByName", query = "select p from Party p where p.name = :name")
})

public class Party implements Serializable {

private static final long serialVersionUID = 1L;
public static final String FIND_BY_NAME = "Party.findPartyByName";

@Id
@GeneratedValue
@Column(name = "party_id")
private int id;

private String name;

@OneToMany(cascade = { CascadeType.ALL}, mappedBy = "party")
private List<Speaker> speakerList;
}

I'm also strugeling with the relations between the object/tables. Especially with the cascades. I read a lot on stackoverflow but nothing helped really. This is my current approach. When a create each object on it's own like:

Speaker speaker= new Speaker("David", "Müller", pf.findById(1));
sf.createSpeaker(speaker);
speaker = sf.findSpeakerById(1);

And then add it to the TextBlock it works without any problem. So can somebody help me?

Greeting, David

user2346226
  • 31
  • 1
  • 5
  • In the third created TextBlock, do you need to findPersonById or findSpeakerById? – darijan Aug 05 '15 at 12:52
  • And your error message says that you haven't saved the Corpus. Save that first. – darijan Aug 05 '15 at 12:54
  • The findSpeakerById() Method is the right one. My question is, when I create the TextBlock hibernate also create the rest (like document, corpus, party and speaker) or? – user2346226 Aug 05 '15 at 12:59
  • `Corpus corpus = new Corpus(); cf.createCorpus(corpus); Document doc1 = new Document(corpus); df.createDocument(doc1); Party party1 = new Party("ASDF"); pf.createParty(party1); Speaker speaker1 = new Speaker("David", "Müller", party1); sf.createPerson(speaker1); TextBlock tb1 = new TextBlock(speaker1, "TB1", doc1); tf.createTextBlock(tb1);` This works fine, but why is there cascade in that case? – user2346226 Aug 05 '15 at 13:12
  • Could be a few causes. When you persist a new object, you should cascade PERSIST, and not MERGE. Also, cascading is unidirectional and usually works for parent - child relationships. mappedBy does create bi-directional relations but as I said, be wary of the first point (MERGE vs PERSIST) that I mention. – darijan Aug 05 '15 at 13:24

2 Answers2

1

Your Corpus does not get persisted. You cascade only MERGE. See the annotation definition in your Document class:

@ManyToOne(cascade = { CascadeType.MERGE }, fetch= FetchType.EAGER)
private Corpus corpus;

should be:

@ManyToOne(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch= FetchType.EAGER)
//you can simply say CascadeType.ALL if you are sure what you are doing
private Corpus corpus;
darijan
  • 9,725
  • 25
  • 38
  • I added the CascadeType to all my @ManyToOne Relations. But I now i get the following Exception: `Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: de.uniba.speechanalyser.persist.model.Party` I changed the CascadeType in Party to ALL like the other @OneToMany Relations – user2346226 Aug 05 '15 at 13:33
  • 1
    What you are trying to achieve is very complicated. You tried to cascade PERSIST on some detached entity (I don't see in your code where and what do you do with Party object). And I don't think it's a very good idea to cascade everything from everywhere, i.e. save once and all is persisted. It has many problems and flaws (as you can see). I would need much more of your code to tell you why it does not work. I would take a step back, read more about Hibernate and JPA and how to model entities. Revert cascades, and use the previous solution. Just create Corpus, save it and continue work – darijan Aug 05 '15 at 13:37
  • Hmm ok, let me see if I can help you... I'm trying to Create a collection of Documents, carried in a Corpus (Simple storage). These documents contain a few textblocks. Each textblock has a speaker and a speaker is related to a party. The ideal solution for me is to add all the information to the corpus and then create the corpus with all his data (documents, textblocks, speakers and partys). – user2346226 Aug 05 '15 at 13:46
  • My first approach was to create a corpus, then create a document and add it to the list in the corpus. then create some textblocks with speakers and partys and add them to the document. At last I persist the corpus and the all data would persist in the database. But this never worked. – user2346226 Aug 05 '15 at 13:47
  • Yes this should be the way to do it. I agree with darijan. review your cascadeTypes and set them to CascadeType.ALL – Pat B Aug 05 '15 at 14:02
0

Ok, cool I narrowed the problem to only the Speaker-Party-Side of the Relations!

My actual new classes are: Corpus Class

@Entity
public class Corpus implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "corpus_id")
private int id;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "corpus")
private List<Document> documentList = new ArrayList<Document>();

public void addDocument(Document document) {
    if (documentList != null) {
        documentList.add(document);
    }
}
}

Document Class

@Entity
public class Document implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "document_id")
private int id;

@ManyToOne
@JoinColumn(name = "corpus_id")
private Corpus corpus;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "document")
private List<TextBlock> textBlockList = new ArrayList<TextBlock>();

public Document() {

}

public Document(Corpus corpus) {
    this.corpus = corpus;
}

public void addTextBlock(TextBlock textBlock) {
    if (textBlockList != null) {
        textBlockList.add(textBlock);
    }
}
}

TextBlock Class

@Entity

public class TextBlock implements Serializable {

private static final long serialVersionUID = 1L;

@Id
@GeneratedValue
@Column(name = "textblock_id")
private int id;

@Lob
String content;

@ManyToOne
@JoinColumn(name = "document_id")
private Document document;

@ManyToOne
private Speaker speaker;

public TextBlock() {

}

public TextBlock(Speaker speaker, String content, Document document) {
    this.speaker = speaker;
    this.content = content;
    this.document = document;
}
}

Speaker Class

@Entity
@NamedQueries({
    @NamedQuery(name = "Speaker.findSpeakerByName", query = "select s from Speaker s where s.firstName = :firstName and s.lastName = :lastName") })

public class Speaker implements Serializable {

private static final long serialVersionUID = 1L;
public static final String FIND_BY_NAME = "Speaker.findSpeakerByName";

@Id
@GeneratedValue
@Column(name = "speaker_id")
private int id;

private String firstName;
private String lastName;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "speaker")
private List<TextBlock> textBlock;

@ManyToOne
private Party party;

public Speaker() {

}

public Speaker(String firstName, String lastName, Party party) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.party = party;
}
}

Party Class

@Entity
@NamedQueries({
@NamedQuery(name = "Party.findPartyByName", query = "select p from Party p where p.name = :name")})

public class Party implements Serializable {

private static final long serialVersionUID = 1L;
public static final String FIND_BY_NAME = "Party.findPartyByName";

@Id
@GeneratedValue
@Column(name = "party_id")
private int id;

private String name;

@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "party")
private List<Speaker> speakerList = new ArrayList<Speaker>();

public Party() {

}

public Party(String name) {
    this.name = name;
}
}

How do I set the CascadeType on Speaker and Party that when I persist a Corpus the Speaker and the Party is persited too.

My test code is the following:

CorpusFacade cf = new CorpusFacade();

Corpus corpus = new Corpus();
Document document = new Document(corpus);
corpus.addDocument(document);

Party party = new Party("ASDF");
Speaker speaker = new Speaker("David", "Müller", party);

TextBlock textBlock1 = new TextBlock(speaker, "TB1", document);
document.addTextBlock(textBlock1);
TextBlock textBlock2 = new TextBlock(speaker, "TB1", document);
document.addTextBlock(textBlock2);

cf.createCorpus(corpus);

Edit For those who are interessted in my solution of the problem. I create first a new Speaker and a new Party. Then I persist the Party (the Speaker persists automatically). After that I add the Speaker to the TextBlock. Thats all. I couldn't find asolution where the corpus creates the Speaker and Party too. I hope it works for someone.

user2346226
  • 31
  • 1
  • 5