NHibernate Cascaded delete not working on one-to-many association - nhibernate

I am trying to delete an object and cascade the delete to the child objects in a one-to-many association. I think that I have done everything correctly for this to work. However, when I run my test, NHibernate attempts to insert a null value into the foreign key column of the child table rather than deleting the items.
From my parent mapping (Carrier):
<set name="Drivers" access="field.camelcase-underscore">
<key column="CarrierId"/>
<one-to-many class="Vehicle"/>
</set>
From my child mapping (Vehicle):
<many-to-one name="Carrier" class="Carrier" column="CarrierId" not-null="true"/>
My test:
[Test]
public void Can_delete_a_carrier_and_associated_vehicles() {
object id;
var carrier = new Carrier { BusinessRef = 759540, Name = "Carrier1" };
var vehicle = new Vehicle { Carrier = carrier, BusinessRef = "FOOBAR", VehicleType = VehicleType.Trailer };
using (var txn = session.BeginTransaction()) {
id = session.Save(carrier);
session.Save(vehicle);
txn.Commit();
}
session.Clear();
using (var txn = session.BeginTransaction()) {
var fromDb = session.Get<Carrier>(id);
Assert.IsNotNull(fromDb);
Assert.AreEqual("FOOBAR", fromDb.Vehicles.First().BusinessRef);
session.Delete(fromDb);
txn.Commit();
}
}
The generated SQL:
INSERT INTO Carriers (...) VALUES (...); select last_insert_rowid();#p0 = 'WSH', #p1 = 759540, #p2 = False
INSERT INTO Vehicles (...) VALUES (...); select last_insert_rowid();#p0 = 2, #p1 = 'FOOBAR', #p2 = 4
SELECT carrier0_.Id, ... FROM Carriers carrier0_ WHERE carrier0_.Id=#p0;#p0 = 4
SELECT vehicles0_.CarrierId as CarrierId1_, ... FROM Vehicles vehicles0_ WHERE vehicles0_.CarrierId=#p0;#p0 = 4
UPDATE Vehicles SET CarrierId = null WHERE CarrierId = #p0;#p0 = 4
It's the line in bold that is causing the test to fail because I have a not null constraint on carrier (see vehicle mapping).
This is what I don't understand, if I have a not-null constraint, why does NHibernate try and insert null into the column.
So what do I need to do to ensure that deleting a carrier, deletes all vehicles?
Thanks,
Ben

