Traversing a collection causes SQL update - spring

I am struggling with this..
Whenever I traverse a collection inside a transaction it will trigger an SQL update for one of its field.
#Entity
class A {
#ManyToOne
#JoinColumn(name = "b1_id")
private B b1;
#ManyToOne
#JoinColumn(name = "b2_id")
private B b2;
}
Unidirectional so B knows nothing about A.
#org.springframework.transaction.annotation.Transactional //1. do nothing
private void doSomething(){
List<A> aEntities = aRepository.findAll();
}
#org.springframework.transaction.annotation.Transactional //2.traversing the list
private void doSomething(){
List<A> aEntities = aRepository.findAll();
for (A a: aEntities){
//dont do anything
}
}
private void doSomething(){ // 3.no transaction
List<A> aEntities = aRepository.findAll();
for (A a: aEntities){
//dont do anything
}
}
SQL log shows:
1. only SELECT is generated
2. UPDATE for b1 (but not for b2) !!!!!
3. only SELECT is generated
And on the top of that 2 UPDATE will be run for b1.
If I turn on JPA versioning I can see that b1's version is incremented by 2, b2 left untouched.(If I remove b2, the same update will be launched for b1)
This drives my nuts....
The class B is unimportant imo because for the other instance of it no update will be generated.
And I have several other classes with manytoany assocciations but never encountered such an issue.
This situation comes up only for b1 in entity A.

Related

How to do lazy loading of the non-owning entity in a unidirectional one-to-one mapping?

#Entity
#Table(name = "...")
public class A
{
private Integer id;
...
...
private B b;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "b_id")
getB() {
return b;
}
setB(B b) {
this.b = b;
}
...other methods...
}
#Entity
#Table(name = "...")
public class B
{
private Integer id;
...
...
...methods...
}
When using
Query query = sessionFactory.getCurrentSession().createQuery()
with
query.uniqueResult()
or
query.list()
to fetch an instance of object A from the db,
I am getting the following error message:
org.hibernate.LazyInitializationException: could not initialize proxy [com.project.core.entity.B#261493] - no Session
The above query method is deprecated, however, I am working on an old project that uses Spring (not Spring Boot), and this is the only way I can go about it.
Now, I know that this may be because the hibernate session is already closed, however, I am using join fetch in my hibernate queries above.
I cannot use a shared primary key for the entities because in the future more entities like C, D, etc. will be added which will also have a unidirectional one-to-one mapping with B.
How do I go about fixing this problem? Thank you.

Spring Boot - relationship deleted on save() method

My problem is this: There is a many to many relationship between two tables - Project and Employee. There is an option to update a given employee, but there is a little problem. After updating the employee, hibernate automatically deletes the employee's record from the connected project_employee table.
Hibernate: update employee set email=?, first_name=?, last_name=? where employee_id=?
And this happens right after that
Hibernate: delete from project_employee where employee_id=?
I'm following a course and I've just noticed this error. Source code of the lecturer is here:
https://github.com/imtiazahmad007/spring-framework-course
I've checked your github page:
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH, CascadeType.PERSIST},
fetch = FetchType.LAZY)
#JoinTable(name="project_employee",
joinColumns=#JoinColumn(name="employee_id"),
inverseJoinColumns= #JoinColumn(name="project_id")
)
#JsonIgnore
private List<Project> projects;
CascadeType.MERGE + CascadeType.PERSIST mean, that if Employee entity is saved, Project entity references must be saved.
In may-to-many cases it means:
DELETE by foreign key
Bulk insert
In case there's no bulk insert, there's an issue with persisntence context (your are saving an entity with empty collection of projects).
Possible solutions:
Remove CascadeType.MERGE + CascadeType.PERSIST if you do not want to change projects every time your save Employee. You can still save the ccollection via Repository
Make sure collection is attached on save action. That will cause Delete+Insert, but the resut will be ok.
Change Many-To-Many to One-To-Many with EmbeddedId
Please, refer to documentation:
When an entity is removed from the #ManyToMany collection, Hibernate simply deletes the joining record in the link table. Unfortunately, this operation requires removing all entries associated with a given parent and recreating the ones that are listed in the current running persistent context.
https://docs.jboss.org/hibernate/orm/5.6/userguide/html_single/Hibernate_User_Guide.html#associations-many-to-many
*** Update from dialog below to make cascade clear.
Say, you have two entities A & B (getters and setters omitted). + repos
#Entity
#Table(name = "a")
public class A {
#Id
private Integer id;
private String name;
#ManyToMany(cascade = {CascadeType.ALL})
#JoinTable(name="a_b",
joinColumns=#JoinColumn(name="a_id"),
inverseJoinColumns= #JoinColumn(name="b_id")
)
private List<B> bs;
}
#Entity
#Table(name = "b")
public class B {
#Id
private Integer id;
private String name;
}
You sample test looks like this:
#Test
public void testSave() {
B b = new B();
b.setId(1);
b.setName("b");
b = bRepository.save(b);
A a = new A();
a.setId(1);
a.setName("a");
a.setBs(Collections.singletonList(b));
aRepository.save(a);
a.setName("new");
service.save(a); //watch sevice implementations below
}
Version1:
#Transactional
public void save(A a) {
aRepository.save(a);
}
Hibernate logs are the following:
Hibernate:
update
a
set
name=?
where
id=?
Hibernate:
delete
from
a_b
where
a_id=?
Hibernate:
insert
into
a_b
(a_id, b_id)
values
(?, ?)
delete+bulk insert present (despite the fact, that B-s where not in fact changed)
Version2:
#Transactional
public void save(A a) {
Optional<A> existing = aRepository.findById(a.getId());
if (existing.isPresent()) {
a.setBs(existing.get().getBs());
}
aRepository.save(a);
}
Logs:
update
a
set
name=?
where
id=?
Here b-collection was forcibly re-attached, so hibernate understands, that it's not needed to be cascaded.

