title: Update an entity with Morphia and its BasicDAO
body: <p>I make a REST webservice with SpringBoot web and the Morphia DAO (to use MongoDB).</p>
  
  <p>As I did when I used Hibernate on MySQL, I would like to use Generic entities, repositories and endpoints so that I just have to set my entities, inherit Repositories and Services and let use the generated CRUD with REST calls.</p>
  
  <p>It almost done, but I encounter a problem with the generic update of my entities with Morphia. Everything I've seen so far talks about manually setting a request with the fields that have to change ; but in the Hibernate way, we just setted the Id field, called persist() and it automatically knowed what changed and applied changes in database.</p>
  
  <p>Here is some code.</p>
  
  <p><strong>BaseEntity.java</strong></p>
  
  <pre><code>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;
      }
  }
  </code></pre>
  
  <p><strong>User.java</strong> (one of my final entities)</p>
  
  <pre><code>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's id
       */
      @Id
      @JsonView(JsonViewContext.Summary.class)
      private String id;
  
      /**
       * User's email address
       */
      @Getter @Setter
      @JsonView(JsonViewContext.Summary.class)
      @FormParam("email")
      @Indexed
  
      @Email
      private String email;
  
      /**
       * User's 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")
          );
      }
  }
  </code></pre>
  
  <p><strong>EntityRepository.java</strong> (my base repository, inheriting from the Morphia one)</p>
  
  <pre><code>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&lt;Entity extends BaseEntity&gt; extends BasicDAO&lt;Entity, ObjectId&gt; {
  
      @Autowired
      protected EntityRepository(Datastore ds) {
          super(ds);
      }
  }
  </code></pre>
  
  <p><strong>UserRepository.java</strong> (my user repository)</p>
  
  <pre><code>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&lt;User&gt; {
  
      @Autowired
      protected UserRepository(Datastore ds) {
          super(ds);
      }
  
  }
  </code></pre>
  
  <p><strong>EntityService.java</strong> (the generic service, used from the Rest endpoints)</p>
  
  <pre><code>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&lt;Entity extends BaseEntity, Repository extends EntityRepository&gt; implements ServiceInterface&lt;Entity&gt; {
  
      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&lt;Entity&gt; 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&lt;Entity&gt; updateSelection = repository.createQuery().filter("_id",id);
          repository.createUpdateOperations().
  
          repository.update(updateSelection,entity);*/
          return entity;
      }
  }
  </code></pre>
  
  <p><strong>UserService.java</strong></p>
  
  <pre><code>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&lt;User, UserRepository&gt; {
  
      @Autowired
      public UserService(UserRepository repository) {
          super(repository);
      }
  }
  </code></pre>
  
  <p><strong>RestResource.java</strong> (my base Rest Endpoint)</p>
  
  <pre><code>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&lt;Entity extends BaseEntity, Service extends EntityService&gt; {
  
      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&lt;Entity&gt; 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&lt;ObjectError&gt; 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();
      }
  }
  </code></pre>