Spring Boot audit #Query - spring-boot

It's possible to user Spring boot Audit with JPA #Query annotation?
For example i have the next Repository:
#Repository
public interface ItemRepository extends JpaRepository<Item, Long> {
#Modifying
#Query(value = "update Item i set statusId = :statusId WHERE i.id = :id")
void updateById(#Param("id") Long id, #Param("statusId") Long statusId);
}
But when i use this into my service it's not updating update_date column in database:
#Autowired
private ItemRepository itemRepository;
#Transactional
public void updateItemStatus(Long itemId, Long statusId) {
//Case 1: This is working, update_date column is updated
Item item = itemRepository.findById(itemId).orElseThrow(() -> new ResourceNotFoundException("Item", "id", itemId));
item.setStatusId(statusId);
itemRepository.save(item);
//Case 2: This is not working
itemRepository.updateById(itemId, statusId);
}
So, can i use Audit with #Query annotation ?
Thank you.

Auditing is based on the JPA Lifecycle events. Only the methods directly manipulating instances (persist, merge and remove) trigger such events.
The execution of queries, modifying or otherwise, does not trigger any events and therefore, won't cause auditing to happen.

You have syntax issue
Either use
#Query(value = "update Item i set i.statusId = :statusId WHERE i.id = :id")
or
#Query(value = "update Item set statusId = :statusId WHERE id = :id")

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.

Custom update in CrudRepository

is that possible to returns updated entity by custom update method instead of numbers of affected rows? How can I achieve this?
I would like to have sth like this:
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Data updateDataByType();
}
instead of this
public interface DataRepository extends CrudRepository<Data, Long> {
#Modifying
#Query(value="UPDATE data SET max_version = max_version + 1 WHERE id = 'A'", nativeQuery=true)
Integer updateDataByType();
}
You cannot do this with the #Modifying annotation. because these methods can only be void and int. Otherwise you will get the error Modifying queries can only use void or int / Integer as return type.
But can be you can implement custom repository Implementation and return your updated entity after done with the query execution.
Reference: Custom Implementations for Spring Data Repositories

Spring Data find by inner relation

My question is about the way Spring data is generating the query .
I have two entities : Message , Sender
#Entity
public class Message extends BaseEntity {
#ManyToOne
protected Account sender;
}
I have a call to
messageDao.findBySenderId(Long id)
The result is query all columns from the two two table with a left outer join between the two tables , but my expectation was simply to just select from message table where sender_id = the passed value.
So is there a way to force selecting only the first message entity and not to join with the other one? I want simple condition in the where clause
by using findBy not custom #Query
You will need a repository like (untested) :
#Repository
public interface MessageRepository extends JpaRepository<Message, Long> {
Message findFirstBySenderId(Long id);
}
See repositories.query-methods
I think Hibernate is doing a LEFT JOIN because your #ManyToOne is optional = true (default).
Try:
#ManyTone(optional = false)
And you will see Hibernate doing a query without a JOIN, as you expected.
You can use fetch type LAZY for the associations types:
#ManyToOne(optional = false, fetch = FetchType.LAZY)

Spring Data: "delete by" is supported?