After all this, the problem ended up being a typo in one of the other sets defined on the parent object. It was only through trying a few more specific tests that I found I was trying to cast a collection to the wrong type - doh!
So basically, if you use the mapping above then the deletes will cascade (providing you don't make silly typos :))

Related

Different delete result by the same function

I'm removing object in loop by using Crud repository delete function. My object have relation which also should be removed with it (orphanRemoval) + cascadeType = ALL.
But I noticed that first iteration removes object correctly with relations and this sql looks:
Hibernate:
delete
from
base_interval_last_modifications
where
base_interval_id=?
Hibernate:
delete
from
visit
where
id=?
Hibernate:
delete
from
last_modification
where
id=?
Hibernate:
delete
from
base_interval
where
id=?
Second iteration produces this sql statement:
Hibernate:
delete
from
visit
where
id=?
Could someone explain me how its possible that the same function on the same type of object generates another statement and in fact doesnt remove all relations correctly?
UPDATE "more code":
In first loop baseInterval is removed correctly and workInterval is untouched but in second loop only visit is removed without baseInterval.
#Entity
class Visit(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Int? = null,
#OneToOne(cascade = [CascadeType.ALL], orphanRemoval = true)
#JoinColumn
var baseInterval: BaseInterval? = null,
)
#Entity
class BaseInterval(
id: Int? = null,
#OneToOne(mappedBy = "baseInterval")
var visit: Visit? = null,
#ManyToOne
#JsonIgnore
var workInterval: WorkInterval? = null
)
Loop
futureVisits
.forEach {
visitRepository.delete(it)
}

Spring Data JPA persistence - Could not commit JPA transaction - ORA-00001: unique constraint violated

I am trying to save an entity that has a many-to-many association to another entity and cascade the persistence to the associated entity and create the association using spring data jpa repository.
I can insert the parent entity_a which contains a set of entity_b using entityARepository.save(entityA). Spring jpa is taking care of all the inserts needed in the transaction. All the entity_b's get inserted, entity_a's get inserted and the join table in the middle has the association inserted as well. If I update the same entity_a with a new value in, say timestamp column, the same entityARepository.save(entityA) handles this and does a corresponding update.
The problem happens when there already exists entity_b (which has an association between some entity_a) and I try to insert a new entity_a with the same entity_b. It is many to many so this is how the data model is supposed to be. But instead of updating the existing entity_b during this entityA save() transaction, it tries to do inserts on entity_b and a constraint violation exception on the primary key is thrown.
org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: unique constraint (USER1.SYS_C0013494) violated
Error Code: 1
Call: INSERT INTO ENTITY_B (ID, NAME, VALUE, TIME_STAMP) VALUES (?, ?, ?, ?)
bind => [4 parameters bound]
Query: InsertObjectQuery(EntityB [name=shape, value=circle])
The problem is that spring doesn't have update(). It only has save which should handle update if it receives the same primary key. It's not doing that when a new entity_a is saved and has a collection of entity_b, if any entity_b exists, the whole transaction is failing sure to primary key constraint violation of entity_b.
public class EntityA {
#Id
#SequenceGenerator( name = "EntityASeq", sequenceName = "SQ_ENTITY_A", allocationSize = 1, initialValue = 1 )
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "EntityASeq")
#Column(name = "ID")
private Integer id;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
#JoinTable(name = "MY_JOINED_TABLE",
joinColumns = {
#JoinColumn(name = "a_id", referencedColumnName = "ID")},
inverseJoinColumns = {
#JoinColumn(name = "b_id", referencedColumnName = "ID")})
private Set<EntityB> attributes;
// These three columns below have a unique constraint together.
#Column(name = "name")
private String name;
#Column(name = "tenant")
private String tenant;
#Column(name = "type")
private String type;
#Column(name = "timestamp")
private Timestamp timestamp;
}
public class EntityB {
#Id
#SequenceGenerator( name = "EntityBSeq", sequenceName = "SQ_ENTITY_B", allocationSize = 1, initialValue = 1 )
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "EntityBSeq")
#Column(name = "ID")
private Integer id;
#ManyToMany(mappedBy = "attributes")
private Set<EntityA> aSet;
// These two columns below have a unique constraint together.
#Column(name = "name")
private String name;
#Column(name = "value")
private String value;
#Column(name = "timestamp")
private Timestamp timestamp;
}
The id for each is generated by default. I also have a unique constraint on a few columns, which means if an EntityB has the same name/value as an existing one in the database, I want to just update the timestamp. That works if entity_a is already in the table and it has the same entity_b's. A and B's timestamp are updated and no error when I persist with entityARepository.save(entityA). (I do some checking on the db with findOne because the id is auto generated an not known. So if a name/value exist, I don't try to insert with a new id, I use the same one in the db and it works (similarly with entity_atenant/name/type.
It also works when I persist an existing entity_a with updated entity_b's. So if a new entity_b is associated with entity_a (that exists as an association with a different entity_a), etc, that works and the persistence is working.
The issue again, is just on INSERT of entityA via repo.save() when some entity_b
s already exist for other associations. It should be doing:
INSERT INTO entity_a ...
UPDATE entity_b ...
INSERT INTO MY_JOINED_TABLE ...
But it seems like it's doing
INSERT INTO entity_a ...
INSERT INTO entity_b ... -- fails because primary key constraint fails
INSERT INTO MY_JOINED_TABLE ...
EDIT: I tried removing CascadeType.PERSIST but I get an error saying
During synchronization a new object was found through a relationship that was not marked cascade PERSIST: EntityB [name=color, value=blue].
I wanted to try to manually insert/update but I couldn't do that. It wants me to have the EntityA specified with PERSIST because it has associations to the entityB
I tried inserting in the reverse and now I'm having issues inserting from entityB.save() when there already exists some entityA and I'm adding a new entityA to entityB

NHibernate doesn't remove unuseful record when update child collections

When I use Nhibernate 3 to update children, the removed item in children doesn't delete from databse.
Case describe below
class Parent {
string Id;
string Name;
IList Children;
}
class Child {
int ChildId;
string Name;
string Value;
Parent parent;
}
hdm Mapping file shows below
Parent.hdm.xml
<bag name="Children" table="ClientsExt" inverse ="true" cascade="all-delete-orphan" lazy="false">
<key column="ChildId"/>
<one-to-many class="XXXX.Child, XXX"/>
</bag>
Child.hdm.xml
<many-to-one name="Parent" column="Id" class="XXXX.Parent, XXX" not-null="true"/>
Let's suppose there is existing parent which associates a set of children in database
Parent Table
Id = "P1", Name = "Test"
Child Table
ChildId = 1, Id="P1", Name = "N1", Value = "V1"
ChildId = 2, Id="P1",Name = "N1", Value = "V2"
ChildId = 3, Id="P1",Name = "N2", Value = "V3"
In my case, I need to update children partially.
In the updated Parent need to update the record 2
set value of ChildId = 2, to value = "NEWVALUE"
and remove ChildId = 1, Id="P1", Name = "N1", Value = "V1"
and ChildId 3 will be keep.
so I get the Parent from database firstly,
var entity = _parentRepo.getById("P1");
var children = entity.Children;
var updatedChildren = children.ToList<Child>;
var tmpList = new List<Child>();
//biz means the business logic object which contains update info
if (biz.N1.Count > 0){
var existN1 = children.Where(x=>x.Name.Equals("N1")).Select(y=>y.ChildId);
int count = existN1.Count;
int index = 0;
biz.N1.ForEach(x=>{
if(index < count){
tmpList.Add(new Child(){ Id = existN1[index],Name="N1",Value="newValue",Parent = entity });
}else{
tmpList.Add(new Child(){ Name="N1",Value="newValue",Parent = entity });
}
});
updatedChildren.RemoveAll(x=>x.Name.Equals("N1"));
updateChildren.AddRange(tmpList);
}
entity.Children = updateChildren;
//Save the entity
However, in Database, the record 2 update the value to "NEWVALUE", but didn't remove the ChildId = 1, Id="P1", Name = "N1", Value = "V1" .
Why?
thanks in advance.
What happend, is that the above code has broken the session principle, splitted the chain. Whenever we want NHibernate to do wise decisions, we must to keep its hands on the underlying stuff - all the time. And this is not in accordance:
var children = entity.Children;
var updatedChildren = children.ToList<Child>; // a brand new NHibernate-detached coll
...
// operations out of the scope of the NHiberante session
...
entity.Children = updateChildren; // broken chain of information
Behind the scene, NHibernates places its own smart Collection into the entity.Children property. It is tracking information about changes (removed elements, changed..) so if it is asked to persist changes... NHibernate knows...
If we put brand new, disconnected collection, NHibernate can hardly find out, that there is some element missing. No way how to issue DELETE.
Solution: work with the entity.Children reference all the time. Then we'll get what we need...

NHibernate 2 FKs to same PK in one-to-one relationship

I have a one-to-one relationship between an Envelope and a Transaction. I mapped it using a primary key association as below:
<class name="Envelope" table="T_ENVELOPE">
<id name="EnvelopeId" column="ENVELOPE_ID"><generator class="identity"/></id>
<one-to-one class="Transaction" cascade="all" name="Transaction" />
</class>
<class name="Transaction" table="T_TRANSACTION">
<id name="TransactionID" column="TRANSACTION_ID" type="long"><generator class="foreign"><param name="property">Envelope</param></generator></id>
<one-to-one name="Envelope" class="Envelope" constrained="true" />
</class>
This works fine, but my case is complicated since T_TRANSACTION has 2 FKs that both map to T_ENVELOPE's PK (ENVELOPE_ID): TRANSACTION_ID (as shown above) and OLD_DEPRECATED_TXN_ID. So, I have 2 FKs in T_ENVELOPE that have the exact same value (the envelope's id). How can I make NHibernate save ENVELOPE_ID not just in TRANSACTION_ID but also in OLD_DEPRECATED_TXN_ID as well?
I wish I didn't have this requirement, but I'm dealing with legacy code schemas and code. I know I can also do something like:
envelope.Save // this saves Envelope and Transaction's transaction ID but not OLD_DEPRECATED_TXN_ID
envelope.transaction.old_deprecated_txn_id = envelope.id
envelope.Save
But, that results in one insert and one update. How do I do a one insert only?
Here's an example:
T_ENVELOPE
----------
ENVELOPE_ID
1
9
121
T_TRANSACTION
-------------
TRANSACTION_ID | OLD_DEPRECATED_TXN_ID
1|1
9|9
121|121
Depending on your requirements you could:
Write a stored procedure to perform the insert and configure NHibernate to use this procedure in the mappings. I have taken this approach when working with legacy DBs and historic fields that were not being used but had to be populated
Use an Interceptor. Something along the followings lines should work. Not tested though!
public class TransactionEntityInterceptor : EmptyInterceptor
{
public override boolean OnSave(object entity,
object id,
object[] state,
string[] propertyNames,
IType[] types)
{
if (entity is Transaction)
{
Object TransactionID;
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "TransactionID" == propertyNames[i] )
{
TransactionID = state[i];
return true
}
}
for ( int i=0; i<propertyNames.Length; i++ )
{
if ( "OLD_DEPRECATED_TXN_ID" == propertyNames[i] )
{
state[i] = TransactionID;
return true;
}
}
return false;
}
else
{
return true;
}
}
}

