How to implement soft (logical) delete with MongoDB and Spring? - java

I have Spring web app with MongoDB. Currently I always permanently delete data from database.
#Repository
public class SessionRepository extends CrudRepository implements SessionService {
...
#Override
public void insert(Session session) {
saveRoom(session);
getTemplate().insert(session);
}
#Override
public void delete(Session session) {
getTemplate().remove(session);
}
...
}
What would be a good way to change this into soft delete?
----------------- edit 1 -------------------
I understand the logic now what I should do, thanks Sarath Nair. But I am unsure how to implement this in Spring. I have a Session object:
#Document(collection = "session")
public class Session {
#Id
private String id;
private Date startDate;
private Date endDate;
//I just put this here
private boolean deleted = false;
public boolean isDeleted() {
return deleted;
}
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
...
}
I want the field boolean isDeleted to be present in the database but I don't wan't to send that piece of information out with a web service.
#Transient is no good because then the field won't show up in database nor in the HTTP response. Right now I am sending deleted: false in my HTTP response.
How should I edit my Session class?

Have an additional field called is_deleted in collection. Insert is_deleted as false for new documents. When you are deleting just update this value to true for that document. Whenever you need to read documents from collection, pass is_deleted : false for the collection.

Solution with "isDeleted" field will not work because #DbRef still retrieves the "isDeleted" records, I am playing around this problem too.
For your second question, you can use custom SpringHttpMessageConverters with GSON to hide "isDeleted" field.

Related

Spring boot does not throw 409 for duplicate entity in database. What should be the response? [duplicate]