I am using Spring JPA for database access. I am able to find examples such as findByName and countByName, for which I dont have to write any method implementation. I am hoping to find examples for delete a group of records based on some condition.
Does Spring JPA support deleteByName-like delete? Any pointer is appreciated.
Regards and thanks.
Deprecated answer (Spring Data JPA <=1.6.x):
#Modifying annotation to the rescue. You will need to provide your custom SQL behaviour though.
public interface UserRepository extends JpaRepository<User, Long> {
#Modifying
#Query("delete from User u where u.firstName = ?1")
void deleteUsersByFirstName(String firstName);
}
Update:
In modern versions of Spring Data JPA (>=1.7.x) query derivation for delete, remove and count operations is accessible.
public interface UserRepository extends CrudRepository<User, Long> {
Long countByFirstName(String firstName);
Long deleteByFirstName(String firstName);
List<User> removeByFirstName(String firstName);
}
Derivation of delete queries using given method name is supported starting with version 1.6.0.RC1 of Spring Data JPA. The keywords remove and delete are supported. As return value one can choose between the number or a list of removed entities.
Long removeByLastname(String lastname);
List<User> deleteByLastname(String lastname);
2 ways:-
1st one Custom Query
#Modifying
#Query("delete from User where firstName = :firstName")
void deleteUsersByFirstName(#Param("firstName") String firstName);
2nd one JPA Query by method
List<User> deleteByLastname(String lastname);
When you go with query by method (2nd way) it will first do a get call
select * from user where last_name = :firstName
Then it will load it in a List
Then it will call delete id one by one
delete from user where id = 18
delete from user where id = 19
First fetch the list of object, then for loop to delete id one by one
But, the 1st option (custom query),
It's just a single query
It will delete wherever the value exists.
Since in 2nd option it is making multiple DB query, try to use the first option.
Go through this link too https://www.baeldung.com/spring-data-jpa-deleteby
If you take a look at the source code of Spring Data JPA, and particularly the PartTreeJpaQuery class, you will see that is tries to instantiate PartTree.
Inside that class the following regular expression
private static final Pattern PREFIX_TEMPLATE = Pattern.compile("^(find|read|get|count|query)(\\p{Lu}.*?)??By")
should indicate what is allowed and what's not.
Of course if you try to add such a method you will actually see that is does not work and you get the full stacktrace.
I should note that I was using looking at version 1.5.0.RELEASE of Spring Data JPA
If you will use pre defined delete methods as directly provided by spring JPA then below two queries will be execute by the framework.
First collect data(like id and other column) using by execute select query with delete query where clause.
then after getting resultSet of first query, second delete queries will be execute for all id(one by one)
Note : This is not optimized way for your application because many queries will be execute for single MYSQL delete query.
This is another optimized way for delete query code because only one delete query will execute by using below customized methods.
#NamedNativeQueries({
#NamedNativeQuery(name = "Abc.deleteByCreatedTimeBetween",
query = "DELETE FROM abc WHERE create_time BETWEEN ?1 AND ?2")
,
#NamedNativeQuery(name = "Abc.getByMaxId",
query = "SELECT max(id) from abc")
})
#Entity
public class Abc implements Serializable {
}
#Repository
public interface AbcRepository extends CrudRepository {
int getByMaxId();
#Transactional
#Modifying
void deleteByCreatedTimeBetween(String startDate, String endDate);
}
It works just
import org.springframework.transaction.annotation.Transactional;
#Transactional
Long removeAddressByCity(String city);
Yes , deleteBy method is supported
To use it you need to annotate method with #Transactional
here follows my 2 cents. You can also use native queries, like:
#Modifying
#Query(value="delete from rreo r where r.cod_ibge = ?1 and r.exercicio= ?2", nativeQuery = true)
void deleteByParameters(Integer codIbge, Integer exercicio);
#Query(value = "delete from addresses u where u.ADDRESS_ID LIKE %:addressId%", nativeQuery = true)
void deleteAddressByAddressId(#Param("addressId") String addressId);

JPA/Hiberante don't generate join sql for FetchType.EAGER while Spring #Transactional annotated

I have a very wired problem.
JPA/Hiberante don't generate join sql for FetchType.EAGER while Spring #Transactional annotated. But if I remove the #Transactional . Everything is fine.
Here is the code:
public class Item {
#ManyToOne
private Order order;
}
public class Order {
#OneToMany(mappedBy = "item", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Item> items;
}
#Test
#Transactional
public void testFetch() throws Exception {
Item randomItem = new Item();
Order randomOrder = new Order();
//OrderService and itemService is implemented by Spring Roo standard.
orderService.saveOrder(randomOrder);
randomItem.setOrder(randomOrder);
itemService.saveItem(randomItem);
Order OrderResult = orderService.findOrder(randomOrder.getId());
final List<Item> itemSearchResult = OrderResult.getItems();
Assert.assertNotNull(itemSearchResult);
}
The assertNotNull will fail if #Transactional on. But will success if #Transactional commented.
I debug more information. Just to find out when #Transactional on Hibernate will not generate join sql for
orderService.findOrder(randomOrder.getId());
Alos I try to switch to elicpseLink as JPA provider. Things become worse, when #Transactional commented, orderService.findOrder(randomOrder.getId()) will return a empty list(not null, size 0).
Any advice? Many Thanks!
I can't comment on the joins in Hibernate except that you should specify fetch join in your query to be portable to other JPA providers. EclipseLink in particular does not join eager relationships without JPA settings or native query hints or #JoinFetch annotations.
As for the collection being empty on EclipseLink. This is because you only setting one side of the relationship. JPA requires you to set both sides of bidirectional relationships so that they remain consistent with what is in the database. When #Transactional is commented out, you are getting back the same randomOrder instance that had an empty items collection when persisted.
Try calling randomOrder.addItems(randomItem); and randomItem.setOrder(randomOrder); before the orderService.saveOrder(randomOrder); call.

Resources