Update an entity with Morphia and its BasicDAO

I am doing a REST web service with a SpringBoot network and Morphia DAO (for using MongoDB).

As with Hibernate in MySQL, I would like to use common entities, repositories, and endpoints, so that I just need to install my entities, inherit repositories and services, and use the generated CRUDs with REST calls.

It is almost done, but I ran into a problem with a general update of my objects with Morphia. All that I have seen so far is about manually setting up a query with fields that need to be changed; but in Hibernate mode, we just set up an Id field called persist (), and it will automatically recognize that it has changed and applied the changes to the database.

Here is the code.

BaseEntity.java

package org.beep.server.entity;

import org.mongodb.morphia.annotations.Entity;

@Entity
abstract public class BaseEntity {
    public static class JsonViewContext {
        public interface Summary {}
        public interface Detailed extends Summary{}
    }

    protected String id;

    public void setId(String id) {
        this.id = id;
    }
}

User.java ( )

package org.beep.server.entity;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonView;
import lombok.*;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import org.mongodb.morphia.annotations.*;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.ws.rs.FormParam;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;

@Entity
@Indexes(
        @Index(value="identifier", fields=@Field("email"))
)

@Builder
@NoArgsConstructor
@AllArgsConstructor
final public class User extends BaseEntity {

    /**
     * User id
     */
    @Id
    @JsonView(JsonViewContext.Summary.class)
    private String id;

    /**
     * User email address
     */
    @Getter @Setter
    @JsonView(JsonViewContext.Summary.class)
    @FormParam("email")
    @Indexed

    @Email
    private String email;

    /**
     * User hashed password
     */
    @Getter
    @JsonView(JsonViewContext.Detailed.class)
    @FormParam("password")

    @NotEmpty
    private String password;

    /**
     * Sets the password after having hashed it
     * @param clearPassword The clear password
     */
    public void setPassword(String clearPassword) throws NoSuchAlgorithmException, InvalidKeySpecException {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        String hashedPassword = encoder.encode(clearPassword);
        setHashedPassword(hashedPassword);
    }

    /**
     * Directly sets the hashed password, whithout hashing it
     * @param hashedPassword The hashed password
     */
    protected void setHashedPassword(String hashedPassword) {
        this.password = hashedPassword;
    }

    /**
     * Converts the user to a UserDetail spring instance
     */
    public UserDetails toUserDetails() {
        return new org.springframework.security.core.userdetails.User(
                getEmail(),
                getPassword(),
                true,
                true,
                true,
                true,
                AuthorityUtils.createAuthorityList("USER")
        );
    }
}

EntityRepository.java ( , Morphia)

package org.beep.server.repository;

import org.beep.server.entity.BaseEntity;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

public class EntityRepository<Entity extends BaseEntity> extends BasicDAO<Entity, ObjectId> {

    @Autowired
    protected EntityRepository(Datastore ds) {
        super(ds);
    }
}

UserRepository.java ( )

package org.beep.server.repository;

import org.beep.server.entity.User;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.dao.BasicDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepository extends EntityRepository<User> {

    @Autowired
    protected UserRepository(Datastore ds) {
        super(ds);
    }

}

EntityService.java ( , Rest)

package org.beep.server.service;

import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.EntityRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.query.Query;
import org.mongodb.morphia.query.UpdateOperations;

import java.util.List;

public abstract class EntityService<Entity extends BaseEntity, Repository extends EntityRepository> implements ServiceInterface<Entity> {

    protected Repository repository;

    public EntityService(Repository repository) {
        this.repository = repository;
    }

    /**
     * {@inheritDoc}
     */
    public Entity create(Entity entity) throws UserEmailAlreadyExistsException {
        repository.save(entity);
        return entity;
    }

    /**
     * {@inheritDoc}
     */
    public void delete(String id) throws EntityNotFoundException {
        //repository.deleteById(id).;
    }

    /**
     * {@inheritDoc}
     */
    public List<Entity> findAll() {
        return repository.find().asList();
    }

    /**
     * {@inheritDoc}
     */
    public Entity findOneById(String id) throws EntityNotFoundException {
        return (Entity) repository.get(id);
    }

