data version dosen't increase when we delete or add an child entity in spring data? - spring

I'm using #version annotation in spring data so I have a parent entity, and it has list of child entity. when I delete an element from child list the parent version doesn't increase. can anyone clarify for me this #version alternative,
why the versing in this case doesn't increase, is it a good way to manage versioning or should I use trasaction "lock".
in the documentation i read that the version update only on updating a row
in the databse but in my case i put version on parent entity and i want
note: i searched a lot in the internet but i didnt find a clear solution, can any one help me.

I assume you are using Hibernate. Lets say that the "UnderlyingPerTradingAccount" table has a column called "trading_account_id", which is a foreign key to the TradingAccount table. In order to achieve the behavior you described, you need to change the mapping. Can you try this:
public class TradingAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="trading_account_id", referencedColumnName = "trading_account_id", insertable = false, updatable = false)
private List<UnderlyingPerTradingAccount> underlyingPerTradingAccounts;
#Version
private Long version;
}
and
public class UnderlyingPerTradingAccount {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name="trading_account_id", nullable = false)
private TradingAccount tradingAccount;
private Boolean enableBuy;
private Boolean enableSell;
}
This should mark the parent entity as "dirty" when the child entity is updated and trigger the version increment.
However, I would think of some other method to track "version" changes of the parent entity as this would just cause an additional overhead and update statements to the parent.

Related

Optimal way of checking if user already upvoted/downvoted a comment on a post - Spring JPA

Post entity:
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToMany(mappedBy = "post")
private List<PostComment> postComments;
...
}
PostComment entity:
public class PostComment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_id")
private Post post;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentUpvote> postCommentUpvotes;
#OneToMany(mappedBy = "postComment")
private Set<PostCommentDownvote> postCommentDownvotes;
...
}
PostCommentUpvote entity (PostCommentUpvote and PostCommentDownvote have the exact same fields - these entities act like counters)
public class PostCommentUpvote {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#ManyToOne
#JoinColumn(name = "post_comment_id")
private PostComment postComment;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
...
}
All relations are bi-directional as you can see from the annotations.
The goal: When a user (authenticated) upvotes/downvotes a PostComment I want to do the following:
Check if user already upvoted/downvoted the PostComment.
For this I have Post id (even though this is not needed) and PostComment id and both are indexed.
There are three possible 'states' when User up/downvotes the comment:
User hasn't yet up/downvoted that comment, so it is either new upvote or new downvote
User has already upvoted and if he upvotes again, it will remove the upvote (same with downvote)
User has already upvoted and if he downvotes, upvote is removed and new downvote is added (and vice-versa)
What would be the most optimal way of doing this? Get the PostComment by its id and then loop through the List of PostCommentUpvote/PostCommentDownvote and check the User on every iteration? Or perform a tactical SQL request, which must be faster than looping in Java? If so, what would this SQL query look like? Or any other approach to make this performant. I am open to any suggestion.
Thanks
Assuming you have the post comment id and user id, the following JPA query (or close to it) will return true if the user has upvoted on the post comment and false otherwise:
select case when count(postCommentUpvote) > 0 then 'true' else 'false'
from PostCommentUpvote postCommentUpvote
join postCommentUpvote.postComment postCommnent
where postComment.id = :postCommentId
and user.id = :userId
You would then have to perform the same query using the PostCommentDownVote entity. An alternative would be to remove the up and down vote entities, simply create a PostCommentVote entity which has a boolean attribute that indicates up or down, and helper methods isUpvote() and isDownVote() that would interpret the boolean for you. You could get everything you need with a single query that returns a PostCommentVote if the user has up or down voted and null otherwise.
You did not indicate what you want to do if the user has already commented on the post; ignore the request or update the PostComment. Either way the most optimal way of doing this would be not checking at all. Create a unique index on (user_id, post_comment_id) or drop the the id column and make a composite PK of those columns. Then just insert without checking. Use the On Conflict to either ignore or update the request. You may also want to add an Up/Down vote indicator column.

Hibernate (SpringBoot JPA) invert save sequence

I'm using SpringBoot 2.2.6 with JPA and I'm run into following problem:
#Transactional
public void batch() {
....
....
repository.save(data) // this is an update
....
....
repository.save(data) // this is a normal save
}
the Hibernate logging says to me that the save is executed before the update and this generate a constraint violation error on my db.
Do you have some idea why happend something like this?
Thanks
UPDATE
The Entity is something like this, clearly there are other Entity nested but the logic is similar
#Id
#Column(name="id")
#GeneratedValue(generator = "domande_dom_stati_domanda_id_seq", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "domande_dom_stati_domanda_id_seq", sequenceName = "domande_dom_stati_domanda_id_seq",allocationSize=1)
private Integer id;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne
#JoinColumn(name="id_dom_stato_domanda", nullable=false)
private DomStatoDomanda domandaStatoDomanda;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne
#JoinColumn(name="id_domanda", nullable=false)
private Domanda domande;
#Temporal(TemporalType.DATE)
#Column(name="data_validita")
private Date dataValidita;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="data_registrazione")
private Date dataRegistrazione;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="data_registrazione_fine")
private Date dataRegistrazioneFine;
#Column(length=50)
private String utente;
#Column(length=250)
private String note;
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#ManyToOne
#JoinColumn(name="id_ruolo", nullable=false)
private Ruolo ruolo;
JPA/Hibernate queues the operations in its session whenever possible, does not call the database instantly and then just before the transaction is completing, order those operations based on type and execute them. This is called Transactional write-behind in hibernate. As you can see, even though you called the insert last, hibernate will order it as first if it was queued.
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
You can tell hibernate to flush it rather than queue it. So replace repository.save(data) with repository.saveAndFlush(data) so it executes in the order you wanted
Reference
Executions Order

