问题
I have a class with several lazily initialized collections, both OneToMany
and ManyToMany
. The ManyToMany
relationship is unidirectional, so the Expert
class has no Project
collection.
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "project")
private List<Logging> loggings = new ArrayList<Logging>();
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinTable(name = "project_expert", joinColumns = { @JoinColumn(name = "project") }, inverseJoinColumns = { @JoinColumn(name = "expert") })
private List<Expert> experts = new ArrayList<Expert>();
public void add(Logging logging) {
logging.setProject(this);
this.loggings.add(logging);
}
public void add(Expert expert) {
this.experts.add(expert);
}
To add an object to a OneToMany
collection, I would do this:
Project project = projectService.save(projectForm.getProject());
Logging logging = new Logging();
project.add(logging);
logging = loggingService.save(logging);
This works fine. However, when I try to add an element to the ManyToMany
collection, the collection seems to be deleted upon saving the parent object, so I can never add more than one child object:
@RequestMapping(value="/newexpert", method=RequestMethod.POST)
public String newExpert(@ModelAttribute("projectForm") ProjectForm projectForm, BindingResult result, ModelMap model) {
Project project = projectService.save(projectForm.getProject());
Expert expert = projectForm.getExpert();
if (expert != null) {
project.add(expert);
}
project = projectService.save(project);
return "redirect:/project/" + project.getId();
}
This is what the service method looks like (projectRepository
extends PagingAndSortingRepository
):
@Override
@Transactional
public Project save(Project project) {
return projectRepository.save(project);
}
In the logs I found this:
2014-05-01 17:01:49 DEBUG AbstractCollectionPersister:1174 - Deleting collection: [test.model.Project.experts#1]
2014-05-01 17:01:49 DEBUG SQL:109 - delete from project_expert where project=?
2014-05-01 17:01:49 DEBUG AbstractCollectionPersister:1232 - Done deleting collection
Edit 1: This is what happens upon save(projectForm.getProject())
:
2014-05-01 18:30:04 DEBUG Loader:2136 - Loading entity: [test.model.Project#1]
2014-05-01 18:30:04 DEBUG SQL:109 - select project0_.id as id1_4_1_, ... from Project project0_ left outer join project_expert experts1_ on project0_.id=experts1_.project left outer join Expert expert2_ on experts1_.expert=expert2_.id where project0_.id=?
2014-05-01 18:30:04 DEBUG Loader:951 - Result set row: 0
2014-05-01 18:30:04 DEBUG Loader:1485 - Result row: EntityKey[test.model.Expert#2], EntityKey[test.model.Project#1]
2014-05-01 18:30:04 DEBUG Loader:1305 - Found row of collection: [test.model.Project.experts#1]
2014-05-01 18:30:04 DEBUG TwoPhaseLoad:160 - Resolving associations for [test.model.Project#1]
2014-05-01 18:30:04 DEBUG TwoPhaseLoad:286 - Done materializing entity [test.model.Project#1]
2014-05-01 18:30:04 DEBUG CollectionLoadContext:232 - 1 collections were found in result set for role: test.model.Project.experts
2014-05-01 18:30:04 DEBUG CollectionLoadContext:280 - Collection fully initialized: [test.model.Project.experts#1]
2014-05-01 18:30:04 DEBUG CollectionLoadContext:240 - 1 collections initialized for role: test.model.Project.experts
2014-05-01 18:30:04 DEBUG Loader:2160 - Done entity load
2014-05-01 18:30:04 DEBUG AbstractTransactionImpl:175 - committing
2014-05-01 18:30:04 DEBUG AbstractFlushingEventListener:149 - Processing flush-time cascades
2014-05-01 18:30:04 DEBUG AbstractFlushingEventListener:189 - Dirty checking collections
2014-05-01 18:30:04 DEBUG CollectionEntry:202 - Collection dirty: [test.model.Project.loggings#1]
2014-05-01 18:30:04 DEBUG CollectionEntry:202 - Collection dirty: [test.model.Project.parties#1]
2014-05-01 18:30:04 DEBUG CollectionEntry:202 - Collection dirty: [test.model.Project.experts#1]
2014-05-01 18:30:04 DEBUG Collections:194 - Collection found: [test.model.Project.experts#1], was: [test.model.Project.experts#1] (initialized)
2014-05-01 18:30:04 DEBUG Collections:201 - Collection found: [test.model.Project.loggings#1], was: [test.model.Project.loggings#1] (uninitialized)
2014-05-01 18:30:04 DEBUG Collections:201 - Collection found: [test.model.Project.parties#1], was: [test.model.Project.parties#1] (uninitialized)
2014-05-01 18:30:04 DEBUG AbstractFlushingEventListener:123 - Flushed: 0 insertions, 0 updates, 0 deletions to 3 objects
2014-05-01 18:30:04 DEBUG AbstractFlushingEventListener:130 - Flushed: 0 (re)creations, 3 updates, 0 removals to 3 collections
2014-05-01 18:30:04 DEBUG EntityPrinter:114 - Listing entities:
2014-05-01 18:30:04 DEBUG EntityPrinter:121 - test.model.Expert{id=2, ...}
2014-05-01 18:30:04 DEBUG EntityPrinter:121 - test.model.Project{..., id=1, ...}
2014-05-01 18:30:04 DEBUG EntityPrinter:121 - test.model.Expert{id=1, ...}
2014-05-01 18:30:04 DEBUG SQL:109 - select count(id) from Logging where project_id =?
2014-05-01 18:30:04 DEBUG SQL:109 - select count(id) from Party where project_id =?
2014-05-01 18:30:04 DEBUG AbstractCollectionPersister:1174 - Deleting collection: [test.model.Project.experts#1]
2014-05-01 18:30:04 DEBUG SQL:109 - delete from project_expert where project=?
2014-05-01 18:30:04 DEBUG AbstractCollectionPersister:1232 - Done deleting collection
2014-05-01 18:30:04 DEBUG JdbcTransaction:113 - committed JDBC Connection
2014-05-01 18:30:04 DEBUG JdbcTransaction:126 - re-enabling autocommit
...
2014-05-01 18:30:04 DEBUG AbstractLoadPlanBasedCollectionInitializer:88 - Loading collection: [test.model.Project.loggings#1]
2014-05-01 18:30:04 DEBUG SQL:109 - select loggings0_.project_id as project_5_4_0_, loggings0_.id as id1_2_0_, ... from Logging loggings0_ where loggings0_.project_id=?
2014-05-01 18:30:04 DEBUG ResultSetProcessorImpl:168 - Preparing collection intializer : [test.model.Project.loggings#1]
2014-05-01 18:30:04 DEBUG ResultSetProcessorImpl:127 - Starting ResultSet row #0
2014-05-01 18:30:04 DEBUG CollectionReferenceInitializerImpl:77 - Found row of collection: [test.model.Project.loggings#1]
2014-05-01 18:30:04 DEBUG TwoPhaseLoad:160 - Resolving associations for [test.model.Logging#1]
2014-05-01 18:30:04 DEBUG TwoPhaseLoad:286 - Done materializing entity [test.model.Logging#1]
2014-05-01 18:30:04 DEBUG CollectionLoadContext:232 - 1 collections were found in result set for role: test.model.Project.loggings
2014-05-01 18:30:04 DEBUG CollectionLoadContext:280 - Collection fully initialized: [test.model.Project.loggings#1]
2014-05-01 18:30:04 DEBUG CollectionLoadContext:240 - 1 collections initialized for role: test.model.Project.loggings
2014-05-01 18:30:04 DEBUG AbstractLoadPlanBasedCollectionInitializer:118 - Done loading collection
Edit 2: Apparently the ManyToMany
collection in the parent object projectForm.getProject()
is initialized and empty. How can that be? I expect it to be unitialized, or if it is initialized for some reason, not empty?
回答1:
Create a method in your service like this:
@Transactional
public Project addExperts(Project project, List<Expert> experts){
List<Expert> projectExperts = project.getExperts();
for(Expert expert: experts){
projectExperts.add(expert);
}
project.setExperts(projectExperts);
return project;
}
This will encapsulate the fetching of Project.experts inside a Transaction so that the lazily loaded collection can be fetched and initialised properly. By adding the list you have determined in your controller, you can then save the project properly and have the cascade create the new expert objects. I'm pretty sure this should fix your problem.
If you don't see INSERT
statements creating your Expert
objects, then thats your problem. Hibernate can't insert a reference to your Expert
objects into your JoinTable
if they don't exist in the database, hence why it would delete your collection object.
回答2:
I think I might have the culprit.
You're initializing the ManyToMany collection as
private List<Expert> experts = new ArrayList<Expert>();
and then your add()
method accesses the collection as this.experts.add(..)
. Yet the ManyToMany association is LAZY
. So how would the JPA provider know that you didn't intend to override the collection with a new one instead?
Try changing add()
with the getter for the collection, as
public void add(Expert expert) {
this.getExperts().add(expert);
}
来源:https://stackoverflow.com/questions/23410753/manytomany-collection-keeps-getting-deleted