使用 Morphia 及其 BasicDAO 更新实体
Update an entity with Morphia and its BasicDAO
我用 SpringBoot web 和 Morphia DAO 创建了一个 REST web 服务(使用 MongoDB)。
正如我在 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", 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")
);
}
}
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();
}
}
您 运行 遇到了 Morphia 的难题之一。根据您在上面发布的代码,您应该查看合并方法 here.
要记住的重要一点是,这不是深度合并,只是顶级字段,如果您有复杂的数据对象,这可能无济于事。
它基本上是这样工作的:
T Entity -> Map 然后获取地图和 运行s 对非空字段的递归更新,如下所示:update({_id:@Id-field},{$set:mapOfEntityFields} )
适用 T 实体的标准转换规则 ->
地图,就像它为保存所做的那样。
对于任何通用实体的深度合并,您需要使用自定义方法自行处理。
This 是另一个关于使用 org.codehaus.jackson.map.ObjectMapper 在复杂实体上使用 Spring 中的 JSON 部分处理深度合并的问题的一个很好的例子。它应该很容易适应您的问题。
如果这些都不能帮助您,请在我的回答中发表评论,我们可以制定出适合您的自定义递归方法。希望对您有所帮助。
我用 SpringBoot web 和 Morphia DAO 创建了一个 REST web 服务(使用 MongoDB)。
正如我在 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", 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")
);
}
}
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();
}
}
您 运行 遇到了 Morphia 的难题之一。根据您在上面发布的代码,您应该查看合并方法 here.
要记住的重要一点是,这不是深度合并,只是顶级字段,如果您有复杂的数据对象,这可能无济于事。
它基本上是这样工作的:
T Entity -> Map 然后获取地图和 运行s 对非空字段的递归更新,如下所示:update({_id:@Id-field},{$set:mapOfEntityFields} )
适用 T 实体的标准转换规则 -> 地图,就像它为保存所做的那样。
对于任何通用实体的深度合并,您需要使用自定义方法自行处理。
This 是另一个关于使用 org.codehaus.jackson.map.ObjectMapper 在复杂实体上使用 Spring 中的 JSON 部分处理深度合并的问题的一个很好的例子。它应该很容易适应您的问题。
如果这些都不能帮助您,请在我的回答中发表评论,我们可以制定出适合您的自定义递归方法。希望对您有所帮助。