Why my trigger is not working with Hibernate? - java

I have a Java spring boot project and I use Hibernate in this project. I have a void deleteAll(List<Long> idList) method and its query is #Query(value = "DELETE c FROM Course c WHERE c.id in :idList", nativeQuery = true). I want to use a trigger after every delete operation. I created a trigger in mysql and it works when I try in mysql, but it doesn't work when I call void deleteAll(List<Long> idList) method. How can use my trigger with hibernate?
My trigger defination:
DELIMITER $$
CREATE TRIGGER after_delete_course AFTER DELETE ON course FOR EACH ROW
BEGIN
DELETE tc1 FROM table1_course tc1 WHERE tc1.course_id = OLD.id;
DELETE tc2 FROM table2_course tc2 WHERE tc2.course_id = OLD.id;
DELETE tc3 FROM table3_course tc3 WHERE tc3.course_id = OLD.id;
END$$
DELIMITER ;
SQL generated by hibernate when deleteAll is called :
Hibernate:
DELETE c
FROM
Course c
WHERE
c.id in (
?
)

How do you know it doesn't work? You won't see the delete queries of your trigger being logged in your application, this is happening only on the DBMS side.
If you have an EntityManager that contains the deleted entity or the associations deleted by the trigger, you will have to detach them from the EntityManager to get a consistent view again, as Hibernate does not know what effects DML statements will have on the managed entities. Try using EntityManager.clear after executing this DML statement.

An alternative solution is to use the entity lifecycle annotations provided by JPA.
In this case, you are performing a delete operation. So, you can use the #PreRemove annotation.
You can check some examples on Baeldung

Related

Spring data: DeleteAll and Insert in same transaction

