2016-01-22 91 views
4

我使用SpringBoot web和Morphia DAO(使用MongoDB)制作REST web服务。用Morphia及其BasicDAO更新实体

正如我在MySQL上使用Hibernate时所做的那样,我希望使用通用实体,存储库和端点,以便我只需设置实体,继承存储库和服务并让生成的CRUD与REST调用一起使用。

它几乎完成,但我遇到与Morphia的我的实体的通用更新的问题。到目前为止,我所见过的所有内容都是关于手动设置一个需要更改的字段的请求。但是在Hibernate方式中,我们只设置了Id字段,名为persist(),它自动知道在数据库中更改了哪些内容并应用了更改。

这是一些代码。

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", [email protected]("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") 
     ); 
    } 
} 

EntityRepository.java(我的基地仓库,从吗啡一个继承)

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(一般性服务,从休息端点使用)

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(我的基地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(); 
    } 
} 
+0

你能提供一个小例子来告诉你如何调用更新方法吗?特别是关于空值语义。 – jah

+0

确实,我想称之为PHP主义(和许多其他ORM)的方式: repository.persist(myObject); 如果它是一个新的对象(没有id) - ORM创建一个新行;如果它有一个id,那么ORM更新编辑的字段。 – maxime

回答

1

你碰上与吗啡的难题之一。根据您在上面发布的代码,您应该查看合并方法here

要记住的重要一点是,这不是一个深度合并,只有顶级字段,如果你有复杂的数据对象,这可能无济于事。

它在本质上是这样工作的:

牛逼实体 - >地图,然后需要的地图,并运行在像这样的非空字段递归更新:更新({_ ID:@标识场},{ $ set:mapOfEntityFields})

来自T Entity的标准转换规则apply - > 映射,就像保存一样。

对于任何泛型实体的深度合并,它都需要您自己使用自定义方法处理它。

This是一个很好的例子,它讲述了如何处理使用org.codehaus.jackson.map.ObjectMapper的复杂实体在Spring中使用JSON partials进行深层合并。它应该很容易适应你的问题。

如果这些都不能帮助您,请在我的回答中留言,我们可以制定出适合您的自定义递归方法。希望有所帮助。