Example for #DomainEvents and #AfterDomainEventsPublication - spring

I have encountered the #DomainEvents and #AfterDomainEventsPublication annotation in spring Data JPA Reference Documentation. But I am not able to find the perfect example to explain about these annotaions

You can see sample in the original unit tests for EventPublishingRepositoryProxyPostProcessor EventPublishingRepositoryProxyPostProcessorUnitTests.java by Oliver Gierke in GitHub Repository of Spring Data Commons.
Description in base issue of Spring Jira DATACMNS-928 Support for exposing domain events from aggregate roots as Spring application events was useful for me.
UPDATE
This is simple and really working example by Zoltan Altfatter:
Publishing domain events from aggregate roots

Here is my example code:
package com.peaceelite.humanService;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import org.springframework.data.domain.AfterDomainEventPublication;
import org.springframework.data.domain.DomainEvents;
import java.util.*;
#Entity
public class SalesmanCustomerRelationship{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
/*getters & setters*/
#DomainEvents
Collection<Object> domainEvents() {
List<Object> result = new ArrayList<Object>();
result.add("Here should be an Event not a String, but, anyway");
return result;
}
#AfterDomainEventPublication
void callbackMethod() {
System.out.println("DATA SAVED!\n"+"WELL DONE");
}
}
This is an entity class managed by a spring data repository. Both #DomainEvents and #AfterDomainEventPublication happens after CrudRepository.save() being executed. One thing that is interesting is that #AfterDomainEventPublication ONLY works when #DomainEvents exists.
I'm learning Spring Data reference too, both this question and Dmitry Stolbov's answer helped me a lot.

Related

Spring Boot JPA Query modify dynamically

