How to use jpa or hibernate to prevent the deletion of the table - spring

i have archive table that i don't want to be deleted so how to use jpa or hibernate to prevent the deletion of the table (delete method must throw an error), or when deleting actually do update (state = deleted). Is there any annotation or any technique to do so?

You can use entity callback #PreRemove and throw runtime exception.
https://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html

The #SqlDelete annotation allows you to override the default DELETE statement executed by Hibernate, so we substitute an UPDATE statement instead. Therefore, removing an entity will end up updating the deleted column to true
#SQLDelete(sql = "UPDATE tag SET deleted = true WHERE id = ?")
#MappedSuperclass
public abstract class BaseEntity {
private boolean deleted;
}
#Entity(name = "Tag")
#Table(name = "tag")
#SQLDelete(sql =
"UPDATE tag " +
"SET deleted = true " +
"WHERE id = ?")
public class Tag extends BaseEntity {
#Id
private String id;
//Getters and setters omitted for brevity
}

Related

Hibernate search does not remove old value from lucene index when the object is deleted via an #NoRepositoryBean Jpa method

I have a NoRepositoryBean Jpa interface that has one custom jpa method called deleteAllByIdIn(...) which is inherited by some concrete JpaRepositories. For some reason this custom delete method is ignored by Hibernate Search. Whenever an entity is deleted through this custom method its value is not removed from the lucene index after the delete is done. I will explain the problem some more further down this post; but first here's the code
#NoRepositoryBean
public interface NameTranslationDao<T extends NameTranslation> extends JpaRepository<T, Long> {
#Modifying
#Transactional
#Query(value = "DELETE FROM #{#entityName} c WHERE c.id IN :translationsToDelete")
public void deleteAllByIdIn(#Param("translationsToDelete") Set<Long> translationsToDelete);
}
Heres a JpaRepository subclass that extends this interface:
#Repository
#Transactional(readOnly = true)
public interface LifeStageCommonNameTranslationDao extends CommonNameTranslationDao<LifeStageCommonNameTranslation> {
}
Theres another #NoRepositoryBean interface in-between the concrete JpaRepository and the NameTranslationDao NoRepositoryBean. That one is called CommonNameTranslationDao but it doesn't override the custom method in any way, so it is unlikely the cause of the problem, nevertheless heres the code of that repository:
#NoRepositoryBean
public interface CommonNameTranslationDao<T extends NameTranslation> extends NameTranslationDao<T> {
#Deprecated
#Transactional(readOnly = true)
#Query("SELECT new DTOs.AutoCompleteSuggestion(u.parent.id, u.autoCompleteSuggestion) FROM #{#entityName} u WHERE u.autoCompleteSuggestion LIKE :searchString% AND deleted = false AND (u.language.id = :preferredLanguage OR u.language.id = :defaultLanguage)")
List<AutoCompleteSuggestion> findAllBySearchStringAndDeletedIsFalse(#Param("searchString") String searchString, #Param("preferredLanguage") Long preferredLanguage, #Param("defaultLanguage") Long defaultLanguage);
#Transactional(readOnly = true)
#Query(nativeQuery = true, value = "SELECT s.translatedName FROM #{#entityName} s WHERE s.language_id = :preferredLanguage AND s.parent_id = :parentId LIMIT 1")
public String findTranslatedNameByParentAndLanguage(#Param("preferredLanguage") Long languageId, #Param("parentId") Long parentId);
#Modifying
#Transactional
#Query(nativeQuery = true, value = "DELETE FROM #{#entityName} WHERE id = :id")
void hardDeleteById(#Param("id") Long id);
#Modifying
#Transactional
#Query(nativeQuery = true, value = "UPDATE #{#entityName} c SET c.deleted = TRUE WHERE c.id = :id")
void softDeleteById(#Param("id") Long id);
}
Also, heres the code of the LifeStageCommonNameTranslation entity class:
#Entity
#Indexed
#Table(
uniqueConstraints = {
#UniqueConstraint(name = "UC_life_cycle_type_language_id_translatedName", columnNames = {"translatedName", "parent_id", "language_id"})
},
indexes = {
#Index(name = "IDX_lifestage", columnList = "parent_id"),
#Index(name = "IDX_translator", columnList = "user_id"),
#Index(name = "IDX_species_language", columnList = "language_id, parent_id, deleted"),
#Index(name = "IDX_autoCompleteSuggestion_language", columnList = "autoCompleteSuggestion, language_id, deleted")})
public class LifeStageCommonNameTranslation extends NameTranslation<LifeStage> implements AuthorizationSubject {
#Id #DocumentId
#GenericGenerator(
name = "sequenceGeneratorLifeStageCommonNameTranslation",
strategy = "org.hibernate.id.enhanced.SequenceStyleGenerator",
parameters = {
#org.hibernate.annotations.Parameter(name = "sequence_name", value = "_lifestagecommonnametranslation_hibernate_sequence"),
#org.hibernate.annotations.Parameter(name = "optimizer", value = "pooled"),
#org.hibernate.annotations.Parameter(name = "initial_value", value = "1"),
#org.hibernate.annotations.Parameter(name = "increment_size", value = "25"),
#org.hibernate.annotations.Parameter(name = "prefer_sequence_per_entity", value = "true")
}
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "sequenceGeneratorLifeStageCommonNameTranslation"
)
#Field(analyze = Analyze.NO, store = Store.YES, name = "parentId")
private Long id;
#IndexedEmbedded(includeEmbeddedObjectId = true)
#ManyToOne(fetch = FetchType.LAZY)
private LifeStage parent;
#Field(index = NO, store = Store.YES)
private String autoCompleteSuggestion;
//Getters and setters ommitted
The problem is the following: Whenever i use the inherited deleteAllByIdIn() method on LifeStageCommonNameTranslationDao then Hibernate Search will not remove the autoCompleteSuggestion field value from the lucene index after the entity has been deleted. If however i use the standard deleteById() JpaRepository method to delete the entity then the field value is removed from the lucene index.
Both the custom and the standard delete method were called within a #Transactional annotated method and i also called the flush() jpaRepository method right afterwards. I did this because I've read that this can sometimes help to update the lucene index. But in the case of deleteAllByIdIn() calling flush() afterwards did not help at all.
I already ruled out the possiblity that the problem was caused by the spEL expression in the SQL query. I tested this by replacing #{#entityName} with a concrete entity name like LifeStageCommonTranslation and then calling the deleteAllByIdIn() delete method. But the problem still persisted. The lucene index still did not remove the autoSuggestionText field value after the delete.
I can easily solve this problem by simply using the standard jpa method deleteById() but i want to know why the custom made jpa method deleteAllByIdIn() does not cause Hibernate search to update the lucene index.
Hibernate Search detects entity change events happening in your Hibernate ORM Session/EntityManager. This excludes insert/update/delete statements that you wrote yourself in JPQL or native SQL queries.
The limitation is documented here: https://docs.jboss.org/hibernate/stable/search/reference/en-US/html_single/#limitations-changes-in-session
The workaround is documented there too:
One workaround is to reindex explicitly after you run JPQL/SQL queries, either using the MassIndexer or manually.
EDIT: And of course your workaround might be valid as well, if deleteById loads the entity in the session before deleting it (I'm not that familiar with the internals of Spring Data JPA):
I can easily solve this problem by simply using the standard jpa method deleteById() but i want to know why the custom made jpa method deleteAllByIdIn() does not cause Hibernate search to update the lucene index.

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.

Implementing soft delete with spring data and hibernate

What is the best way to implement soft delete in spring data and on delete that should also trigger hibernate events
There is an example on how to implement soft deletes in the Hibernate ORM documentation:
#SQLDelete(
sql = "UPDATE person SET valid = false WHERE id = ? "
)
public static class Person {
#Id
#GeneratedValue
private Long id;
private String name;
...
}

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.

Hibernate performs update and delete on custom JPQL

I am trying to update the fields of an entity that has a ManyToMany relationship, however, as I just want to update the table fields and ignore the ManyToMany relationship. The relationship is between the Company and UserSystem entities, it was defined in the relationship that company_user_system is the union table of the entities. The problem is that when executing my update in Company, always before my update, Hibernate makes an update in company and the relationship delete in user_system_company and this erases the relationship between Company and UserSystem and I don't understand why these two queries occur if I don't execut.
These are the queries, the first and second are not executed by my code:
Hibernate: update company set active=?, email=?, identification_code=?, trading_name=?, update_on=? where id=?
Hibernate: delete from company_user_system where company_id=?
Hibernate: update company set email=?, phone=?, corporate_name=?, trading_name=?, identification_code=?, email=?, phone2=? where id=?
Hibernate: select company0_.id as id1_0_, company0_.active as active2_0_, company0_.corporate_name as corporat3_0_, company0_.created_on as created_4_0_, company0_.email as email5_0_, company0_.email2 as email6_0_, company0_.identification_code as identifi7_0_, company0_.phone as phone8_0_, company0_.phone2 as phone9_0_, company0_.trading_name as trading10_0_, company0_.update_on as update_11_0_ from company company0_ where company0_.id=?
Following is the update implementation code:
public class CompanyRepositoryImpl implements CompanyRepositoryCustom {
#PersistenceContext
private EntityManager entityManager;
public Company updateCompanyFields(Company company) {
// ... fieldSql implementation omitted
String sql = "UPDATE Company SET "+ fieldsSql +" WHERE id = :id ";
Query query = entityManager.createQuery(sql);
// set the values for the fields
for (Method method : getMethods) {
query.setParameter(lowercaseFirstCharInString(cutGetInMethods(method.getName())), method.invoke(company));
}
// set id
query.setParameter("id", company.getId());
// execute update and search the database to return the updated object
if (query.executeUpdate() == 1) {
query = entityManager.createQuery("SELECT c FROM Company c WHERE c.id = :id");
query.setParameter("id", company.getId());
Company getCompany = (Company) query.getResultList().get(0);
return getCompany;
}
return null;
}
// ... Other methods omitted
}
Repository Code:
#Repository
public interface CompanyRepository extends JpaRepository<Company, Long>, JpaSpecificationExecutor<Company> , CompanyRepositoryCustom {
#Modifying
Company updateCompanyFields(Company company);
}
Company entity code, I just added the attributes that I think may contain something useful to try to solve the problem:
#Entity
#DynamicUpdate
#Table(name = "company")
public class Company implements Serializable {
#CreationTimestamp
#Column(name = "created_on", nullable = false)
private Instant createdOn;
#UpdateTimestamp
#Column(name = "update_on")
private Instant updateOn;
#ManyToMany
#JoinTable(
name = "company_user_system",
joinColumns = #JoinColumn(
name = "company_id", referencedColumnName = "id"
),
inverseJoinColumns = #JoinColumn(
name = "user_system_id", referencedColumnName = "id"
)
)
private Set<UserSystem> userSystems = new HashSet<>();
}
The UserSystem class defines the relationship as follows:
#ManyToMany(mappedBy = "userSystems")
private Set<Company> companies = new HashSet<>();
What may be causing this update and delete before my update?
This happens because you changed somewhere the value(s) of your relationship. EntityManager tracks such changes and marks the entity as dirty. When you execute a custom SQL query Hibernate will perform all the pending queries (submit any dirty entities).
You may prevent it by calling EntityManager.clear().

Resources