I'm currently playing around on Spring boot 1.4.2 in which I've pulled in Spring-boot-starter-web and Spring-boot-starter-jpa.
My main issue is that when I save a new entity it works fine (all cool).
However if I save a new product entity with the same id (eg a duplicate entry), it does not throw an exception. I was expecting ConstrintViolationException or something similar.
Given the following set up:
Application.java
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
ProductRepository.java
#Repository
public interface ProductRepository extends JpaRepository<Product, String> {}
JpaConfig.java
#Configuration
#EnableJpaRepositories(basePackages = "com.verric.jpa.repository" )
#EntityScan(basePackageClasses ="com.verric.jpa")
#EnableTransactionManagement
public class JpaConfig {
#Bean
JpaTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
Note JpaConfig.java and Application.java are in the same package.
ProductController.java
#RestController
#RequestMapping(path = "/product")
public class ProductController {
#Autowired
ProductRepository productRepository;
#PostMapping("createProduct")
public void handle(#RequestBody #Valid CreateProductRequest request) {
Product product = new Product(request.getId(), request.getName(), request.getPrice(), request.isTaxable());
try {
productRepository.save(product);
} catch (DataAccessException ex) {
System.out.println(ex.getCause().getMessage());
}
}
}
and finally Product.java
#Entity(name = "product")
#Getter
#Setter
#AllArgsConstructor
#EqualsAndHashCode(of = "id")
public class Product {
protected Product() { /* jpa constructor*/ }
#Id
private String id;
#Column
private String name;
#Column
private Long price;
#Column
private Boolean taxable;
}
The getter, setter and equalsHashcode.. are lombok annotations.
Miscellaneous:
Spring boot : 1.4.2
Hibernate ORM: 5.2.2.FINAL
This issue happens regardless if I annotate the controller with or without #Transactional
The underlying db shows the exception clearly
2016-11-15 18:03:49 AEDT [40794-1] verric#stuff ERROR: duplicate key value violates unique constraint "product_pkey"
2016-11-15 18:03:49 AEDT [40794-2] verric#stuff DETAIL: Key (id)=(test001) already exists
I know that is better (more common) to break the data access stuff into its own service layer instead of dumping it in the controller
The semantics of the controller aren't ReST
Things I've tried:
Spring CrudRepository exceptions
I've tried implementing the answer from this question, unfortunately my code never ever hits the DataAccesException exception
Does Spring JPA throw an error if save function is unsuccessful?
Again similar response to the question above.
http://www.baeldung.com/spring-dataIntegrityviolationexception
I tried adding the bean to my JPAconfig.java class that is:
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
But nothing seemed to happen.
Sorry for long post, ty in advance
My solution is a lot cleaner. Spring Data already provides a nice way for us to define how an entity is considered to be new. This can easily be done by implementing Persistable on our entities, as documented in the reference.
In my case, as is the OP's, the IDs come from an external source and cannot be auto generated. So the default logic used by Spring Data to consider an entity as new if the ID is null wouldn't have worked.
#Entity
public class MyEntity implements Persistable<UUID> {
#Id
private UUID id;
#Transient
private boolean update;
#Override
public UUID getId() {
return this.id;
}
public void setId(UUID id) {
this.id = id;
}
public boolean isUpdate() {
return this.update;
}
public void setUpdate(boolean update) {
this.update = update;
}
#Override
public boolean isNew() {
return !this.update;
}
#PrePersist
#PostLoad
void markUpdated() {
this.update = true;
}
}
Here, I have provided a mechanism for the entity to express whether it considers itself new or not by means of another transient boolean property called update. As the default value of update will be false, all entities of this type are considered new and will result in a DataIntegrityViolationException being thrown when you attempt to call repository.save(entity) with the same ID.
If you do wish to perform a merge, you can always set the update property to true before attempting a save. Of course, if your use case never requires you to update entities, you can always return true from the isNew method and get rid of the update field.
The advantages of this approach over checking whether an entity with the same ID already exists in the database before saving are many:
Avoids an extra round trip to the database
We cannot guarantee that by the time one thread has determined that this entity doesn't exist and is about to persist, another thread doesn't attempt to do the same and result in inconsistent data.
Better performance as a result of 1 and having to avoid expensive locking mechanisms.
Atomic
Simple
EDIT: Don't forget to implement a method using JPA callbacks that sets the correct state of the update boolean field just before persisting and just after loading from the database. If you forget to do this, calling deleteAll on the JPA repository will have no effect as I painfully found out. This is because the Spring Data implementation of deleteAll now checks if the entity is new before performing the delete. If your isNew method returns true, the entity will never be considered for deletion.
I think you are aware of CrudRepository.save() is used for both insert and update. If an Id is non existing then it will considered an insert if Id is existing it will be considered update. You may get an Exception if your send the Id as null.
Since you don't have any other annotations apart from #Id on your id variable, The Unique Id generation must be handled by your code Or else you need to make use of #GeneratedValue annotation.
To build upon Shazins answer and to clarify. the CrudRepositroy.save() or JpaRespository.saveAndFlush() both delegate to the following method
SimpleJpaRepository.java
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
Hence if a user tries to create a new entity that so happens to have the same id as an existing entity Spring data will just update that entity.
To achieve what I originally wanted the only thing I could find was to drop back down to JPA solely, that is
#Transactional
#PostMapping("/createProduct")
public Product createProduct(#RequestBody #Valid Product product) {
try {
entityManager.persist(product);
entityManager.flush();
}catch (RuntimeException ex) {
System.err.println(ex.getCause().getMessage());
}
return product;
}
Here if we try to persist and new entity with an id already existing in the database it will throw will throw the constraint violation exception as we originally wanted.
Note that there are 3 scenarios here:
1. Setting ID manually
If there is no choice(like the OP), i.e if you are setting your own id "manually", Spring Data JPA is assuming that you want to check if there are duplicates(hence the SELECT), so it will do a "(i)SELECT + (ii)INSERT" if there is no existing record or a "(i)SELECT + (ii)UPDATE" if there is already an existing record.
In short, 2 SQLs!
2. Use an ID Generator
Cleaner & better, for example:
#Id
#GeneratedValue(generator = "my-uuid")
#GenericGenerator(name = "my-uuid", strategy = "uuid2")
private UUID id;
Result: there is ALWAYS only 1 INSERT statement.
3. Implement Persistable and isNew()
This has already been brilliantly answered by #adarshr, but is also more painful, i.e to implement Persistable(instead of Serializable), and implement the isNew() method.
Result: Also, 1 INSERT statement.
According to Spring Data documentation Spring persists an entity if does not exists or merge, this means update, the existing one:
Saving an entity can be performed via the CrudRepository.save(…)-Method. It will persist or merge the given entity using the underlying JPA EntityManager. If the entity has not been persisted yet Spring Data JPA will save the entity via a call to the entityManager.persist(…)-Method, otherwise the entityManager.merge(…)-Method will be called.

persisted data changed after rest service return

I have a REST service that use a stateless bean to perform an algorithm by using other beans.
All those beans made changes to the fields of my data, including a ManyToOne field.
At the end of the algorithm, just before the “return” of my rest service, my data is in the correct state, and the ManyToOne field in question has been changed.
But just after the return of the rest service, an update seems to happen on that field, and change it back to its previous state. But the other fields are still in the correct state.
What could possibly change my data after the “return” of my rest service ?
The project work with hibernate and JTA transactions in a wildfly environment.
The data looks like below :
public class Data extends JpaDecorator {
#Column(length=1024)
#Override
public String getCommentaire() {
return getImpl().getCommentaire();
}
#Column(length=1024)
#Override
public void setCommentaire(String value) {
getImpl().setCommentaire(value);
}
}
#MappedSuperclass
public class JpaDecorator<T> {
private WorkspaceJpa workspaceJpa;
private T impl;
#Transient
public T getImpl() {
return impl;
}
#ManyToOne
#JoinColumn(name = "wid", referencedColumnName = "oid")
public WorkspaceJpa getJpaWid() {
return this.workspaceJpa;
}
public void setJpaWid(WorkspaceJpa value) {
this.workspaceJpa = value;
Workspace wks = ((this.workspaceJpa != null) ? this.workspaceJpa.getImpl() : null);
getImpl().setWid(wks);
}
}
EDIT :
I finally solved the problem. It came from an unclosed transaction used to collect the data at the beginning of the algorithm. That transaction stayed alive until the return of the rest service, and then was forced to end and flush the state of its data, which was obsolete.

hibernate findbyid causes update?

I faced with a very strange behavior in my web app with spring 3 and hibernate-core 3.5.1-Final.
For simplicity i provide my code..
if(ripid!=null){ //Parameter
Appuntamento apDaRip = appuntamentoService.findById(ripid);
if(apDaRip.getIdpadre()!=null){
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(apDaRip.getIdpadre());
}else{
apDaRip.setNota("RIPROGRAMMATO n."+ripid.toString()+"\n"+apDaRip.getNota());
apDaRip.setIdpadre(ripid);
}
try{
apDaRip.setOrarioinizio(null);
apDaRip.setDurata(null);
//apDaRip.setIdappuntamento(null);
}catch(Exception e){e.printStackTrace();}
map.put("appuntamento", apDaRip);
}
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
The idea behind is to use some data of an object "Appuntamento" for produce a new one.
So i'm going to change some value and before send the object to my view (jsp) i fetch other data by calling findbyid. This cause an update to the Appuntamento object... Off course i don't want this behavior. Someone can have an explanation of this?
Edit-1
Here's the Dao
#Transactional
public class DatiintranetService {
private DatiintranetDAO datiintranetDAO;
public void setDatiintranetDAO(DatiintranetDAO datiintranetDAO) {
this.datiintranetDAO = datiintranetDAO;
}
public DatiintranetDAO getDatiintranetDAO() {
return datiintranetDAO;
}
public Datiintranet findById(Integer id) {
return datiintranetDAO.findById(id);
}
}
and For Appuntamento class I provide to you a snapshot
#Entity
#Table(name = "appuntamento", schema = "public")
public class Appuntamento implements java.io.Serializable {
#Id
#SequenceGenerator(name="appuntamentoID", sequenceName="appuntamento_idappuntamento_seq",allocationSize =1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="appuntamentoID")
#Column(name = "idappuntamento", unique = true, nullable = false)
public Integer getIdappuntamento() {
return this.idappuntamento;
}
}
Edit-2
IF i move thoese two row above the if statement no update occur.
di = datiintranetService.findById(DatiintranetService.PASS_X_INTERVENTI);
map.put("passinterventi", di.getBoolean());
If you query for an entity and change the entity, the default behavior is to persist those changes via an update to the database. This is usually what you want to happen, but obviously not in all cases.
If you want to avoid the update, you need to detach the entity by calling session.evict(apDaRip) where session is a reference to the hibernate session (see Session.evict()). You probably want to evict the entity right after you get it (immediately following the call to findById).

How to get old entity value in #HandleBeforeSave event to determine if a property is changed or not?

I'm trying to get the old entity in a #HandleBeforeSave event.
#Component
#RepositoryEventHandler(Customer.class)
public class CustomerEventHandler {
private CustomerRepository customerRepository;
#Autowired
public CustomerEventHandler(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
#HandleBeforeSave
public void handleBeforeSave(Customer customer) {
System.out.println("handleBeforeSave :: customer.id = " + customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
Customer old = customerRepository.findOne(customer.getId());
System.out.println("handleBeforeSave :: new customer.name = " + customer.getName());
System.out.println("handleBeforeSave :: old customer.name = " + old.getName());
}
}
In the event I try to get the old entity using the findOne method but this return the new event. Probably because of Hibernate/Repository caching in the current session.
Is there a way to get the old entity?
I need this to determine if a given property is changed or not. In case the property is changes I need to perform some action.
If using Hibernate, you could simply detach the new version from the session and load the old version:
#RepositoryEventHandler
#Component
public class PersonEventHandler {
#PersistenceContext
private EntityManager entityManager;
#HandleBeforeSave
public void handlePersonSave(Person newPerson) {
entityManager.detach(newPerson);
Person currentPerson = personRepository.findOne(newPerson.getId());
if (!newPerson.getName().equals(currentPerson.getName)) {
//react on name change
}
}
}
Thanks Marcel Overdijk, for creating the ticket -> https://jira.spring.io/browse/DATAREST-373
I saw the other workarounds for this issue and want to contribute my workaround as well, cause I think it´s quite simple to implement.
First, set a transient flag in your domain model (e.g. Account):
#JsonIgnore
#Transient
private boolean passwordReset;
#JsonIgnore
public boolean isPasswordReset() {
return passwordReset;
}
#JsonProperty
public void setPasswordReset(boolean passwordReset) {
this.passwordReset = passwordReset;
}
Second, check the flag in your EventHandler:
#Component
#RepositoryEventHandler
public class AccountRepositoryEventHandler {
#Resource
private PasswordEncoder passwordEncoder;
#HandleBeforeSave
public void onResetPassword(Account account) {
if (account.isPasswordReset()) {
account.setPassword(encodePassword(account.getPassword()));
}
}
private String encodePassword(String plainPassword) {
return passwordEncoder.encode(plainPassword);
}
}
Note: For this solution you need to send an additionally resetPassword = true parameter!
For me, I´m sending a HTTP PATCH to my resource endpoint with the following request payload:
{
"passwordReset": true,
"password": "someNewSecurePassword"
}
You're currently using a spring-data abstraction over hibernate.
If the find returns the new values, spring-data has apparently already attached the object to the hibernate session.
I think you have three options:
Fetch the object in a separate session/transaction before the current season is flushed. This is awkward and requires very subtle configuration.
Fetch the previous version before spring attached the new object. This is quite doable. You could do it in the service layer before handing the object to the repository. You can, however not save an object too an hibernate session when another infect with the same type and id it's known to our. Use merge or evict in that case.
Use a lower level hibernate interceptor as described here. As you see the onFlushDirty has both values as parameters. Take note though, that hibernate normally does not query for previous state of you simply save an already persisted entity. In stead a simple update is issued in the db (no select). You can force the select by configuring select-before-update on your entity.
Create following and extend your entities with it:
#MappedSuperclass
public class OEntity<T> {
#Transient
T originalObj;
#Transient
public T getOriginalObj(){
return this.originalObj;
}
#PostLoad
public void onLoad(){
ObjectMapper mapper = new ObjectMapper();
try {
String serialized = mapper.writeValueAsString(this);
this.originalObj = (T) mapper.readValue(serialized, this.getClass());
} catch (Exception e) {
e.printStackTrace();
}
}
}
I had exactly this need and resolved adding a transient field to the entity to keep the old value, and modifying the setter method to store the previous value in the transient field.
Since json deserializing uses setter methods to map rest data to the entity, in the RepositoryEventHandler I will check the transient field to track changes.
#Column(name="STATUS")
private FundStatus status;
#JsonIgnore
private transient FundStatus oldStatus;
public FundStatus getStatus() {
return status;
}
public FundStatus getOldStatus() {
return this.oldStatus;
}
public void setStatus(FundStatus status) {
this.oldStatus = this.status;
this.status = status;
}
from application logs:
2017-11-23 10:17:56,715 CompartmentRepositoryEventHandler - beforeSave begin
CompartmentEntity [status=ACTIVE, oldStatus=CREATED]
Spring Data Rest can't and likely won't ever be able to do this due to where the events are fired from. If you're using Hibernate you can use Hibernate spi events and event listeners to do this, you can implement PreUpdateEventListener and then register your class with the EventListenerRegistry in the sessionFactory. I created a small spring library to handle all of the setup for you.
https://github.com/teastman/spring-data-hibernate-event
If you're using Spring Boot, the gist of it works like this, add the dependency:
<dependency>
<groupId>io.github.teastman</groupId>
<artifactId>spring-data-hibernate-event</artifactId>
<version>1.0.0</version>
</dependency>
Then add the annotation #HibernateEventListener to any method where the first parameter is the entity you want to listen to, and the second parameter is the Hibernate event that you want to listen for. I've also added the static util function getPropertyIndex to more easily get access to the specific property you want to check, but you can also just look at the raw Hibernate event.
#HibernateEventListener
public void onUpdate(MyEntity entity, PreUpdateEvent event) {
int index = getPropertyIndex(event, "name");
if (event.getOldState()[index] != event.getState()[index]) {
// The name changed.
}
}
Just another solution using model:
public class Customer {
#JsonIgnore
private String name;
#JsonIgnore
#Transient
private String newName;
public void setName(String name){
this.name = name;
}
#JsonProperty("name")
public void setNewName(String newName){
this.newName = newName;
}
#JsonProperty
public void getName(String name){
return name;
}
public void getNewName(String newName){
return newName;
}
}
Alternative to consider. Might be reasonable if you need some special handling for this use-case then treat it separately. Do not allow direct property writing on the object. Create a separate endpoint with a custom controller to rename customer.
Example request:
POST /customers/{id}/identity
{
"name": "New name"
}
I had the same problem, but I wanted the old entity available in the save(S entity) method of a REST repository implementation (Spring Data REST).
What I did was to load the old entity using a 'clean' entity manager from which I create my QueryDSL query:
#Override
#Transactional
public <S extends Entity> S save(S entity) {
EntityManager cleanEM = entityManager.getEntityManagerFactory().createEntityManager();
JPAQuery<AccessControl> query = new JPAQuery<AccessControl>(cleanEM);
//here do what I need with the query which can retrieve all old values
cleanEM.close();
return super.save(entity);
}
The following worked for me. Without starting a new thread the hibernate session will provide the already updated version. Starting another thread is a way to have a separate JPA session.
#PreUpdate
Thread.start {
if (entity instanceof MyEntity) {
entity.previous = myEntityCrudRepository.findById(entity?.id).get()
}
}.join()
Just let me know if anybody would like more context.
Don't know if you're still after an answer, and this is probably a bit 'hacky', but you could form a query with an EntityManager and fetch the object that way ...
#Autowired
EntityManager em;
#HandleBeforeSave
public void handleBeforeSave(Customer obj) {
Query q = em.createQuery("SELECT a FROM CustomerRepository a WHERE a.id=" + obj.getId());
Customer ret = q.getSingleResult();
// ret should contain the 'before' object...
}

Objectify Key/Ref roundtrip between backend and client [without GWT]

There are a lot of articles here and all over the web, but these all target different Objectify versions and seem not to work for one or the other reason.
I have an entity, which references another entity (e.g. an Account entity references a User entity):
#Cache
#Entity
public final class Account {
#Id Long id;
#Index private Ref<User> user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user.get();
}
public void setUser(User user) {
this.user = Ref.create(user);
}
}
I am trying to do this:
From the client, GET the account entity over REST/Google Cloud Endpoints.
Modify the resource.
UPDATE it on the server.
As discussed here Objectify loads object behind Ref<?> even when #Load is not specified above code always returns the referenced user as well, whích I don't want.
One option would be, as #svpino suggested, "Make your #ApiMethod return a different Account object without the user property (thus avoiding fetching the user if you don't need it)." This works as long as I don't want to UPDATE the resource. If I need to UPDATE, the Key/Ref needs to be preserved (even though I don't need it on the client).
One possible approach that I can see would be using Key instead of Ref and rendering a web-safe string, then recreating the user during UPDATE.
private Key<User> user;
public String getUser() {
return user.toString();
}
public void setUser(String user) {
this.user = Key.create(user);
}
The string looks like "Key(User(5723348596162560))", but it seems not to be reconstituted (at least I get an exception here, haven't tracked it down yet).
Another approach would be writing an #ApiTransformer, which did not solve the problem either.
Jeff #StickFigure posted several times during the last years and the issue still seems not to be solved.
What's the current state with Objectify 5.0.2 and what's the recommendation for preserving the key between roundtrips, when the key is not needed on the client?
You need to annotate the property that you want to omit with #ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
Google documentation says the following about the #ApiResourceProperty:
#ApiResourceProperty provides provides more control over how resource
properties are exposed in the API. You can use it on a property getter
or setter to omit the property from an API resource. You can also use
it on the field itself, if the field is private, to expose it in the
API. You can also use this annotation to change the name of a property
in an API resource.
I encourage you to read more by visiting this link
https://developers.google.com/appengine/docs/java/endpoints/annotations#apiresourceproperty
So in your case your class should look like this after the modification.
#Cache
#Entity
public final class Account
{
#Id Long id;
#Index private Ref<User> user;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public User getUser() {
return user.get();
}
#ApiResourceProperty(ignored = AnnotationBoolean.TRUE)
public void setUser(User user) {
this.user = Ref.create(user);
}
}
The following code serializes an entity object to a web-safe string so it can be transferred over REST. When the entity is sent back to the server, the Ref<> is reconstituted. This way a server-side reference is not lost while the object does a round-trip to the client. This way referenced objects are not transferred to the client and back, but can be "worked" as Ref<> on the client.
#Index private Ref<User> user;
// for serialization
public String getUser() {
return user.getKey().getString(); // .toWebSafeString() will be added in future version of objectify and .toWebSafeString() will do the same as .getString()
}
public void setUser(String webSafeString) {
Key<User> key = Key.create(webSafeString);
this.user = Ref.create(key);
}
Two separate functions (not named well, I admit) are there for loading the actual object on the server and for creating the reference in the first place:
// for load and create reference
public User loadUser() {
return user.get();
}
public void referenceUser(User user) {
this.user = Ref.create(user);
}
I hope this solves the problem for everybody. This did not yet go through thorough testing, so comments are still welcome.
I have run a test to compare between using a Key<> and a Ref<> and to me it looks like even with Ref<> the entity is only reconstituted when loadEntity()/.get() is called. So Ref<> if probably better as #Load annotations will work. Maybe the objectify guys can confirm this.
You can create a class that extends Ref<User> and use an #ApiTransformer to transfer that class between backend and client
#ApiTransformer(UserRefTransformer.class)
public class UserRef extends LiveRef<User>
{
}
public class UserRefTransformer implements Transformer<UserRef, User>
{
// Your transformation code goes here
}

Categories

Resources