Using Spring boot,I am working on one business use case where i need to modify the JPA query generated at runtime based on configuration.
For Example .. if query that JPA generates is
select * from customers where id=1234
I want to modify it in runtime like based on user's logged in context. (Context has one attribute business unit) like given below ..
select * from customers where id=1234 and ***business_unit='BU001'***
Due to certain business use case restrictions i can't have statically typed query.
Using Spring boot and Postgres SQL.
Try JPA criteria builder , it let you to create dynamics query programmatically.
Take look in this post
What is stopping you to extract the business unit from the context and pass it to the query?
If you have this Entity
#Entity
CustomerEntity {
Long id;
String businessUnit;
//geters + setters
}
you can add this query to your JPA Repository interface:
CustomerEntity findByIdAndBusinessUnit(Long id, String businessUnit)
This will generate the following "where" clause:
… where x.id=?1 and x.businessUnit=?2
for complete documentation check Spring Data Jpa Query creation guide.
you would do something like this, this lets you dynamically define additional predicates you need in your query. if you don't want to have all the conditions in your query with #Query
The below example just adds a single predicate.
import java.util.ArrayList;
import java.util.List;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;
import study.spring.data.jpa.models.TicketPrice;
#Component
public class TricketPriceCriteriaRepository {
#Autowired
TicketPriceJpaRepository ticketPriceJpaRepository;
public List<TicketPrice> findByCriteria(int price) {
return ticketPriceJpaRepository.findAll(new Specification<TicketPrice>() {
#Override
public Predicate toPredicate(Root<TicketPrice> root, CriteriaQuery<?> query,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
if (price > 0) {
predicates.add(
criteriaBuilder.and(criteriaBuilder.greaterThan(root.get("basePrice"), price)));
}
// Add other predicates here based on your inputs
// Your session based predicate
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
});
}
}
Your base repository would be like
// Other imports
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface TicketPriceJpaRepository
extends JpaRepository<TicketPrice, Long>, JpaSpecificationExecutor<TicketPrice> {}
the model consists basePrice
#Column(name = "base_price")
private BigDecimal basePrice;

ERROR: operator does not exist: uuid = bigint

I have an error regarding deploying the backend trough docker on localhost 8080 .
When i run the website normally (started the postgres server from inteliji) it works properly.
When i try to deploy it trough docker i get the following error:
org.postgresql.util.PSQLException: ERROR: operator does not exist: uuid = bigint
Hint: No operator matches the given name and argument types. You might need to add explicit type casts.
Position: 580
The next code is an example of class using UUID
package com.ds.assignment1.ds2021_30244_rusu_vlad_assignment_1.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.UUID;
#Entity
#Data
public class Account {
#Id
#GeneratedValue(generator = "uuid4")
#GenericGenerator(name = "uuid4", strategy = "org.hibernate.id.UUIDGenerator")
private UUID id;
private String username;
private String password;
private String role;
}
The error shows that the database column is UUID whereas the JPA entity is bigint. I should mention that this may be or may not be about the ID field of this entity.
As #Adrian-klaver said you have to look at position 580 of the SQL query, which you can do by enabling hibernate query logs and looking at the last hibernate SQL query log.
I had a similar problem, spending three days to finally resolve it. My problem was due to having multiple attribute types being different than their database type. As I was migrating from Long id to UUID, it made me confuse figuring out what the cause for my error was.

Same Generic commit object getting saved from different instances

I am using Javers version 5.1.2, with jdk 11, in my application, where I am committing Generic Object T and saving into mongodb. The Generic commit objects are actually created from generic rest service, where user can pass any Json.
Every thing is going fine on single instance. Whenever any re commit is sent with same request, Javers commit.getChanges().isEmpty() method returns true.
Issues:
1) Whenever same request to sent to different instance, commit.getChanges().isEmpty() method returns false.
2) If I commit one request, and restart the instance and then again commit, commit.getChanges().isEmpty() again returns false. Instead of true.
As a result of above issue, new version is getting created if request goes to different new instance or instance is restarted.
Could you please let me know, how we can handle this issue.
I will extract code from the project and will create a sample running project and share.
Right now, I can share few classes, please see, if these help:
//---------------------Entitiy Class:
import java.util.Map;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
#AllArgsConstructor
#NoArgsConstructor
#ToString
public class ClientEntity<T> {
#Getter
#Setter
private String entityId;
#Getter
#Setter
private T commitObj;
#Getter
#Setter
private String authorName;
#Getter
#Setter
private boolean major;
#Getter
#Setter
private Map<String, String> commitProperties;
}
//--------DataIntegrator
#Service
public class DataIntegrator {
private final Javers javers;
private IVersionRepository versionDao;
private IdGenerator idGenerator;
#Inject
public DataIntegrator(Javers javers, IVersionRepository versionDao, IdGenerator idGenerator) {
this.javers = javers;
this.versionDao = versionDao;
this.idGenerator = idGenerator;
}
public <T> String commit(ClientEntity<T> clientObject) {
CommitEntity commitEntity = new CommitEntity();
commitEntity.setEntityId(clientObject.getEntityId());
commitEntity.setEntityObject(clientObject.getCommitObj());
Map<String, String> commitProperties = new HashMap<>();
commitProperties.putAll(clientObject.getCommitProperties());
commitProperties.put(commit_id_property_key, clientObject.getEntityId());
commitProperties.putAll(idGenerator.getEntityVersions(clientObject.getEntityId(), clientObject.isMajor()));
Commit commit = javers.commit(clientObject.getAuthorName(), commitEntity, commitProperties);
if (commit.getChanges().isEmpty()) {
return "No Changes Found";
}
versionDao.save(
new VersionHead(clientObject.getEntityId(), Long.parseLong(commitProperties.get(major_version_id_key)),
Long.parseLong(commitProperties.get(minor_version_id_key))));
return commit.getProperties().get(major_version_id_key) + ":"
+ commit.getProperties().get(minor_version_id_key);
}
}
1) commitObj is a Generic object, in ClientEntity, which holds Json coming from the Rest webService. The JSON can be any valid json. Can have nested structure also.
2) After calling javers.commit method, we are checking if it is existing entity or there is any change using commit.getChanges().isEmpty().
If same second request goes to same instance, it returns true for change, as expected
If same second request goes to different instance, under load balancer, it takes it as different request and commit.getChanges().isEmpty() returns false. Expected response should be true, as it is same version.
If after first request, I restart instance, and make a same request, it returns false, instead of true, which means, getChanges method taking the same request as same.

#Query does not give desired result when native query is used

