Different delete result by the same function - sql

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)
}

Related

Subquery - JPA - Include Subquery as part of the main selection

I have an issue to add the result of a subquery as part of the selection.
This is how my data model looks like:
#Entity
#Table(name = "flow")
public class Flow {
#Id
Long id;
#OneToMany(mappedBy="flow", fetch = FetchType.LAZY)
Set<Subscription> subscribers;
... other internal fields
//extra fields that come from other tables
#Transient //as it is not part of the model sometimes must be null
Long numberOfActiveSubscribers;
}
#Entity
#Table(name = "subscriptions")
public class Subscription {
#Id
Long id;
#ManyToOne
#JoinColumn(name = "idflow")
Flow flow;
#OneToMany(name = "user")
User user;
#Column(name = "active")
boolean active;
}
I need to implement a query using jpa specifications that will include in its definition the number of user that have active subscriptions to the flow. So that I will be able to use pagination over that, including sort by this field that is not part of the original flow table. The SQL query I came out with look like this:
SELECT f.*,
(SELECT count(sf.id)
FROM subscription sf
WHERE sf.active = true
AND sf.idf = f.id) as numberofactivesubscribers,
FROM flow f;
I would like to us it in a findAll method like so:
this.repository.findAll(new Specification<Flow>() {
#Override
public Predicate toPredicate(Root<Flow> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
Subquery<Long> sq = query.subquery(Long.class);
Root<Subscription> subsRoot = sq.from(Subscription.class);
sq.select(cb.count(subsRoot.get("id")))
.where(cb.equal(subsRoot.get("flow"), flowRoot),
cb.equal(subsRoot.get("active"), true));
//I DONT KNOW HOW TO INCLUDE THIS AS A PART OF THE MAIN SELECT OF THIS QUERY
}
}, pagination);
But as you can see I don't know how to include the subquery as part of the select method as I did in my SQL query.

micronaut-data and composite key mapping

I have an entity with a composite key
#Entity
data class Page(
#EmbeddedId
val pageId : PageId,
...
)
#Embeddable
data class PageId (
#Column(name = "id")
val id: UUID,
#Column(name = "is_published")
val isPublished: Boolean
)
But I need to respect the existing column names in the db table, which are 'id' and 'is_published'
But querying the db with a JDBCRepository I get the error:
SQL Error executing Query: ERROR: column page_.page_id_published does
not exist
Is there any way that I can map the columns correctly?
Try and error led me to the answer, somehow Micronaut does not like a Boolean to be named 'isPublished', when I rename it to 'published' it works fine:
data class PageId (
#MappedProperty(value = "id")
val id: UUID,
#MappedProperty(value = "is_published")
val published: Boolean)

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

Hibernate : map field with condition without foreign key

I have the following structure to store data for tracking devices (reduced complexity to simplify) in a MySQL database.
I use Hibernate to handle this data in a web application.
Tables structure
tag
id
name (string)
last_tag_detail_id (foreign key on tag_detail table)
tag_detail
id
tag_id (foreign key on tag table)
date (UTC date)
lat (gps latitude, may be null if no GPS position)
lng (gps longitude, may be null is no GPS position)
Problem
I have a requirement that when I retrieve a tag, I should get the last tag_detail (which I have thanks to the tag_detail_id foregin key).
Now I also have to get the most recent tag_detail with a valid GPS position (latitude and longitude != null).
Is it possible to do so with Hibernate annotations without having to add a last_tag_detail_gps_id foreign key in the tag table ?
For now I have the following mapping :
Tag class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#Column(name = "mac")
private String name;
#OneToOne(fetch = FetchType.LAZY, cascade = { CascadeType.MERGE, CascadeType.REFRESH })
#JoinColumn(name = "last_tag_detail")
private TagDetail lastTagDetail;
TagDetail class
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", unique = true, updatable = false, nullable = false)
private Long id;
#ManyToOne(fetch = FetchType.EAGER, cascade = { CascadeType.REFRESH, CascadeType.MERGE })
#JoinColumn(name = "tag_id", nullable = false)
private Tag tag;
#Column(name = "lat")
private BigDecimal latitude;
#Column(name = "lng")
private BigDecimal longitude;
#Column(name = "data", nullable = false)
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
private DateTime date;
Is it possible without adding a foreign key in the tag table to add a lastTagDetailWithGPS field in the Tag class, which would retrieve the last TagDetail by date which have a non null latitude and longitude ? If so, what would be the proper Jpa or Hibernate annotation to do so ?
Thank you
You say you wanted to:
retrieve the last TagDetail by date which have a non null latitude and
longitude
I don't think you need to add an additional foreign key to your tag table, since in your Tag entity you already have a reference to a TagDetail entity mapped as one-to-one. In your JPA query, you can just simply traverse to your TagDetail entity and check on the values of its properties: latitude, longitude and date.
Here's your JPA query:
SELECT td FROM Tag t INNER JOIN TagDetail td
WHERE td.date = :date AND
td.latitude IS NOT NULL AND
td.longitude IS NOT NULL

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

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 :))