使用 Morphia 及其 BasicDAO 更新实体

max*_*ime 5 java rest morphia spring-boot

我使用 SpringBoot Web 和 Morphia DAO(以使用 MongoDB)创建一个 REST Web 服务。

正如我在 MySQL 上使用 Hibernate 时所做的那样,我想使用通用实体、存储库和端点,这样我只需设置实体、继承存储库和服务,并通过 REST 调用使用生成的 CRUD。

快完成了,但我在使用 Morphia 进行实体的通用更新时遇到了问题。到目前为止,我所看到的所有内容都涉及手动设置带有必须更改的字段的请求;但在 Hibernate 方式中,我们只需设置 Id 字段,调用 persist(),它就会自动知道发生了什么变化并在数据库中应用更改。

这是一些代码。

基础实体.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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'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")
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

用户服务.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

RestResource.java(我的基本 Rest 端点)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

小智 3

您遇到了 Morphia 的难题之一。根据您上面发布的代码,您应该在此处查看合并方法。

要记住的重要一点是,这不是深度合并,只是顶级字段,如果您有复杂的数据对象,这可能没有帮助。

它本质上是这样工作的:

T Entity -> Map,然后获取映射并对非空字段运行递归更新,如下所示: update({_id:@Id-field},{$set:mapOfEntityFields})

T Entity 的标准转换规则适用 -> Map,就像保存一样。

对于任何通用实体的深度合并,需要您使用自定义方法自行处理。

是另一个关于使用 org.codehaus.jackson.map.ObjectMapper 在 Spring 中使用 JSON 部分对复杂实体进行深度合并的问题的一个很好的例子。它应该很容易适应您的问题。

如果这些都不能帮助您,请在我的答案中发表评论,我们可以制定出适合您的自定义递归方法。希望有帮助。