Axon - State Stored Aggregates exception in test

Environment setup : Axon 4.4, H2Database( we are doing component testing as part of the CI)
Code looks something like this.
#Aggregate(repository = "ARepository")
#Entity
#DynamicUpdate
#Table(name = "A")
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#Log4j2
Class A implements Serializable {
#CommandHandler
public void handle(final Command1 c1) {
apply(EventBuilder.buildEvent(c1));
}
#EventSourcingHandler
public void on(final Event1 e1) {
//some updates to the modela
apply(new Event2());
}
#Id
#AggregateIdentifier
#EntityId
#Column(name = "id", length = 40, nullable = false)
private String id;
#OneToMany(
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true,
targetEntity = B.class,
mappedBy = "id")
#AggregateMember(eventForwardingMode = ForwardMatchingInstances.class)
#JsonIgnoreProperties("id")
private List<C> transactions = new ArrayList<>();
}
#Entity
#Table(name = "B")
#DynamicUpdate
#Getter
#Setter
#NoArgsConstructor
#EqualsAndHashCode(onlyExplicitlyIncluded = true, callSuper = false)
#Log4j2
Class B implements Serializable {
#Id
#EntityId
#Column(name = "id", nullable = false)
#AggregateIdentifier
private String id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({#JoinColumn(name = "id", referencedColumnName = "id")})
#JsonIgnoreProperties("transactions")
private A a;
#EventSourcingHandler
public void on(final Event2 e2) {
//some updates to the model
}
}
I'm using a state store aggregate but I keep getting the error randomly during Spring Test with embedded H2. The same issue does not occur with a PGSQL DB in non embedded mode but than we are not capable of runnign it in the pipeline.
Error : "java.lang.IllegalStateException: The aggregate identifier has not been set. It must be set at the latest when applying the creation event"
I stepped through AnnotatedAggregate
protected <P> EventMessage<P> createMessage(P payload, MetaData metaData) {
if (lastKnownSequence != null) {
String type = inspector.declaredType(rootType())
.orElse(rootType().getSimpleName());
long seq = lastKnownSequence + 1;
String id = identifierAsString();
if (id == null) {
Assert.state(seq == 0,
() -> "The aggregate identifier has not been set. It must be set at the latest when applying the creation event");
return new LazyIdentifierDomainEventMessage<>(type, seq, payload, metaData);
}
return new GenericDomainEventMessage<>(type, identifierAsString(), seq, payload, metaData);
}
return new GenericEventMessage<>(payload, metaData);
}
The sequence for this gets set to 2 and hence it throws the exception instead of lazily initializing the aggregate
Whats the fix for this? Am i missing some configuration or needs a fix in Axon code?
I believe the exception you are getting is the pointer to what you are missing #Rohitdev. When an aggregate is being created in Axon, it at the very least assume you will set the aggregate identifier. Thus, that you will fill in the #AggregateIdentifier annotated field present in your Aggregate.
This is a mandatory validation as without an Aggregate Identifier, you are essentially missing the external reference towards the Aggregate. Due to this, you would simply to be able to dispatch following commands to this Aggregate, as there is no means to route them.
From the code snippets you've shared, there is nothing which indicates that the #AggregateIdentifier annotated String id fields in Aggregate A or B are ever set. Not doing this in combination with using Axon's test fixtures will lead you the the exception you are getting.
When using a state-stored aggregate, know that you will change the state of the aggregate inside the command handler. This means that next to invoke in the AggregateLifecycle#apply(Object) method in your command handler, you will set the id to the desired aggregate identifier.
There are two main other pointers to share based on the question.
There is no command handler inside your aggregate which creates the aggregate itself. You should either have an #CommandHandler annotated constructor in your aggregates, or use the #CreationPolicy annotation to define a regular method as the creation point of the aggregate (as mentioned here in the reference guide).
Lastly, your sample still uses #EventSourcingHandler annotated functions, which should be used when you have an Event Sourced Aggregate. It sounds like you have made a conscious decision against Event Sourcing, hence I wouldn't use those annotations either in your model. Right now it will likely only confuse developers that a mix of state-stored and event sourced aggregate logic is being used.
Finally after debugging we found out that in class B we were not setting the id for update event
#EventSourcingHandler
public void on(final Event2 e2) {
this.id=e2.getId();
}
Once we did that the issue went away.

Spring Data JPA Repository with Hibernate - persist (sql insert) parent entity but only update nested child entities

I'm developing a Spring Boot app with Spring data, JPA, Hibernate combination. Below is the scenario I'm struggling with where the expected behavior is to update only some child entities while the parent entity is being inserted as new.
Entity classes
#Entity
public class A {
#Id
private long id;
#ManyToOne
#JoinColumn (name = "B_ID")
#Cascade ( { CascadeType.ALL } )
private B b;
}
#Entity
#DynamicUpdate
public class B {
#Id
private long id;
#OneToMany (mappedBy = "b")
#Cascade ( { CascadeType.ALL } )
private Set<A> as;
#ManyToOne
#JoinColumn (name = "C_ID")
#Cascade ( { CascadeType.ALL } )
private C c;
}
#Entity
#DynamicUpdate
public class C {
#Id
private long id;
#OneToMany (mappedBy = "c")
#Cascade ( { CascadeType.ALL } )
private Set<B> bs;
#ManyToOne
#JoinColumn (name = "D_ID")
#Cascade ( { CascadeType.ALL } )
private D d;
#ManyToOne
#JoinColumn (name = "E_ID")
#Cascade ( { CascadeType.ALL } )
private E e;
}
#Entity
#DynamicUpdate
public class D {
#Id
private long id;
#OneToMany (mappedBy = "d")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
#Entity
#DynamicUpdate
public class E {
#Id
private long id;
#OneToMany (mappedBy = "e")
#Cascade ( { CascadeType.ALL } )
private Set<C> cs;
}
Below are the steps I'm performing in the app:
Pull B from repo; if it exists, update some of its field values [Or] create new B with field values.
Create new instance of A and set B into A.
Persist A (by invoking JpaCrudRepository.save(A)).
Success part:
Everything works fine when B doesn't exist in repo already. Which means:
New instances of B (and C,D,E) are created,
This newly created B is set into A,
And A is persisted to repo successfully (a new row is inserted in all corresponding tables in DB/repo).
Failure part:
Now, when B already exists in repo, the existing B is pulled properly,
Some fields of B are updated while C, D, E are left untouched,
And this updated B is set into A.
But when trying to persist A now a Unique constraint violation is thrown on D and E.
All the entities are marked with following:
• ID as auto-generated column (used as PK implicitly),
• Mapping between entities using the ID column,
• CascadeType ALL wherever mappings like OneToMany, ManyToOne are applied,
• Dynamic update annotation.
So far what I could gather from around the web on this subject:
The JPA Repository doesn't have merge() or update() operations available explicitly, and the save() is supposed to cover those. And this save() operation works by calling merge() if the entity instance exists already Or by calling persist() if it is new. And digging further, the entity's ID column is being used to determine its existence in the repo. If this ID is null, entity is determined as new, and as existing if ID is not null.
So in my failure case above, as the existing B entity instance is being pulled from the repo, it already has a non-null ID.
So I'm not currently clear on what exactly I'm missing here.
I tried finding any matching solution online but couldn't arrive at one yet.
Can someone please help me identify what is wrong with my approach?
Found the issue and solution. As A and B were being created as new, subsequently C,D,E were all created as new which caused the violation. So to solve this I had to pull each existing entity of C,D,and E from the DB, and set them respectively/hierarchically inside the newly created B entity, before attempting to persist.
Basically, don't create new instance for an entity if it is nested within a parent entity which is about to be persisted.. and the goal is to update when a similar record already exists.

Spring JPA OneToOne out of a OneToMany with only the fist entry

my problem can be broken down to this little example:
I have a entity class A and a entity class B. A has a List of B objects. Now there is always only one B relevant. So I do not want to load all B's of an A, only to access this one B (last inserted B inside a A).
The question: Can I manipulate an entity without an service, so that there is a #Transient variable, that is always the newest B? And also without saving the newest B separately in A. Is there a way to achieve this?
class B{
#Id
#GeneratedValue
private Long id;
#Column(nullable=false)
private String name;
#Column(nullable=false)
private Date created = new Date();
}
class A{
#Id
#GeneratedValue
private Long id;
#OneToMany
#OrderBy("created ASC")
private List<B> b;
#Transient
private B newestB; // Here should be only the newest B
}
Yes. Forget storing the newest B as a variable and instead simply add a getter for it:
#Transient
public B getNewestB() {
return b.get(b.size() -1);
}
This will solve your problem under the assumption that b is set to FetchType.EAGER. Fetching using b's getter and FetchType.LAZY may not be so straight forward as Spring may rely on an AOP proxy call to trigger the lazy load (you'd need to experiment).
However, I'd discourage both these approaches. You're effectively trying to fit business logic into your Entity. Why not keep your entity clean and perform this query using B's repository?
E.g.
public interface BRepository extends CrudRepository<B, Long> {
#Query(...) //query to get newest B for specified A
B getNewest(A a)
}

Resources