    /**
     * {@inheritDoc}
     */
    public Entity update(String id, Entity entity) {

        // Try to get the old entity, and to set the Id on the inputed one
        // But how can I merge the two ? If I persist like that, I will just have the modified fields, others
        // will be set to null...
        Entity oldEntity = (Entity) repository.get(id);
        entity.setId(id);
        repository.save(entity);

        // Create update operations works, but I have to set the changing fields manually...
        // not so compatible with generics !

        /*final Query<Entity> updateSelection = repository.createQuery().filter("_id",id);
        repository.createUpdateOperations().

        repository.update(updateSelection,entity);*/
        return entity;
    }
}

UserService.java

package org.beep.server.service;

import org.beep.server.entity.Message;
import org.beep.server.entity.User;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.repository.UserRepository;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Key;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.ws.rs.BadRequestException;
import java.util.List;
import java.util.Optional;

@Service
public class UserService extends EntityService<User, UserRepository> {

    @Autowired
    public UserService(UserRepository repository) {
        super(repository);
    }
}

RestResource.java ( )

package org.beep.server.api.rest.v1;

import com.fasterxml.jackson.annotation.JsonView;
import org.beep.server.entity.BaseEntity;
import org.beep.server.entity.User;
import org.beep.server.entity.BaseEntity;
import org.beep.server.exception.EntityNotFoundException;
import org.beep.server.exception.UserEmailAlreadyExistsException;
import org.beep.server.service.EntityService;
import org.beep.server.service.ServiceInterface;
import org.beep.server.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;

public class RestResource<Entity extends BaseEntity, Service extends EntityService> {

    protected Service service;

    // Default constructor private to avoid blank constructor
    protected RestResource() {
        this.service = null;
    }

    /**
     * Creates an object
     * @param object Object to create
     * @return The newly created object
     */
    @RequestMapping(method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.CREATED)
    @JsonView(BaseEntity.JsonViewContext.Detailed.class)
    Entity create(@RequestBody @Valid Entity object) throws UserEmailAlreadyExistsException {
        return service.create(object);
    }

    /**
     * Deletes an object from its id
     * @param id Object to delete id
     * @return The deleted object
     * @throws EntityNotFoundException
     */
    @RequestMapping(value = "{id}", method = RequestMethod.DELETE)
    @JsonView(BaseEntity.JsonViewContext.Detailed.class)
    User delete(@PathVariable("id") String id) throws EntityNotFoundException {
        service.delete(id);
        return new User();
    }

    /**
     * Gets all the objects
     * @return All the objects
     */
    @RequestMapping(method = RequestMethod.GET)
    @JsonView(BaseEntity.JsonViewContext.Summary.class)
    List<Entity> findAll() {
        return service.findAll();
    }

    /**
     * Finds one object from its id
     * @param id The object to find id
     * @return The corresponding object
     * @throws EntityNotFoundException
     */
    @RequestMapping(value = "{id}", method = RequestMethod.GET)
    @JsonView(BaseEntity.JsonViewContext.Detailed.class)
    Entity findById(@PathVariable("id") String id) throws EntityNotFoundException {
        return service.findOneById(id);
    }

    /**
     * Updates an object
     * @param object The object to update
     * @return The updated object
     */
    @RequestMapping(value = "{id}", method = RequestMethod.PUT)
    @JsonView(BaseEntity.JsonViewContext.Detailed.class)
    Entity update(@PathVariable String id, @RequestBody @Valid Entity object) {
        return service.update(id, object);
    }

    /**
     * Handles the EntityNotFound exception to return a pretty 404 error
     * @param ex The concerned exception
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public void handleEntityNotFound(EntityNotFoundException ex) {
    }

    /**
     * Handles the REST input validation exceptions to return a pretty 400 bad request error
     * with more info
     * @param ex The validation exception
     * @return A pretty list of the errors in the form
     */
    @ExceptionHandler
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public List<ObjectError> handleValidationFailed(MethodArgumentNotValidException ex) {

        // TODO : Check and improve the return of this method according to the front
        // The concept is to automatically bind the error dans the failed parameter
        return ex.getBindingResult().getAllErrors();
    }
}
+4
1

. , , .

, , , , , , .

:

T Entity → Map, : update ({_ id: @Id-field}, {$ set: mapOfEntityFields})

T Entity → , .

.

SO, JSON Spring org.codehaus.jackson.map.ObjectMapper. .

, , , . , .

+2

Source: https://habr.com/ru/post/1625502/


All Articles