2
votes

I'm using JPA / Hibernate.

Let's say I have folders and files. Every folder can contain more folders and files. Every file knows what its root folder is (not the parent).

@Entity
public class Folder{
    ...

    @OneToMany
    @JoinColumn("folder_id")
    private List<Folder> folders;

    @OneToMany
    @JoinColumn("file_id")
    private List<File> files;

    ...
}

@Entity
public class File {
    ...

    @ManyToOne
    private Folder rootFolder;

    ...
}

I create a new file and a new folder. I put the file in the folder. Now the file is in the folders "files"-collection and the folder is referenced in the files "rootFolder"-variable.

If I persist this, I get the "object references an unsaved transient instance" or "save the transient instance before flushing: entities.file.rootFolder -> entities.Category" (depends on what gets persisted first).

There hast to be a way I can annotate this, so it will work not matter what I save first!? I could work around it somehow programmatically to insert first one Entity without referencing the other, then insert the other and then put in the reference, but I don't think that that should be neccessary here.

I searched for this, but all I could find was @OneToMany combined with @ManyToOne, but in my case I can't use it. A folder shouldn't know if it is a root folder.

I'd appreciate any thoughts or directions to tutorials.

2

2 Answers

5
votes

There's no problem with those mappings or with the scenario you describe, assuming it all occurs within one transaction, or, more accurately, with no flushing in between. The errors you describe only happen upon flush, which typically happens upon transaction commit. It even tells you in the error, "save the transient instance before flushing". What's disallowed is flushing a partially-saved object graph. As long as all objects in a graph are saved before the flush happens, you're fine.

I've created a working example on Github based on your code. You can browse the code and/or clone the project and run it with:

git clone git://github.com/zzantozz/testbed.git tmp
cd tmp/stackoverflow/14921963-hibernate-circular-reference
mvn -q compile exec:java -D exec.mainClass=com.foo.Main
0
votes

Your code should work if you persist both objects within the same transaction.

However, in the relationship between files and folders, you're incorrectly marking the owner. The many-to-one side should almost always be the owner of the relationship. Therefore, the correct way would be:

@Entity
public class File {
  @ManyToOne
  @JoinColumn(name = "folder_id") // this table has a foreign key
  private Folder rootFolder;
}

@Entity
public class Folder {
  @OneToMany(mappedBy = "rootFolder") // 'File' is the owner of this relationship
  private List<File> files;
}

Similarly, in Folder, it should be just:

@OneToMany 
private List<Folder> folders;

Or, you may go with a bi-directional relationship:

@Entity
public class Folder {
  @ManyToOne
  private Folder parent;

  @OneToMany(mappedBy = "parent")
  private List<Folder> folders;

I hope this helps.