iam using spring data jpa in my project
package com.mf.acrs.model;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
#Data
#Entity(name= "mv_garage_asset_mapping")
public class GarageAssetMapping implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2535545189473989744L;
#Id
#Column(name="GARAGE_CODE")
private String garageCode;
#Column(name="GARAGE_NAME")
private String garageName;
#Column(name="GARAGE_ADDRESS")
private String garageAddress;
#Column(name="GARAGE_BRANCH")
private String garageBranch;
#Column(name="CONTRACT_NUMBER")
private String contractNumber;
}
this is my entity object
package com.mf.acrs.repository;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import com.mf.acrs.model.GarageAssetMapping;
public interface GarageAssetMappingRepository extends JpaRepository<GarageAssetMapping, String> {
// #Query(name="select u.CONTRACT_NUMBER from mv_garage_asset_mapping u where u.GARAGE_CODE = ?1", nativeQuery = true) //**QUERY 1**
#Query("select u.contractNumber from mv_garage_asset_mapping u where u.garageCode = ?1") // **QUERY 2**
List<String> findByGarageCode(String garageCode);
}
this is my repository interface
when i use the QUERY 1 in my application the query fired by spring data jpa is
Hibernate: select garageasse0_.garage_code as garage_code1_2_, garageasse0_.contract_number as contract_number2_2_, garageasse0_.garage_address as garage_address3_2_, garageasse0_.garage_branch as garage_branch4_2_, garageasse0_.garage_name as garage_name5_2_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
but when i use QUERY 2 the query fired is
Hibernate: select garageasse0_.contract_number as col_0_0_ from mv_garage_asset_mapping garageasse0_ where garageasse0_.garage_code=?
QUERY 2 gives me desired result.
but my question is why spring data jpa fires a incorrect query in 1st case.
in QUERY 1 hibernate tries to pull all the data fields despite the fact i have explicitly written in query that i want to fetch only one field.
What mistake iam doing in this case?
The method defined in the controller which calls the method is below:
#PostMapping("/searchAssetsAjax")
#ResponseBody
public String searchAssetsAjax(#RequestBody SearchAssetData searchAssetData) throws IOException{
System.out.println("iam in the searchAssetsAjax "+searchAssetData);
System.out.println("iam in the searchAssetsAjax "+searchAssetData.toString());
// System.out.println("throwing exceptions" ); throw new IOException();
System.out.println("hitting the db "+searchAssetData.getGarageCode());
// List<String> contractNums = garageAssetMapRepository.findContractNumberByGarageCode(searchAssetData.getGarageCode());
List<String> contractNums = garageAssetMapRepository.findByGarageCode(searchAssetData.getGarageCode());
System.out.println("############contract num size is "+contractNums.size());
for(String contract: contractNums) {
System.out.println("contract nums are "+contract);
}
return "success";
}

Spring Boot repository save does not work (only shows a select)

I'm facing for hours with a strange proceeding in Spring Boot when try to save a mapped entity.
The entity class with a composite key that must all be set by the user is as follows:
package model
import javax.persistence.*
#Entity
#Table(name = 'MY_TABLE')
#IdClass(MyIdClass.class)
class MyClass implements Serializable{
#Id
#Column(name = "MY_COLUMN_1")
Long column1
#Id
#Column(name = "MY_COLUMN_2")
Long column2
#Id
#Column(name = "MY_COLUMN_3")
String column3
#Id
#Column(name = "MY_COLUMN_4")
Date date1
#Column(name = "MY_COLUMN_5")
Date date2
#Column(name = "MY_COLUMN_6")
BigDecimal column6
}
#Embeddable
class MyIdClass implements Serializable{
Long column1
Long column2
String column3
Date date1;
}
The corresponding repository is:
package repository
import org.springframework.data.repository.CrudRepository
interface MyRepository extends CrudRepository<MyClass, Long>{
}
My service is:
package service
import model.MyClass
import repository.MyRepository
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
#Service
class MyService {
#Autowired
MyRepository repository
void save(MyClass myClass) {
repository.save(myClass)
}
}
My controller mounts a MyClass object with all data set, including the composite key. When it calls the service save method the object is not inserted in the database. I saw the logs and checked that there is a SELECT in MY_TABLE instead of INSERT. I tried not to inform the composite key in the object and then the save method did an INSERT with error due to null values in the primary key.
I really don't understand why the insertion is not done when the composite key has values. How can I solve it?
I've already tried with #Transactional in service class and didn't work. I didn't do any Transaction configuration in the project since Spring Boot delivers it as default.
Thanks.
It seems you are using MyIdClass as the Id for MyClass. So, the Repository should be:
interface MyRepository extends CrudRepository<MyClass, MyIdClass>{
}
Hope this help.
I take your code sample and tried it on a sample Spring Boot project, where I was able to save to H2 DB (In memory) with #Embeddable & #EmbeddedId annotations. If you would like to verify, you can clone the GitHub repo and run the BootJpaApplication.java as a Java Application.
After execution access the H2 console with the below link from local where table details can be verified.
http://localhost:8080/h2-console
https://github.com/sujittripathy/springboot-sample.git
Hope the detail helps:)

Resources