There is 3 entities in MxN relationship, B being association entity. We create them in single TX, persist all of them, and fetch entity with OneToMany association. This association is not initialized after fetch.
Source: https://github.com/alfonz19/springboot222demo/commits/what
#Transactional
#Test
void contextLoads() {
// for(int i = 0; i < 3; i++) {
UUID aId = UUID.randomUUID();
AEntity aEntity = aRepository.save(new AEntity().setId(aId));
UUID bId = UUID.randomUUID();
CEntity cEntity = cRepository.save(new CEntity().setId(bId));
em.flush();
bRepository.save(new BEntity().setAEntity(aEntity).setCEntity(cEntity));
// }
em.flush();
// em.clear();
Iterable<CEntity> centities = cRepository.findAll();
List<BEntity> bEntities =
iterableToStream(centities).flatMap(e -> e.getBEntities().stream()).collect(Collectors.toList());
Assert.assertThat(centities, Matchers.iterableWithSize(1));
Assert.assertThat(bRepository.findAll(), Matchers.iterableWithSize(1));
Assert.assertThat(bEntities.size(), CoreMatchers.is(1));
...
}
Ok, I understand, that when creating BEntity I do not update AEntity and CEntity leaving them corrupted. Calling cRepository.findAll() then does call select on db to get all Cs (even without any evict/flush/clear) but leaves #OneToMany uninitialized. I don't get it. I would understand, if there woulndn't be no call to db at all, but if I fetch Cs anyway to refresh it, why not refresh also the association table. Why's that?
Even more suprisingly aRepository.save(new AEntity().setId(aId)) when doing em.merge (entity has assigned id) the hibernate does load whole MxN structure using 2 left outer joins, even if #OneToMany is lazy. Why's that?? EDIT: ok, that's not surprising at all, that's implication of cascade merge. Compeletely ok.
I'm little bit surprised by this behavior, as there are select issued where they shouldn't be (IIUC), and there aren't ones, where they easily could be.
And to keep the best to the end. With small change: uncommenting for loop and clear, I'm getting full nondeterministic behavior.
source: https://github.com/alfonz19/springboot222demo/tree/nondeterministic
Tests will either work, or produces exception like:
array out of bounds
collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance:
java.lang.NullPointerException
but if I put breakpoint on bEntities variable declaration, cEntities are always correctly created and test then pass. I have no idea what can cause this.
I have answer to non-deterministic behavior problem bonus-question.
One more randomly generated exceptions to the list is org.springframework.orm.jpa.JpaSystemException: Found shared references to a collection and all this behavior just disappers with removal of flatMap. Ie replace:
List<BEntity> bEntities =
StreamSupport.stream(centities.spliterator(), true).flatMap(e -> e.getBEntities().stream()).collect(Collectors.toList());
with
List<BEntity> bEntities = new LinkedList<>();
centities.forEach(e->bEntities.addAll(e.getBEntities()));
and test in (not anymore) "nondeterministic" branch will pass 100%. Not sure why, however it seems, that stream-api is not that safe with hibernate-managed collections.
Related
We are using hibernate search 5.9.2.
We have two entities with A and B. A has One-To-Many relationship with B. And we are using them as below:
#Entity
#Indexed(index="master_index")
public class A{
#IndexedEmbedded
private Set<B> b= new HashSet<>(0);
//Setter and getter for b
}
#Entity
#Indexed(index = "b")
public class B{
#ContainedIn
private A a;
//Setter and getter for a
}
One-to-Many relationship is defined under the .hbm files.
Now when some record is deleted from index B directly(but through hibernate process) the same record is not deleted from 'master-index'.
Let us assume I have a record 'xyz' which is available under index B and is also available under 'A' with a relationship like DUDE(data of A) can contain many data like 'xyz'.
DUDE->xyz
The expected result should be the record should delete from index 'b' as well as from the 'master-index'.
Does hibernate search provides a way to handle this situation.
Identified the reason, why this was not working. Will try to provide those findings below:
Initially in my older code, our system was fetching the to be deleted data with a query and then a executeUpdate() was fired and it was not deleting the data using the session.delete(). And due to this the hibernate cache was not aware of the deletion of the object.
Below is the old and new code:
Older Version:
Query query = session.getNamedQuery("deleteBySeqnumRec");
query.setLong("seqnum", seqnum);
int result = query.executeUpdate();
status.setNumOfRows(result);
New Version
DataTO dataTO = selectById(new DataTO, seqnum);
if(!dataTO.isRecNotFound()) {
session.delete(dataTO);
status.setNumOfRows(1);
}
Hibernate Search expects bi-directional associations to be updated consistently on both sides.
This means that, when you delete B, you are expected to remove it from A.b. This will cause a change in entity A, which will trigger reindexing of the entity.
If A wasn't reindexed, it probably means you forgot to remove B from A.b.
Identified the reason, why this was not working. Will try to provide those findings below:
Initially in my older code, our system was fetching the to be deleted data with a query and then a executeUpdate() was fired and it was not deleting the data using the session.delete(). And due to this the hibernate cache was not aware of the deletion of the object.
Below is the old and new code:
Older Version:
Query query = session.getNamedQuery("deleteBySeqnumRec");
query.setLong("seqnum", seqnum);
int result = query.executeUpdate();
status.setNumOfRows(result);
New Version
DataTO dataTO = selectById(new DataTO, seqnum);
if(!dataTO.isRecNotFound()) {
session.delete(dataTO);
status.setNumOfRows(1);
}
I want to use EF DbContext/POCO entities in a detached manner, i.e. retrieve a hierarchy of entities from my business tier, make some changes, then send the entire hierarchy back to the business tier to persist back to the database. Each BLL call uses a different instance of the DbContext. To test this I wrote some code to simulate such an environment.
First I retrieve a Customer plus related Orders and OrderLines:-
Customer customer;
using (var context = new TestContext())
{
customer = context.Customers.Include("Orders.OrderLines").SingleOrDefault(o => o.Id == 1);
}
Next I add a new Order with two OrderLines:-
var newOrder = new Order { OrderDate = DateTime.Now, OrderDescription = "Test" };
newOrder.OrderLines.Add(new OrderLine { ProductName = "foo", Order = newOrder, OrderId = newOrder.Id });
newOrder.OrderLines.Add(new OrderLine { ProductName = "bar", Order = newOrder, OrderId = newOrder.Id });
customer.Orders.Add(newOrder);
newOrder.Customer = customer;
newOrder.CustomerId = customer.Id;
Finally I persist the changes (using a new context):-
using (var context = new TestContext())
{
context.Customers.Attach(customer);
context.SaveChanges();
}
I realise this last part is incomplete, as no doubt I'll need to change the state of the new entities before calling SaveChanges(). Do I Add or Attach the customer? Which entities states will I have to change?
Before I can get to this stage, running the above code throws an Exception:
An object with the same key already exists in the ObjectStateManager.
It seems to stem from not explicitly setting the ID of the two OrderLine entities, so both default to 0. I thought it was fine to do this as EF would handle things automatically. Am I doing something wrong?
Also, working in this "detached" manner, there seems to be an lot of work required to set up the relationships - I have to add the new order entity to the customer.Orders collection, set the new order's Customer property, and its CustomerId property. Is this the correct approach or is there a simpler way?
Would I be better off looking at self-tracking entities? I'd read somewhere that they are being deprecated, or at least being discouraged in favour of POCOs.
You basically have 2 options:
A) Optimistic.
You can proceed pretty close to the way you're proceeding now, and just attach everything as Modified and hope. The code you're looking for instead of .Attach() is:
context.Entry(customer).State = EntityState.Modified;
Definitely not intuitive. This weird looking call attaches the detached (or newly constructed by you) object, as Modified. Source: http://blogs.msdn.com/b/adonet/archive/2011/01/29/using-dbcontext-in-ef-feature-ctp5-part-4-add-attach-and-entity-states.aspx
If you're unsure whether an object has been added or modified you can use the last segment's example:
context.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
You need to take these actions on all of the objects being added/modified, so if this object is complex and has other objects that need to be updated in the DB via FK relationships, you need to set their EntityState as well.
Depending on your scenario you can make these kinds of don't-care writes cheaper by using a different Context variation:
public class MyDb : DbContext
{
. . .
public static MyDb CheapWrites()
{
var db = new MyDb();
db.Configuration.AutoDetectChangesEnabled = false;
db.Configuration.ValidateOnSaveEnabled = false;
return db;
}
}
using(var db = MyDb.CheapWrites())
{
db.Entry(customer).State = customer.Id == 0 ?
EntityState.Added :
EntityState.Modified;
db.SaveChanges();
}
You're basically just disabling some extra calls EF makes on your behalf that you're ignoring the results of anyway.
B) Pessimistic. You can actually query the DB to verify the data hasn't changed/been added since you last picked it up, then update it if it's safe.
var existing = db.Customers.Find(customer.Id);
// Some logic here to decide whether updating is a good idea, like
// verifying selected values haven't changed, then
db.Entry(existing).CurrentValues.SetValues(customer);
In EclipseLink, I run into a problem where an element is inserted twice, resulting into a primary key violation. The scenario is as follows:
I have three entities, Element, Restriction and RestrictionElement. The entity RestrictionElement acts as a many-to-many relationship between the two others.
When I create a new RestrictionElement and merge the Element, the RestrictionElement is inserted twice. The code:
// element is an Element, restriction is a Restriction. Both are already in present in the database.
RestrictionElement newRestrictionElement = new RestrictionElement(restriction, element);
Transaction transaction = new Transaction();
em.merge(element); //em is the EntityManager
transaction.commit();
However, if I remove the line restriction.getReferencedRestrictionElements().add(this); the RestrictionElement is inserted once.
Can anyone explain why this happens? Or point to a document that explains how to work out what the merge() command does?
Relevant JPA code: (I'll only given a small part. There aren't any other big problems with the code.)
public class RestrictionElement {
#JoinColumns({#JoinColumn(name = "ELEMENT_ID", referencedColumnName = "ID"),#JoinColumn(name = "ELEMENT_DESCRIPTOR", referencedColumnName = "DESCRIPTOR")})
private Element element;
#JoinColumns({#JoinColumn(name = "RESTRICTION_ID", referencedColumnName = "ID"),#JoinColumn(name = "RESTRICTION_DESCRIPTOR", referencedColumnName = "DESCRIPTOR")})
private Restriction restriction;
public RestrictionElement(Restriction restriction, Element element) {
this.restriction = restriction;
this.element = element;
restriction.getReferencedRestrictionElements().add(this);
element.getReferingRestrictionElements().add(this);
}
}
public class Element {
#OneToMany(mappedBy = "element")
private List<RestrictionElement> referingRestrictionElements = new ArrayList<RestrictionElement>();
}
public class Restriction extends Element {
#OneToMany(mappedBy = "restriction", cascade = { ALL, PERSIST, MERGE, REMOVE, REFRESH })
private List<RestrictionElement> referencedRestrictionElements = new ArrayList<RestrictionElement>();
}
How do your persist RestrictionElement? My guess is when you persist it you get one copy, then a second when you merge the Element with the reference to it.
Try using persist() for new objects, and related the objects after they are managed with the correct managed copy.
I got a similar issue when I run my program, but the issue is not there under step by step debugging.
I resolved the issue by changing List to Set in the OneToMany relationship.
Don't forget that once you retrieve an instance of the class using JPA, the instance becomes managed, any changes to it will be automatically merged into the database.
By default, this merge will occur at the moment you query the table. Therefore the following situation can happen:
query (find by ID)
update (setName = "xx")
query another class that has a direct relationship to this one (find by ID again)
in a situation similar to the above, the second find will effectively issue a merge to the first table. (I'm not sure exactly of the details or scenarios here).
My suggestion is that you issue every single query (findById for example) or every instance you have before you start modifying it (ie, set, etc).
Hope it helps.
I have an entity with self reference (generated by Entity Designer):
public MyEntity: EntityObject
{
// only relevant stuff here
public int Id { get...; set...; }
public MyEntity Parent { get...; set...; }
public EntityCollection<MyEntity> Children { get...; set...; }
...
}
I've written a stored procedure that returns a subtree of nodes (not just immediate children) from the table and returns a list of MyEntity objects. I'm using a stored proc to avoid lazy loading of an arbitrary deep tree. This way I get relevant subtree nodes back from the DB in a single call.
List<MyEntity> nodes = context.GetSubtree(rootId).ToList();
All fine. But when I check nodes[0].Children, its Count equals to 0. But if I debug and check context.MyEntities.Results view, Children enumerations get populated. Checking my result reveals children under my node[0].
How can I programaticaly force my entity context to do in-memory magic and put correct references on Parent and Children properties?
UPDATE 1
I've tried calling
context.Refresh(ClientWins, nodes);
after my GetSubtree() call which does set relations properly, but fetches same nodes again from the DB. It's still just a workaround. But better than getting the whole set with context.MyEntities().ToList().
UPDATE 2
I've reliably solved this by using EF Extensions project. Check my answer below.
You need to assign one end of the relationship. First, divide the collection:
var root = nodes.Where(n => n.Id == rootId).First();
var children = nodes.Where(n => n.Id != rootId);
Now, fix up the relationship.
In your case, you'd do either:
foreach (var c in children)
{
c.Parent = root;
}
...or:
foreach (var c in children)
{
root.Children.Add(c);
}
It doesn't matter which.
Note that this marks the entities as modfied. You'll need to change that if you intend to call SaveChanges on the context and don't want this saved.
The REAL solution
Based on this article (read text under The problem), navigation properties are obviously not populated/updated when one uses stored procedures to return data.
But there's a nice manual solution to this. Use EF Extensions project and write your own entity Materilizer<EntityType> where you can correctly set navigation properties like this:
...
ParentReference = {
EntityKey = new EntityKey(
"EntityContextName.ParentEntitySetname",
new[] {
new EntityKeyMember(
"ParentEntityIdPropertyName",
reader.Field<int>("FKNameFromSP")
)
})
}
...
And that's it. Calling stored procedure will return correct data, and entity object instances will be correctly related to eachother. I advise you check EF Extensions' samples, where you will find lots of nice things.
I have the following code snippet.
// Here I get an entity out of an EntityCollection<TEntity> i.e ContactSet. Entity is obtained and not null.
ProjectContact obj = ((Project)projectDataGrid.SelectedItem).ContactSet
.Where(projectContact => projectContact.ProjectId == item.ProjectId &&
projectContact.ContactId == item.ContactId).First();
// And the next line I just check whether ContactSet contains the queried entity that i.e. obj.
bool found = ((Project)projectDataGrid.SelectedItem).ContactSet.Contains(obj);
but found is always false. How can that be?
edit: Matt thank you for your guidance but let me make it a bit more clear since I haven't given out the full source code.
I have three tables in my database:
Project, Contact and ProjectContact and there's a many-to-many relationship between Project and Contact table through the ProjectContact table, although ProjectContact table has some extra columns other than Project and Contact table keys, and that's why I get an extra entity called ProjectContact if I use ADO.NET entity framework's entity designer generated code.
Now at some point I get a Project instance within my code by using a linq to entities query i.e:
var item = (from project in myObjectContext.Project.Include("ContactSet.Contact")
orderby project.Name select project).FirstOrDefault();
Note that ContactSet is the navigational property of Project to ProjectContact table and Contact is the navigational property of ProjectContact to Contact table.
Moreover the queried Project in question i.e. "item" has already some ProjectContacts in its item.ContactSet entity collection, and ContactSet is a standard EntityCollection implementation generated by entity designer.
On the other hand, ProjectContact overrides Equals() and GetHashCode() etc. but if I use that overriden implementation within an EqualityComparer then Project.ContactSet.Contains returns true so I'm guessing there's no problem with that but now the tricky part comes along. Assume that I have the following snippet:
using(SomeObjectContext myObjectContext = new SomeObjectContext())
{
var projectQueryable = from project in myObjectContext.Project.Include("ContactSet.Contact") orderby project.Name select project;
ObservableCollection<Project> projects = new ObservableCollection<Project>(projectQueryable.ToList());
var contactQueryable = from contact in myObjectContext.Contact select contact;
ObservableCollection<Contact> contacts = new ObservableCollection<Contact>(contactQueryable.ToList());
Project p = projects[0];
Contact c = contacts[0];
//Now if I execute the code below it fails.
ProjectContact projectContact = new ProjectContact();
projectContact.Contact = c;
projectContact.Project = p;
projectContact.ContactId = c.Id;
projectContact.ProjectId = p.Id;
projectContact.Role = ContactRole.Administrator; // This corresponds to the column in ProjectContact table and I do manual conversion within the partial class since EF doesn't support enums yet.
p.ContactSet.Add(projectContact); // This line might be unnecessary but just to be on the safe side.
// So now p.ContactSet does indeed contain the projectContact and projectContact's EntityState is Added as expected. But when I execute the line below without saving changes it fails.
bool result = p.ContactSet.Remove(projectContact); // result == false and projectContact is still in the p.ContactSet EntityCollection.
//Now if I execute the line below
myObjectContext.Delete(projectContact);
//Now projectContact's EntityState becomes Detached but it's still in p.ContactSet.
// Also note that if I test
bool exists = p.ContactSet.Contains(projectContact);
// It also returns false even if I query the item with p.ProjectContact.Where(...) it returns false.
}
Since everything occurs within the same ObjectContext I think I am missing something about EntityCollection.Remove(). But it seems still odd that ContactSet.Contains() returns false for an item obtained via direct Where query over ContactSet. In the end the question becomes:
How do you really Remove an item from an EntityCollection without persisting to the database first. Since a Remove() call after Add() apparently fails.
This looks like it should work, Some ideas:
Does ProjectContact override Object.Equals()? Or perhaps the ContactSet implements ICollection, and there may be a bug in the implementation of ICollection.Contains()?