我正在使用Spring-Boot 2.5.0和MongoDB来持久化一些文档。这里是Github项目。
对于每个文档,我还需要自动保存一些审计信息,因此我扩展了以下类:
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.*;
import java.time.Instant;
@Data
@SuperBuilder
@NoArgsConstructor
@AllArgsConstructor
public abstract class AuditingDocument {
@Version
private Long version;
@CreatedBy
private String creator;
@CreatedDate
private Instant created;
@LastModifiedBy
private String modifier;
@LastModifiedDate
private Instant modified;
}
例如。让我们考虑Book
类:
@Data
@SuperBuilder
@Document
@NoArgsConstructor
@AllArgsConstructor
public class Book extends AuditingDocument {
@Id
private String id;
private String name;
}
我遇到的问题是,当我通过JSON更新文档时,我能够更改/覆盖CreatedBy和CreatedDate字段的值。
这意味着,如果未提供字段,则结果值将保存为null,否则,它将为创建者和创建的字段保存新值。
这不应该被允许,因为它在大多数用例中代表了一个安全问题。如何使这两个字段不可更新?如果创建者存在,则无需稍后更新它。此类值会自动填充,因此不会出现需要更新值的错误。
我发现了其他类似的问题,但它们是关于JPA而不是MongoDB,例如。
他们在这里使用
@Column(name = "created_by", updatable = false)
保护字段不受更新影响。不幸的是,MongoDB的字段没有这样的属性。
在数据库中已经存在这些字段之后,如何保护这些字段不被修改?显然,我需要一个能够扩展所有文档实体的解决方案,而无需单独处理每个实体,例如从DB手动读取并修复要首先保存的文档。
更新
我试图通过覆盖MongoTemboard
子类中的doUpdate
方法来实现这种行为。
public class CustomMongoTemplate extends MongoTemplate {
public CustomMongoTemplate(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
super(mongoDbFactory);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
super(mongoDbFactory, mongoConverter);
}
@Override
protected UpdateResult doUpdate(String collectionName, Query query, UpdateDefinition update, Class<?> entityClass, boolean upsert, boolean multi) {
Document updateDocument = update.getUpdateObject();
List<?> list = this.find(query, entityClass);
if (!list.isEmpty()) {
Object existingObject = list.get(0);
Document existingDocument = new Document();
this.getConverter().write(existingObject, existingDocument);
// Keep the values of the existing document
if (existingDocument.keySet().containsAll(Arrays.asList("version", "creator", "created"))) {
// Long version = existingDocument.getLong("version");
String creator = existingDocument.getString("creator");
Date created = existingDocument.getDate("created");
System.out.println("Creator: " + creator);
System.out.println("Created: " + created);
// updateDocument.put("version", version++);
updateDocument.put("creator", creator);
updateDocument.put("created", created);
System.out.println("Update Document");
System.out.println(updateDocument.toJson());
}
return super.doUpdate(collectionName, query, Update.fromDocument(updateDocument), entityClass, upsert, multi);
} else {
return super.doUpdate(collectionName, query, update, entityClass, upsert, multi);
}
}
}
这种方法部分有效,这意味着在我调用存储库的保存方法后,更新的对象不会覆盖现有的创建者和创建的字段,但是由于某种原因,保存方法返回一个创建者和创建的空值对象,即使在数据库中文档有这样的值。
我还尝试一次获取集合的所有文档,它们的值(creator、created)由APIendpoint正确填充和返回。似乎doUpdate()方法弄乱了一些东西,但我无法使用wath。
更新2
每个文档都使用实现此接口的Service保存在DB中,该服务只需调用MongoRepository的相应保存()
方法。
import org.apache.commons.collections4.IterableUtils;
import org.springframework.data.mongodb.repository.MongoRepository;
import java.util.List;
import java.util.Optional;
public interface EntityService<T, K> {
MongoRepository<T, K> getRepository();
default Optional<T> findById(K id) {
return this.getRepository().findById(id);
}
default List<T> findAll(){
return this.getRepository().findAll();
}
default List<T> findAllByIds(List<K> ids){
return IterableUtils.toList(this.getRepository().findAllById(ids));
}
default T save(T entity) {
return this.getRepository().save(entity);
}
default List<T> save(Iterable<T> entities) {
return this.getRepository().saveAll(entities);
}
default void delete(T entity) {
this.getRepository().delete(entity);
}
default void delete(Iterable<T> entity) {
this.getRepository().deleteAll(entity);
}
}
这是相应的存储库
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface BookRepository extends MongoRepository<Book, String>, QuerydslPredicateExecutor<Book> {}
更新3
RestController调用此方法,其中服务是上面定义的服务:
default T save(T entity) {
return this.convert(this.getService().save(this.decode(entity)));
}
这些是转换和解码方法:
@Override
public BookDTO convert(Book source) {
return BookDTO.builder()
.id(source.getId())
// Auditing Info
.version(source.getVersion())
.creator(source.getCreator())
.created(source.getCreated())
.modifier(source.getModifier())
.modified(source.getModified())
.build();
}
@Override
public Book decode(BookDTO target) {
return Book.builder()
.id(target.getId())
// Auditing Info
.version(target.getVersion())
// .creator(target.getCreator())
// .created(target.getCreated())
// .modifier(target.getModifier())
// .modified(target.getModified())
.build();
}
更新4
我刚刚创建了一个Spring Boot/Java 16 MWP,以便在GitHub上重现错误。
这是RestController:
@RestController
@RequiredArgsConstructor
public class BookController {
private final BookRepository bookRepository;
@PostMapping(value = "/book")
public Book save(@RequestBody Book entity) {
return this.bookRepository.save(entity);
}
@GetMapping(value = "/book/test")
public Book test() {
Book book = Book.builder().name("Book1").build();
return this.bookRepository.save(book);
}
@GetMapping(value = "/books")
public List<Book> books() {
return this.bookRepository.findAll();
}
}
如果我通过endpoint更新文档,则数据库中的文档会正确保存(使用现有创建者
但是,“/图书”会返回所有正确填充了所有字段的图书。
doUpdate方法和控制器返回之间似乎存在某种东西,将这些字段设置为null。
更新5
为了更好地检查BookRepository的保存方法,我创建了一些测试。
我发现:
version, creator,创建,修改,修改
)都按预期填充。创建者
和创建的
字段的现有值以供后续查找查询。以下是我的测试方法(也可在GitHub上获得)。
import com.example.demo.domain.Book;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;
@SpringBootTest
@Rollback
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class BookRepositoryTests {
@Autowired
private BookRepository bookRepository;
@Test
@Order(1)
@Transactional
public void testCreateBook() {
this.doCreateBook("1001", "Java Programming");
}
@Test
@Order(2)
@Transactional
public void testUpdateBookAndFind() {
this.doCreateBook("1002", "Python Programming");
Book existingBook = this.bookRepository.findById("1002").orElse(null);
// Check Existing Book
Assertions.assertNotNull(existingBook);
// Update
existingBook.setCreated(null);
existingBook.setCreator(null);
existingBook.setModifier(null);
existingBook.setModified(null);
this.bookRepository.save(existingBook);
Book existingUpdatedBook = this.bookRepository.findById("1002").orElse(null);
// Check Existing Updated Book (Working)
Assertions.assertNotNull(existingUpdatedBook);
Assertions.assertNotNull(existingUpdatedBook.getCreator());
Assertions.assertNotNull(existingUpdatedBook.getCreated());
Assertions.assertNotNull(existingUpdatedBook.getModifier());
Assertions.assertNotNull(existingUpdatedBook.getModified());
}
@Test
@Order(3)
@Transactional
public void testUpdateBookDirect() {
this.doCreateBook("1003", "Go Programming");
Book existingBook = this.bookRepository.findById("1003").orElse(null);
// Check Existing Book
Assertions.assertNotNull(existingBook);
// Update
existingBook.setCreated(null);
existingBook.setCreator(null);
existingBook.setModifier(null);
existingBook.setModified(null);
Book updatedBook = this.bookRepository.save(existingBook);
// Check Updated Book (Not working)
Assertions.assertNotNull(updatedBook);
Assertions.assertNotNull(updatedBook.getCreator());
Assertions.assertNotNull(updatedBook.getCreated());
Assertions.assertNotNull(updatedBook.getModifier());
Assertions.assertNotNull(updatedBook.getModified());
}
private void doCreateBook(String bookID, String bookName) {
// Create Book
Book book = Book.builder().id(bookID).name(bookName).build();
Book createdBook = this.bookRepository.save(book);
Assertions.assertNotNull(createdBook);
Assertions.assertEquals(bookID, createdBook.getId());
Assertions.assertEquals(bookName, createdBook.getName());
// Check Auditing Fields
Assertions.assertNotNull(createdBook.getVersion());
Assertions.assertNotNull(createdBook.getCreator());
Assertions.assertNotNull(createdBook.getCreated());
Assertions.assertNotNull(createdBook.getModifier());
Assertions.assertNotNull(createdBook.getModified());
}
}
在合成中,只有testUpdateBookDirect()方法的断言不起作用。似乎在CustomMongoTemplate之后有某种拦截器。doUpdate()覆盖这些字段的方法(creator,created)。
如果您不希望请求覆盖审计字段(或任何其他字段),那么一种方法是为您的数据模型和DTO使用不同的类,并在输入和输出的过程中从一个类转换到另一个类(Lombok构建器使这非常容易)。
虽然转换有一定的开销,再加上需要维护这两个类,但它确实将您的数据模型与endpoint面向公众的需求隔离开来。
e、 g.Java对枚举使用了SNAKE\u CASE,但出于某些疯狂的原因,您需要在API上使用kebab CASE。
或者,您有一个多租户服务,您必须在DB中持久化租户,但不需要或不想通过DTO公开它。
一种可能的解决方案或解决方法是:
存储库。save
方法为审核字段(creator
,created
)返回null,即使它们随后在数据库中正确填充这里我们需要覆盖MongoTemboard
的doUpdate
方法。
@Override
protected UpdateResult doUpdate(String collectionName, Query query, UpdateDefinition update, Class<?> entityClass, boolean upsert, boolean multi) {
Document updateDocument = update.getUpdateObject();
List<?> list = this.find(query, entityClass);
if (!list.isEmpty()) {
Object existingObject = list.get(0);
Document existingDocument = new Document();
this.getConverter().write(existingObject, existingDocument);
// Keep the values of the existing document
if (existingDocument.keySet().containsAll(Arrays.asList("version", "creator", "created"))) {
String creator = existingDocument.getString("creator");
Date created = existingDocument.getDate("created");
System.out.println("Creator: " + creator);
System.out.println("Created: " + created);
updateDocument.put("creator", creator);
updateDocument.put("created", created);
System.out.println("Update Document");
System.out.println(updateDocument.toJson());
}
return super.doUpdate(collectionName, query, Update.fromDocument(updateDocument), entityClass, upsert, multi);
} else {
return super.doUpdate(collectionName, query, update, entityClass, upsert, multi);
}
}
最后,我使用一个调用存储库的服务来执行保存和查找操作。这是它实现的接口。
public interface EntityService<T extends MongoDBDocument<K>, K> {
MongoRepository<T, K> getRepository();
default T save(T entity) {
// First save it
this.getRepository().save(entity);
// Then find it by ID
return this.getRepository().findById(entity.getId()).orElse(entity);
}
default List<T> save(Iterable<T> entities) {
// First save them
List<T> savedEntities = this.getRepository().saveAll(entities);
List<K> savedEntitiesIDs = savedEntities.stream().map(entity -> entity.getId()).collect(Collectors.toList());
// Then find them by IDs
return IterableUtils.toList(this.getRepository().findAllById(savedEntitiesIDs));
}
}
通过这种方式,我能够做我正在寻找的事情:
我在spring data jdbc中使用简单的crud操作和审计。因此,当我插入新记录时,审计工作完全正常。但是当我更新我的记录时,我的createdby和createddate设置为null。有什么方法可以避免修改这些列吗? 注意:column(可更新= false)特性不支持spring data jdbc。 我的审计实体如下所示: 以及使用@EnableJdbcAuditing和定义下面的
我使用的是spring boot,因此我没有使用任何xml文件进行配置。我要做的是,在使用MongoRepositories保存数据时,启用OngOAuditing以保存createdDate、lastModifiedDate等。 我的模特课 我使用@CreateDate注释来保存createDate。我对DateTime使用了jodatime依赖项 Spring-data-mongoDB也添加在
我正在使用Spring Mongo审计和@CreatedDate@CreatedBy不工作,但@LastModifiedDate和@LastModifiedBy工作正常。 我在配置类上添加了@EnableMongoAudting,并定义了AuditAware。 审核类别为: 当我保存文档时,它在createdOn和createdBy中都设置为null,但在modifiedOn和modifiedBy
我有一个MongoDb数据库,其中包含一个名为的集合。在中,我希望通过属性搜索特定对象。我想更新找到的对象中的数组内容。例如,最初,在向该endpoint发出请求之前,所需的数据如下所示: 发出类似的请求后,mongodb中的数据应更新为 这是我的文件,为了执行它,注释中的代码应该是什么?
问题内容: 某些数据库功能(例如和)很容易受到死锁的影响,因为数据库未指定将使用哪种锁定顺序。我发现有两次 讨论暗示此行为不是SQL标准指定的,更不用说具体的实现了。因此,我在假设我们无法控制锁定顺序的情况下进行操作(至少,这样做并不明显)。 如果我们不能依赖锁定顺序,应该如何避免数据库死锁? 如果我们不应该避免僵局(您将不得不非常努力地说服我),那么我们应该怎么做? 这个问题与数据库无关,所以请
例如,我可以运行这个mongo查询: 我们如何用代码和spring MongoTemplate来实现这一点呢?例如,下面是UPDATE一个值的代码: Update#set方法似乎只接受一个(key、value),而不接受多值或值列表。