I am trying to achieve below native query logic using hibernate (Spring JPA). But save(Iterable) throws exception and rollback the entire transaction if one of the record fails to persist. Is there any way to do to catch the record error and proceed with insertion on other records.
eg:-
Native Sql Query
set autocommit=false
delete from EMPLOYEE;
insert into EMPLOYEE(id, name, sal) values(2, ‘Roy’, ‘rt’); —-stmt1
insert into EMPLOYEE(id, name, sal) values(2, ‘Joe’, 3000);
commit;
Note: sal column is numeric in EMPLOYEE table. Execution continues eventhough stmt1 failed.
Hibernate (CrudRepository)
#Autowired
CrudRepository employeeRepository;
#Transactional
public void saveToDB(List dataList) {
employeeRepository.deleteAll();
employeeRepository.save(dataList);
}
Anyone else stumbling upon this problem. I managed to get it work with writing own deleteAll Method in RepositoryInterface and setting annotation like this:
#Modifying(flushAutomatically = true)
#Query("delete from MyEntity")
void deleteAllCustom()
Use flush between deleteall and save.
Heey Warriors,finally this works for me.
#Modifying(flushAutomatically = true)
#Transactional
void deleteByProjet(Projet projet);
Good Luck ;)
I just changed from deleteAll to deleteAllInBatch (JpaRepository interface) and it worked.
deleteAllInBatch results in Hibernate registering a prepared statement delete from yourtable right when deleteAllInBatch is called.
With deleteAll, Hibernate
select all your data into memory, as entities
for each entity, it calls EntityManager.delete
each delete generates a EntityDeleteAction which will be called when transaction ends and session flushes. But for whatever reason, the actions for the inserts end up being called before the deletes (maybe the order is unpredictable, I'm not sure).

What is the best way to bulk delete rows in JPA while also cascading the delete to child records

I'm trying to do bulk delete in my entities, and the best solution will be to go with CriteriaDelete. But CriteriaDelete does not cascade (at least not for me).
So, it seems like the only solution which I have is to do select first and delete each element separately. Which does not seems wrong to me.
Is anyone have a better idea of how to do bulk delete? Is it actually a better way?
If it helps I'm using EclipseLink 2.5.2.
The options are:
use the cascade.Remove setting on the mapping, loading entities
and calling em.remove on each
Use bulk delete on the main entity and have the "ON DELETE
CASCADE" database option set so that the database will cascade the
delete for you. EclipseLink has a #CascadeOnDelete annotation that
lets it know the "ON DELETE CASCADE" is set on a relationship, or to
create it if using JPA for DDL generation: http://eclipse.org/eclipselink/documentation/2.5/jpa/extensions/a_cascadeondelete.htm
Use multiple bulk deletes to remove children that might be referenced before removing the main entity. For example: "Delete FROM Child c where c.parent = (select p from Parent P where [delete-conditions])" and "Delete FROM Parent p where [delete-conditions]" See section 10.2.4 of http://docs.oracle.com/middleware/1212/toplink/OTLCG/queries.htm#OTLCG94370 for details.
How does the JPA CriteriaDelete work
A JPA CriteriaDelete statement generates a JPQL bulk delete statement, that's parsed to an SQL bulk delete statement.
So, the following JPA CriteriaDelete statement:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaDelete<PostComment> delete = builder.createCriteriaDelete(PostComment.class);
Root<T> root = delete.from(PostComment.class);
int daysValidityThreshold = 3;
delete.where(
builder.and(
builder.equal(
root.get("status"),
PostStatus.SPAM
),
builder.lessThanOrEqualTo(
root.get("updatedOn"),
Timestamp.valueOf(
LocalDateTime
.now()
.minusDays(daysValidityThreshold)
)
)
)
);
int deleteCount = entityManager.createQuery(delete).executeUpdate();
generates this SQL delete query:
DELETE FROM
post_comment
WHERE
status = 2 AND
updated_on <= '2020-08-06 10:50:43.115'
So, there is no entity-level cascade since the delete is done using the SQL statement, not via the EntityManager.
Bulk Delete Cascading
To enable cascading when executing bulk delete, you need to use DDL-level cascade when declaring the FK constraints.
ALTER TABLE post_comment
ADD CONSTRAINT FK_POST_COMMENT_POST_ID
FOREIGN KEY (post_id) REFERENCES post
ON DELETE CASCADE
Now, when executing the following bulk delete statement:
DELETE FROM
post
WHERE
status = 2 AND
updated_on <= '2020-08-02 10:50:43.109'
The DB will delete the post_comment records referencing the post rows that got deleted.
The best way to execute DDL is via an automatic schema migration tool, like Flyway, so the Foreign Key definition should reside in a migration script.
If you are generating the migration scripts using the HBM2DLL tool, then, in the PostComment class, you can use the following mapping to generate the aforementioned DDL statement:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(foreignKey = #ForeignKey(name = "FK_POST_COMMENT_POST_ID"))
#OnDelete(action = OnDeleteAction.CASCADE)
private Post post;
If you really care about the time it takes to perform this bulk delete, I suggest you use JPQL to delete your entities. When you issue a DELETE JPQL query, it will directly issue a delete on those entities without retrieving them in the first place.
int deletedCount = entityManager.createQuery("DELETE FROM Country").executeUpdate();
You can even do conditional deletes based on some parameters on those entities using Query API like below
Query query = entityManager.createQuery("DELETE FROM Country c
WHERE c.population < :p");
int deletedCount = query.setParameter(p, 100000).executeUpdate();
executeUpdate will return the number of deleted rows once the operation is complete.
If you've proper cascading type in place in your entities like CascadeType.ALL (or) CascadeType.REMOVE, then the above query will do the trick for you.
#Entity
class Employee {
#OneToOne(cascade=CascadeType.REMOVE)
private Address address;
}
For more details, have a look at this and this.
JPQL BULK DELETE (whether using string-based JPQL or using Criteria JPQL) is not intended to cascade (i.e follow the cascade type settings for fields). If you want cascading then you either set up the datastore to use real FOREIGN KEYs, or you pull back the objects to delete and call EntityManager.remove().

Delete data from JPA without using SQL

im familiar with this following way to delete the data (just the data ,not the entity itself)
from the entity
entityManager.getTransaction().begin();
entityManager.createQuery("DELETE FROM " + className)
.executeUpdate();
entityManager.getTransaction().commit();
there is another way to do that like to provide the entityname and then reomve all the data .
You're not using SQL in your code but JPQL, JPA Query Language.
There is no other way to delete all data at once, except by loading all of them and deleting them one by one. It's not even possible with criteria queries since they don't support delete operation yet.
Well.. in this case both NativeSQLQuery and JPQL resolve to the same thing. What you did is JPQL way. The following you could write a nativeSQLQuery
EntityManager em = ...;
Query query = em.createNativeQuery ("SELECT * FROM EMP", Employee.class);

Delete Derby entries

I use JPA to add data to Derby DB with eclipse link and when there is duplicated ID.
I got error, there is a way in run-time (with code) to simple delete all the table entries.
I don't need the old entry just new entries in every time that I invoke the program.
I tried with
factory = Persistence.createEntityManagerFactory(PERSISTENCE_UNIT_NAME);
EntityManager entityManager = factory.createEntityManager();
entityManager.getTransaction().begin();
Query query = entityManager.createQuery("SELECT p FROM Job p ");
List resultList = query.getResultList();
for (Object result : resultList) {
entityManager.remove(result);
}
entityManager.getTransaction().commit();
entityManager.close();
but here I use query before and I don't want to do that I want to delete all the entries and I don't care what is there before.
You can use the SQL DELETE query at runtime everytime the application starts. If you don't post some code, we can't help.
EDIT :
You can execute the query as follows in some earlier transaction or the same transaction before the query:
int deletedCount = entityManager.createQuery("DELETE FROM Job").executeUpdate();
You could either execute a delete query, or configure your persistence unit to recreate the tables when the persistence unit is deployed.
See,
http://www.eclipse.org/eclipselink/documentation/2.4/jpa/extensions/p_ddl_generation.htm#BABHEJJI

JPA 1.0 and Hibernate 3.4 generating FOR UPDATE NOWAIT when locking

I am currently working on a Java EJB project being deployed to Weblogic 10.3.3. We are using JPA 1.0 with Hibernate 3.4 as the implementor. We are also using the Oracle10g Dialect.
The issue we are running in to involves the generation of SQL by hibernate when attempting to lock a row for update.
We execute a query:
Query q = entityManager.createNamedQuery("findMyObject");
MyHibernateObject myObject= (MyHibernateObject ) q.getSingleResult();
And then lock that object with:
entityManager.lock(myObject, LockModeType.WRITE);
This act of locking generates the query:
SELECT myObject FROM myTable FOR UPDATE NOWAIT
What I want it to generate is:
SELECT myObject FROM myTable FOR UPDATE
Enabling other threads to query for this object without throwing the exception: org.hibernate.exception.LockAcquisitionException and to just wait their turn or let the EJB transaction timeout.
So knowing all this, can I force Hibernate to generate the SQL without the NOWAIT keyword?
I know that using Hibernate 3.6 and JPA 2.0 will allow this using a pessimistic lock but due to Weblogic only supporting JPA 1.0 our hands are tied.
Ideally I want to avoid writing our own retry and/or timeout mechanism by handling the exception when all I need is to just augment the SQL that Hibernate is generating when the EntityManager creates the lock.
Ok, we are using a workaround until we update to Weblogic 10.3.4
Here it is in case someone else stumbles upon this:
SessionImpl session = (SessionImpl)entityManager.getDelegate();
session.lock(myObject, LockMode.UPGRADE);
This of course breaks from the JPA standard, in that we are exposing hibernates implementation and using the hibernate session.
But it will generate a
SELECT myObject FOR UPDATE
instead of
SELECT myObject FOR UPDATE NOWAIT
Hope this helps someone.
Use below code to skip a locked row. This is the alternate of
select * from student where student id = n for update nowait
findStudnt method throws error if row is already locked. If there is no error call updateStudent method to update student entity else log the error for audit.
#Override
public Student findStudent(final Long studentId) {
TypedQuery<Student> query = getEntityManager().createNamedQuery("from Student s where s.studentId =:studentId", Student.class);
query.setParameter("studentId", studentId);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.setHint(JAVAX_PERSISTENCE_LOCK_TIMEOUT, ZERO_NUMBER);
return query.getSingleResult();
}
#Override
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void updateStudent(Student student) {
makePersistent(student);
getEntityManager().flush();
}

Categories

Resources