NHibernate won't delete orphaned object

I have a few classes that look like this
public class Token
{
public int Id
{
get;
set;
}
public ITokenInstance Instance
{
get;
set;
}
}
public interface ITokenInstance
{
int Id
{
get;
set;
}
Token Token
{
get;
set;
}
}
and mapping files
<class name="Token" >
<id name="Id" >
<generator class="hilo" />
</id>
<any name="Instance" meta-type="class" id-type="Int32" cascade="all-delete-orphan">
<column name="instance_type" />
<column name="instance_id" />
</any>
</class>
<class name="TokenInstanceOne" >
<id name="Id" >
<generator class="hilo" />
</id>
<many-to-one name="Token" class="Token" column="token_id"/>
</class>
I have various implementations of the ITokenInstance interface, all looking in different tables but all using the same baisc structure as shown in the mapping. The problem is that whilst i can add a new ITokenInstance to a Token that has no instance set (null) and it will update correctly I can NOT add a new Instance to a Token that has already got an instance and then Update it, NHibernate will add the new instance i provide but not delete the now un-assigned instance. For example
Token token = Session.Get<Token>(4);
var instance = Session.Get<TokenInstanceOne>(1);
Assert.AreSame(token.Instance, instance);
var newInstance = new TokenInstanceOne();
token.Instance = newInstance;
newInstance.Token = token;
instance.Token = null;
Session.Flush();
This fires SQL to insert the new TokenInstance, and updates the token table to point at it, it does NOT delete the instance that was originaly set to the token. Does anyone know how I can instruct NHibernate to delete the original TokenInstance from the database
EIDT:
I had missed something off that is now included in the code example (setting original TokenInstance's Token reference to null).
Also just to clarify this is the SQL NHibernate is producing;
INSERT INTO TokenInstanceOne (token_id, Id) VALUES (#p0, #p1); #p0 = '4', #p1 = '32768'
UPDATE Token SET instance_type = #p0, instance_id = #p1 WHERE Id = #p2; #p0 = 'ClassLibrary1.TokenInstanceOne', #p1 = '32768', #p2 = '4'
UPDATE TokenInstanceOne SET token_id = #p0 WHERE Id = #p1; #p0 = '', #p1 = '1'
Notice the last Update is setting token_id = '', what i need is for NHibernate to delete the row instead.
NHibernate does not implement a so called persistent garbage collection. There are situations where you need to remove entities explicitly. The cascade is for the case when you delete the Token.
This is your code:
var token = Session.Get<Token>(4);
Assert.IsNotNull(token.Instance);
// remove the old token
Session.Delete(token.Instance);
// assign the new token
var newInstance = new TokenInstance();
token.Instance = newInstance;
newInstance.Token = token;
// don't need to call update, the token is in the session.
// (except you turned off session flush)
// Session.Update(token);
Sorry, I misunderstood your question.
Have you tried setting inverse="true" on your any end?
Alternatively move the cascade to th the other class' mapping.
Read