one-way one-to-many throws Hibernate Cannot add or update a child row: a foreign key constraint fails

I have an application that teaches the user how to play various card games. The data model that gets persisted consists of a TrainingSession with a uni-directional one-to-many relationship with the Hands.
[EDIT] To clarify, a Hand has no existence outside the context of a TrainingSession (i.e they are created/destroyed when the TrainingSession is). Following the principals of Data Driven Design, the TrainingSession is treated as an aggregate root and therefore a single spring-data CrudRepository is used (i.e., no repository is created for Hand)
When I try to save a TrainingSession using a CrudRepository, I get: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (blackjack.hand, CONSTRAINT FKrpuxac6b80xc7rc98vt1euc3n FOREIGN KEY (id) REFERENCES training_session (tsid))
My problem is the 'save(trainingSession)' operation via the CrudRepository instance. What I don't understand is why the error message states that FOREIGN KEY (id) REFERENCES training_session (tsid)). That seems to be the cause of the problem but I cant figure out why this is the case or how to fix it. The relationship is uni-directional and nothing in the Hand class refers to the TrainingSession.
The code, minus all the getters and setters, is:
#Entity
public class TrainingSession {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer tsid;
private String strategy;
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="id")
private List<Hand> hands;
private int userId;
protected TrainingSession() {
}
public TrainingSession(int userId, Strategy strategy, List<Hand> hands) {
this.strategy = strategy.getClass().getSimpleName();
this.hands = hands;
this.userId = userId;
}
while Hand is
#Entity // This tells Hibernate to make a table out of this class
public class Hand {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private int p1;
private String p1s;
private int p2;
private String p2s;
private int d1;
private String d1s;
private int trials;
private int score;
public Hand() {
}
You need to save your TrainingSession and Hand objects first before saving the adding the hand objects to TrainingSession.
TrainingSession ts1 = new TrainingSession();
trainingSessionManager.save(ts1);
Hand hand1 = new Hand();
handManager.save(hand1);
Hand hand2 = new Hand();
handManager.save(hand2);
ts1.gethands().add(hand1);
ts1.gethands().add(hand2)
trainingSessionManager.save(ts1);
If you check your database you will find 3 tables TrainingSession, Hand and TrainingSession_Hand, The TrainingSession_Hand table references to both TrainingSession and Hand both. Therefore you need to save TrainingSession and hand before saving the relationship.
Found the problem. I was assuming that when spring-data set up the DB tables, it was able to figure out and set up the uni-directional 1-to-many relationship. Apparently that isn't the case. When I configure the relationship as bi-directional everything seems to work.
To fix things I:
removed from TrainingSession the #joincolumn annotation for hands
in Hands I added a TrainingSession field with a #ManyToOne annotation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "tsid", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private TrainingSession tsession;
I also added in the Hand class the getter/setter for tsession
I can now do a save of the entire aggregate construct using only a TrainingSessionRepository.

No foreign key exist in db created

I have created many entities in jpa.
When i checked in the database, i don't see any foreign key.
#Entity
public class Lodger implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long lodgerId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "lodger")
private List<AccountOperation> accountOperationList;
...
}
#Entity
public class AccountOperation {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long accountOperationId;
#ManyToOne
#JoinColumn(name = "lodger_id")
private Lodger lodger;
...
}
In this example i was thinking to get a foreign key in the account operation class.
table automaticaly created
http://www.wepaste.com/table_example/
Why?
Hibernate does not automatically generate foregein keys when generating dlls. I would recommend to turn of the generate-dll option as it may create inconsistent databases as the complexity increases. also check out either spring boot default database administration options:
Flyway
Liquibase
The main difference between the two relies in the fact that while both may be administrared with SQL, Liquibase offers a more database agnostic formats such as XML, and YML for the creation of your database

Hibernate: need update parent entity without pulling all its child-cascade

I faced the problem when I need to partially udate data in BD.
What I have:
I have three linked entities:
Profile --(1-m)--> Person --(1-1)--> Address
Where Person -> Address is lazy relationship. It was achieved via optional=false option (that allow hibernate to use proxy).
What the problem:
I need to update Profile in such way, that I needn't pull all Addresses that linked with this profile.
When I update Profile (don't work):
profile.setPersons(persons);
session.saveOrUpdate(profile);
throws: org.springframework.dao.DataIntegrityViolationException: not null property references a null or transient value
It happens because Person->Address relationship has optional=false option
I need to do:
//for each person
Address address = requestAddressFromDB();
person.setAddress(address);
persons.add(person)
//and only then
profile.setPersons(persons);
session.saveOrUpdate(profile);
profile.setPerson(person)
But I don't want to pull all address each time I update Profile name.
What is the question:
How can I avoid obligatory Person->(not null)Address constraint to save my profile without pulling all addresses?
ADDITION:
#Entity
public class Person{
#Id
#SequenceGenerator(name = "person_sequence", sequenceName = "sq_person")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "person_sequence")
#Column(name = "id")
private long personID;
#OneToOne(mappedBy="person", cascade=CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
private Address address;
//.. getters, setters
}
#Entity
public class Address {
#Id
#Column(name="id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="person"))
private long personID;
#PrimaryKeyJoinColumn
#OneToOne
private FileInfo person;
}
Modify the cascade element on the #OneToOne annotation so that the PERSIST operation is not cascaded. This may require you to manually persist updates to Address in certain areas of your code. If the cascade is not really used however no change is needed.
#OneToOne(mappedBy="person", cascade={CascadeType.MERGE, CascadeType.REMOVE, CascadeType.REFRESH}, optional = false, fetch = FetchType.LAZY)
private Adress address; //Do you know that Address is missing a 'd'?

Resources