Hibernate (SpringBoot JPA) invert save sequence - spring-boot

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

Related

How to delete a #ManyToOne #NotNull reference?

Assume I have an Entity0
#Entity
public class Entity0 implements Serializable {
#Id
private Long id;
#ManyToOne
#NotNull
private Entity1 entity1;
public Entity0() {
}
public Entity0(Long id,
Entity1 entity1) {
this.id = id;
this.entity1 = entity1;
}
[getter and setter for id and entity1]
}
which references Entity1
#Entity
public class Entity1 implements Serializable {
#Id
private Long id;
#Basic
private String property0;
public Entity1() {
}
public Entity1(Long id,
String property0) {
this.id = id;
this.property0 = property0;
}
[getter and setter for id and property0]
}
The #NotNull annotation is useful to assert that Entity0.entity1 is set during persist and merge. However, it disallows to delete the reference to Entity1 (set it to null and merge the instance into the persistence context) which is necessary to delete instance of Entity1 which are referenced. I'm wondering whether there's any way to have a #ManyToOne #NotNull property in an entity at all. The following illustrates the problems which arise from the different approaches I took to make it possible to delete Entity1 instances from persistence:
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("de.richtercloud_jpa-not-nulll-many-to-one-removal_jar_1.0-SNAPSHOTPU");
EntityManager entityManager = entityManagerFactory.createEntityManager();
Entity1 entity1 = new Entity1(2l, "abc");
Entity0 entity0 = new Entity0(1l, entity1);
entityManager.getTransaction().begin();
entityManager.persist(entity1);
entityManager.persist(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entity1 = entityManager.merge(entity1);
entity0 = entityManager.merge(entity0);
//1: fails due to `ERROR 23503: DELETE on table 'ENTITY1' caused a violation of foreign key constraint 'FK5CQRG47R3H3KQG834IH36DUB' for key (2). The statement has been rolled back.`
//entityManager.remove(entity1);
//entityManager.flush();
//entityManager.merge(entity0);
//entityManager.flush();
//2:
entity0.setEntity1(null);
entityManager.remove(entity1);
entityManager.flush();
entityManager.merge(entity0);
entityManager.flush();
entityManager.getTransaction().commit();
entityManager.close();
entityManagerFactory.close();
The code contains so many flushs because I'm reproducing this for a JTA-environment where a flush can occur between the parts of a transaction.
I'm aware that I can drop the #NotNull annotation to work around the problem. My question is whether there's any solution to have both annotations and be able to delete. In case that's no possible, is it more common to set the reference temporarily to a bogus Entity1 instance or to give up on #NotNull?
Cascading might be a valid way, if not the way, however I've given up on it because I found it to be hiding issues and behaving in an unwanted fashion - not that it's not possible to master, it's just easier to handle for me personally.
If cascading is a way you could add the following to your entity1
#OneToMany(cascade=CascadeType.REMOVE)
private List<Entity0> entity0s;
This way the referenced entity0s will be deleted when entity1 is deleted.
Cascading can be dangerous and you should always think twice about it whether it is a good idea to always delete the referenced entities. Sometimes it is better to delete the references first and then remove the "parent" entity.
I'm not sure I see what the problem is, why do you think you have to set #NotNull on a #ManyToOne foreign key declaration? #NotNull is not a JPA annotation. If you want to make the database column set to not null you should use:
#ManyToOne
#JoinColumn(name = "entity1_id", nullable=false)
private Entity1 entity1;
So if you want to have nullable=false and delete them both then delete the child first then the parent.
tx.begin();
entity1 = em.find(Entity1.class, 1L);
entity0 = em.find(Entity0.class, 2L);
em.remove(entity0);
em.remove(entity1);
tx.commit();

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

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.

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.

Hibernate Sequence Conflict after using Audited annotation

I'm using hibernate in my spring boot application
my domain model is like this
#Entity
#Table(name = "skill")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Document(indexName = "skill")
#Audited
public class Skill implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
}
The increment size of sequence is 50 and is working properly
but when I add Envers Audited annotation I see this error
conflicting values for 'increment size'. Found [50] and [1]
How can I resolve this conflict?
This doesn't sound like an Envers problem but a general mapping problem.
When you add an #Audited annotation, that simply informs Envers that it should inspect that particular entity mapping during Hibernate bootstrap and create the necessary audit objects to store the entity state during each transaction.
The generated Envers objects use their own sequence generators and primary key. The user defined generation strategy, sequences, etc are all ignored in the Envers object because the associated column is meant to just be a copy/pass-thru value, nothing special.
In other words, the Envers table would have a PK defined that mirrors this POJO:
#Embeddable
public class EnversSkillId implements Serializable {
#Column(name = "REV", nullable = false, updatable = false)
private Integer rev;
#Column(name = "id", nullable = false, updatable = false)
private Long id;
}
When Envers generates the audit record, it automatically uses its internal sequence generator to get the next value and assign it to EnversSkillId#rev and copies your entity's id value directly into the EnversSkillId#id property.
So as mentioned in the comments, your problem is very unlikely related to Envers.

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

Resources