I have some code to delete a recipe from my mongoDB database:
#RequestMapping(value = "/recipe/{id}", method = RequestMethod.DELETE)
public String deleteSingleRecipe(#PathVariable("id") String recipeId) {
try {
repository.deleteById(recipeId);
return "Deleted RECIPE success";
} catch (Exception ex) {
return ex.toString();
}
}
This is able to successfully delete a recipe based on ID. However, I'm unsure how to catch cases such as if the recipe doesn't even exist, or if the deletion is failed.
With JavaScript/Node this was really easy because I could pass a callback function with depending on if result/error were null I could determine the success of the query and proceed. I'm pretty lost how to do this in Java/Spring.
When I tried to delete a recipe a 2nd time I still got "Deleted RECIPE success".
If you check the JPARepository Interface you will get
/**
* Deletes the entity with the given id.
*
* #param id must not be {#literal null}.
* #throws IllegalArgumentException in case the given {#code id} is {#literal null}
*/
void deleteById(ID id);
/**
* Deletes a given entity.
*
* #param entity
* #throws IllegalArgumentException in case the given entity is {#literal null}.
*/
void delete(T entity);
so as per your requirement it will not throw any exception if given id is not exist in DB.
for that you can use boolean isFound = repository.existsById(recipeId); and if isFound is true you can delete it. and if isFound is false then you can throw exception.
second way is you can check
public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T>
this class contains deleteById method. and this method will throw exception if id is not exist in DB.
/*
* (non-Javadoc)
* #see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
*/
#Transactional
public void deleteById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException(
String.format("No %s entity with id %s exists!", entityInformation.getJavaType(), id), 1)));
}
Starting from Spring Data JPA (>=1.7.x), we can use the derived delete or remove query. It returns the number of entities deleted or what all entities are deleted.
Using it you can update your code as:
public interface RecipetRepository extends CrudRepository<Reciepe, Long> {
long deleteById(String id);
}
and we can throw an exception if the entity doesn't exist.
#RequestMapping(value = "/recipe/{id}", method = RequestMethod.DELETE)
public String deleteSingleRecipe(#PathVariable("id") String recipeId) {
long numberOfRecipiesDeleted;
try {
numberOfRecipiesDeleted = repository.deleteById(recipeId);
} catch (DataAccessException ex) {
return ex.toString();
}
if(numberOfRecipiesDeleted == 0){
throw new ReciepeNotFoundException();
}
return "Deleted RECIPE success";
}
You can add a check before using existsById
boolean isFound = repository.existsById(recipeId);
Returns whether an entity with the given id exists.
Try add a new method to the repository interface like this
#Modifying
#Query("DELETE FROM MY_TABLE WHERE ID = ?1")
default boolean deleteById(IdPrimitiveType id){
return true;
}
Hope it helps
It has been reported to Spring as a bug (which is still open) : https://jira.spring.io/browse/DATAMONGO-1997?jql=text%20~%20%22deleteById%22
Checking if it exists before or after was not a solution for us as it is sensitive to race condition. The best and more efficient workaround we found was what #SandOfTime suggested : in your MongoRepository interface, add :
#DeleteQuery("{_id:?0}")
Long deleteByIdReturningDeletedCount(String id);
I also had a similar kind of requirement but my case is slightly different. First I'll tell my requirement I need to add a user if the username is not exist. So in the Repository there is a built in method to find the username.
Usermodel findByUsername(String username);
So in the Controller layer I've implemented the following.
public String signUp(#RequestBody Usermodel user) {
if(userRepository.findByUsername(user.getUsername())==null) {
userRepository.save(user);
return "Success";
}
else{
return "Username Already Exsist";
}
}
I think for your case also this can be applied. Need to check the recipeId and then you can perform a deletion.
Related
I am using Spring Data JPA to process database calls. For this purpose, I have created:
An EmployeeRepository interface, which extends a JpaRepository<Employee, Long>
An EmployeeService, which defines three methods:
Employee saveEmployee(Employee employee);
Optional<Employee> getEmployee(Long id);
Long deleteEmployee(Long id);
An implementation of the EmployeeService:
#Override
public Employee saveEmployee(Employee employee) {
return employeeRepository.save(employee);
}
#Override
public Optional<Employee> getEmployee(Long id) {
return employeeRepository.findEmployeeById(id);
}
#Override
public Long deleteEmployee(Long id) {
employeeRepository.deleteById(id);
return id;
}
The issue is the following:
The get-methods work fine and can return an optional. The save-method, on the other hand, cannot return an optional. Apparently the JpaRepository returns an instance of the saved object upon calling save(). I would rather return an optional, since something could go wrong when saving the employee and in that case, I would like to throw an error - i.e. whenever the optional is not present, I throw an error.
The same holds for the delete-operation: What, for example, if I ask to delete an employee and pass in an id, which does not exist? I would like to catch this error and only then return the passed in id, if the delete operation was successfull. Which error would I have to catch for this purpose? Can someone explain this to me?
=================================================
update:
I have fixed the problem with the delete-call by simply checking if the given employee-id exists before calling `deleteById(id); if it doesn't, the service returns null, if it does, it return the id. The controller looks like this:
#DeleteMapping("/{id}")
public ResponseEntity<Long> deleteEmployee(#PathVariable Long id) {
Long deletedEmployeeId = employeeService.deleteEmployee(id);
if (deletedEmployeeId != null) {
return ResponseEntity.ok(deletedEmployeeId);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST);
}
I am missing the DataAccessException, however. So, can it be that I would actually have to do the following:
#DeleteMapping("/{id}")
public ResponseEntity<Long> deleteEmployee(#PathVariable Long id) {
try {
Long deletedEmployeeId = employeeService.deleteEmployee(id);
if (deletedEmployeeId != null) {
return ResponseEntity.ok(deletedEmployeeId);
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST);
}
} catch (DataAccessException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
}
This looks a bit like an over-kill to be honest.
I am still a bit unsure how to deal with the save-call. Before I posted this question, my controller was simply doing the following:
#PostMapping
public ResponseEntity<Employee> saveEmployee(#RequestBody Employee employee) {
return ResponseEntity.ok(employeeService.saveEmployee(employee));
}
What happens, if employeeService.saveEmployee(employee) throws a DataAccessException? Am I still returning a HTTP-status-code of 200, as I wrap the response in an ResponseEntity.ok() ?
If so, I would suggest to do the following:
#PostMapping
public ResponseEntity<Employee> saveEmployee(#RequestBody Employee employee) {
try {
Employee savedEmployee = employeeService.saveEmployee(employee);
return ResponseEntity.ok(savedEmployee);
} catch (DataAccessException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Is this something people do? Or are DataAccessExceptions usually neglected as they are not expected?
The method "save" always returns you the same object you are going to save. Only by checking the "id", you can see if the object is saved or not. But if an error occurs in the database, an exception will be thrown and you can catch it by putting "employeeRepository.save(employee)" in a try-catch block. The same way you can do for deleteById
Spring's Security "hasPermission" method has an implementation, which (as I get) is intended for passing class name (targetType) and Object Id (Serializable).
So could you please explain (at least in general) how to do this implementation right?
I've searched for example of passing object ID and found no any (even at Spring's doc).
In my situation I want to check for User's DELETE permission on some of my classes (for instance, "Goal"). All of these classes has universal methods and fields, so I can have universal logic for checking permission inside a PermissionEvaluator.
For doing this I'm intended to pass an Object's ID and Object's class name into PermissionEvaluator and do the check here like this:
#PreAuthorize("hasPermission(#id, 'Goal','DELETE')")
It sounds pretty good till it not comes to the implementation, because I don't really understand how can I get Object's instance by Class name and Id inside Permission evaluator.
#Component
public class CustomPermissionEvaluator implements PermissionEvaluator
#Override
public boolean hasPermission(Authentication authentication, Serializable serializable, String targetType,
Object permission) {
Yes, I can instantiate object by Class.forName(targetType), but how can I get it's instance by Id (serializable) from appropriate Repository then? (I have different repository for every object).
#Autowiring all of my 30 repositories would be the madness.
Implemented my service, which takes Object ID and Object Type and then sends back Object, which I can later unbox. I used dynamic HQL, so no need in 30+ JPA repositories autowiring (my bad, I missed this possibility at the beginning).
#PersistenceContext
EntityManager entityManager;
static String entityClassPath="com.platform.entity.";
public Object getEntity(String className, Long id) {
String classToQuery = capitalize(className);
/* Check if Entity class exists to decide whether to query DB or not */
try {
Class cls = Class.forName(entityClassPath + className);
} catch (Exception e) {
return null;
}
/* Query DB if Entity class exist */
Query query;
try {
query = entityManager.createQuery("SELECT Q FROM " + classToQuery + " Q WHERE Q.id=?1");
query.setParameter(1, id);
return query.getSingleResult();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
I have used a delete method of Spring Data JPA in the service layer, but I wonder why neither the deleteById method nor delete method has any return values.
If we inspect the implementation of the delete method carefully, there is an if statement that when the entity to be deleted doesn't exist returns nothing.
public void delete(T entity) {
Assert.notNull(entity, "Entity must not be null!");
if (entityInformation.isNew(entity)) {
return;
}
Class<?> type = ProxyUtils.getUserClass(entity);
T existing = (T) em.find(type, entityInformation.getId(entity));
// if the entity to be deleted doesn't exist, delete is a NOOP
if (existing == null) {
return;
}
em.remove(em.contains(entity) ? entity : em.merge(entity));
}
Personally, I think returning a Boolean value could be an adequate approach in this case because the controller layer will know about the deletion status, and the view layer can be provided with the far more reliable alert message.
Spring Data JPA design some build-in methods that way they think and give us the option to use the other way also.
You can easily get deleted records and their count using derived delete query supported By Spring Data JPA (Reference)
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
Fruit deleteById(Long id); // To get deleted record
}
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
Long deleteById(Long id); // To get deleted record count
}
use #Modifying and #Query ant it will return number of deleted rows.
#Repository
public interface FruitRepository extends JpaRepository<Fruit, Long> {
#Modifying
#Query(value = "DELETE FROM Fruit f where f.id = ?1")
int costumDeleteById(Long id);
}
Another option would be to follow the suggestion from this answer and check if the number of affected entities is 1 (in case of a deleteById method).
Assume that "Project" is POJO. In service layer of my project, I wrote a function that is get a row from table.
#Override
public ProjectDto getAProject(Long id) {
try {
Project project = projectRepository.getOne(id);
if (project==null) {
throw new IllegalArgumentException("Project not found");
} else {
return modelMapper.map(project, ProjectDto.class);
}
} catch (EntityNotFoundException ex) {
throw new IllegalArgumentException("Project not found");
}
}
The function is working fine with already exist id values. But if I give non-exist value, an exception occur like following. Looks like "getOne()" function don't throw "EntityNotFoundException".
ModelMapper mapping errors: Error mapping com.issuemanagement.entity.Project to com.issuemanagement.dto.ProjectDto
that means the exception come from model mapper logic. Because "project" object filled with null values so couldn't map to DTO class. I modified the function as following to fix this.
#Override
public ProjectDto getAProject(Long id) {
boolean isExist = projectRepository.existsById(id);
if (isExist) {
Project project = projectRepository.getOne(id);
return modelMapper.map(project, ProjectDto.class);
} else {
throw new IllegalArgumentException("Project not found");
}
}
but in this way the program goes to DB for two times. I don't like it. How can I do this operation with just one transaction?
BTW, if I try to run "toString()" function of "project", it throw "EntityNotFoundException" but it's looks like not official way. or it is? I hope there should be a boolean variable in somewhere.
getOne() on JpaRepository will call getReference() on EntityManager under the hood which will return an instance whose state is lazily fetch .The EntityNotFoundException will not throw immediately but will only be thrown when its state is accessed at the first time .
It is normally used in the case that when you need to configure a #ManyToOne relationship for an entity (Let say configure a Department for an Employee) but you just have the ID of the related entity.(e.g. DepartmentId) . Using getOne() allows you to get a Department proxy instance such that you do not really need to query the database to get the actual Department instance just for setting up its relationship for an Employee.
In your case , you should use findById() which will return an empty Optional if the instance does not exist:
#Override
public ProjectDto getAProject(Long id) {
Project project = projectRepository.findById(id)
.orElseThrow(()->new IllegalArgumentException("Project not found"));
return modelMapper.map(project, ProjectDto.class);
}
I am using org.appfuse.dao.hibernatepackage and I have used all the method in the GenericDaoHibernate<T,PK> class.
I found these methods
public List<T> getAll();
public List<T> getAllDistinct();
public List<T> search(String searchTerm);
public T get(PK id);
public boolean exists(PK id);
public T save(T object);
public void remove(T object);
public void remove(PK id);
public List<T> findByNamedQuery(String queryName, Map<String, Object> queryParams);
public void reindex();
public void reindexAll(boolean async);
I have some model classes, services and methods.
Now I want to get list of object using some other fieled in the model class other than id(I have some common fields in many model classes).
I need to write similar methods in all the services and daos. So i was thinking is it possible to create a common method in generic dao.
The following I tried, but it didn't work.
public T getbyClientKey(Long clientkey) {
Session sess = getSession();
IdentifierLoadAccess byId = sess.byId(persistentClass);
List<T> entity = (List<T>) byId.load(clientkey);
if (entity == null) {
log.warn("Uh oh, '" + this.persistentClass + "' object with client '" + clientkey + "' not found...");
throw new ObjectRetrievalFailureException(this.persistentClass, clientkey);
}
return entity;
}
I knew this will be error. and it showed TypeCastingException, because return type of byId.load(id) is object only, not List.
So how can I create a method like that?
If so, I think I can create method for remove() also(But that's not necessary for me now, may be in future).
The Javadoc for IdentifierLoadAccess is pretty clear in how the load method should behave:
Return the persistent instance with the given identifier, or null if there is no such persistent instance.
This means it should return just one object, not a List of objects. Try casting it to T instead.
If you want to query your entity (that is, retrieve items by any other means than primary key), you most likely want to implement the search(String) method.
If you want to query your entity (that is, retrieve items by any other means than primary key), take a look at the UserDaoHibernate that is shipped with AppFuse. It contains a method loadUserByUsername() which is implemented like this:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List users = getSession().createCriteria(User.class).add(Restrictions.eq("username", username)).list();
if (users == null || users.isEmpty()) {
throw new UsernameNotFoundException("user '" + username + "' not found...");
} else {
return (UserDetails) users.get(0);
}
}
Obviously, if you want to return all items, it should be modified a bit (this one is made up):
public List<UserDetails> loadLockedUsers() {
List<UserDetails> users = (List<UserDetails>) getSession().createCriteria(User.class).add(Restrictions.eq("account_locked", true)).list();
return users;
}