spring boot mongodb audit gives duplicate collection error. I'm trying to create dateCreate and dateUpdate fields when I insert and update a collection but when updated it gives the error:
org.springframework.dao.DuplicateKeyException: Write operation error
on server user.domain.com:27017. Write error: WriteError{code=11000,
message='E11000 duplicate key error collection: springboot.category
index: id dup key: { _id: "21" }', details={}}.
the execution is duplicating the key, below is my structure
My class AuditingConfig.java:
Configuration
#EnableMongoAuditing
public class AuditingConfig {
#Bean
public AuditorAware<String> myAuditorProvider() {
return new AuditorAwareImpl();
}
}
My class AuditMetadata.java:
#Setter
#Getter
public class AuditMetadata {
#CreatedDate
private LocalDateTime createdDate;
#LastModifiedDate
private LocalDateTime lastModifiedDate;
#Version
private Long version;
// #CreatedBy
// private String createdByUser;
// #LastModifiedBy
// private String modifiedByUser;
//...getters and setters omitted
}
My class AuditorAwareImpl.java:
public class AuditorAwareImpl implements AuditorAware<String> {
#Override
public Optional<String> getCurrentAuditor() {
return Optional.of("Admin");
}
}
My class entity CategoryMongo.java:
Getter
#Setter
#NoArgsConstructor
#Document(collection = "category")
public class CategoryMongo extends AuditMetadata{
#Id
#JsonProperty("category_id")
private String category_id;
#JsonProperty("id_colletion")
private String emberId;
public String getEmberId() {
return category_id;
}
#JsonProperty("category_name")
private String name;
#JsonProperty("category_active")
private ProductEnum active = ProductEnum.ativo;
#JsonProperty("category_slug")
private String slug;
// #JsonProperty("category_updateAt")
// #LastModifiedDate
// private Date updateAt;
// #JsonProperty("category_createdAt")
// #CreatedDate
// private Date createdAt;
}
My method save:
CategoryMongo catm = new CategoryMongo();
catm.setName(category.getName());
catm.setSlug(category.getSlug());
catm.setActive(category.getActive());
catm.setCategory_id(category.getCategory_id().toString());
catm.setEmberId(category.getCategory_id().toString());
categoryRepositoryMongo.save(catm);
SOLVED
I solved the error I use in the document an interface follows the updated classes:
class AuditMetadata.java:
#Setter
#Getter
public class AuditMetadata {
#CreatedDate
private LocalDateTime createdDate;
#LastModifiedDate
private LocalDateTime lastModifiedDate;
#Version
private Long version;
protected boolean persisted;
// #CreatedBy
// private String createdByUser;
// #LastModifiedBy
// private String modifiedByUser;
//...getters and setters omitted
}
class Document CategoryMongo.java:
#Getter
#Setter
#NoArgsConstructor
#Document(collection = "category")
public class CategoryMongo extends AuditMetadata implements Persistable<String>{
#Id
#JsonProperty("category_id")
private String category_id;
#JsonProperty("id_colletion")
private String emberId;
public String getEmberId() {
return category_id;
}
#JsonProperty("category_name")
private String name;
#JsonProperty("category_active")
private ProductEnum active = ProductEnum.ativo;
#JsonProperty("category_slug")
private String slug;
#Override
#Nullable
public String getId() {
return category_id;
}
#Override
public boolean isNew() {
return !persisted;
}
// #JsonProperty("category_updateAt")
// #LastModifiedDate
// private Date updateAt;
// #JsonProperty("category_createdAt")
// #CreatedDate
// private Date createdAt;
}
save method:
CategoryMongo catm = new CategoryMongo();
catm.setName(category.getName());
catm.setSlug(category.getSlug());
catm.setActive(category.getActive());
catm.setCategory_id(category.getCategory_id().toString());
catm.setPersisted(true);
categoryRepositoryMongo.save(catm);
But something happens that I didn't want to happen: the #CreatedDate field disappears when I update it and only #LastModifiedDate appears in the result. if anyone knows how to solve this post here
Related
I'm using java validation API to validate fields in my Note class:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "note")
public class Note {
#Id
#Column(name = "id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "date", columnDefinition = "DATE")
private LocalDate date;
#NotBlank(message = "Enter a topic")
#Column(name = "topic")
private String topic;
#NotBlank(message = "Content can't be empty")
#Column(name = "content")
private String content;
#Column(name = "type")
private NoteType noteType;
#NotNull
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
#JoinColumn(name = "user_id")
#JsonIgnore
private User user;
}
NoteService:
#Service
#AllArgsConstructor
public class NoteService {
#Autowired
private NoteRepository noteRepository;
#Autowired
private UserRepository userRepository;
public void addNote(#Valid Note note) {
note.setUser(getLoggedInUser());
if (validateNote(note)) {
noteRepository.save(note);
}
}
public List<Note> getNotes() {
return getLoggedInUser().getNotes();
}
public Note editNote(Note newNote, Long id) {
noteRepository.editNoteById(newNote, id);
return newNote;
}
public List<Note> getNotesByTopic(String topic) {
List<Note> notes = noteRepository.getNotesByTopicAndUser(topic, getLoggedInUser());
return notes;
}
public boolean validateNote(Note note) {
return validateNoteType(note.getNoteType())
&& note.getDate() != null;
}
public boolean validateNoteType(NoteType type) {
return type.equals(NoteType.NOTE)
|| type.equals(NoteType.SKILL);
}
public User getLoggedInUser() {
return userRepository.findByEmail(SecurityContextHolder.getContext().getAuthentication().getName());
}
}
Test:
#ExtendWith(MockitoExtension.class)
#ExtendWith(SpringExtension.class)
class NoteServiceTest {
#Mock
private NoteRepository noteRepositoryMock;
#Mock
private UserRepository userRepositoryMock;
#Mock
SecurityContext mockSecurityContext;
#Mock
Authentication authentication;
private NoteService noteService;
#BeforeEach
void setUp() {
noteService = new NoteService(noteRepositoryMock, userRepositoryMock);
Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(authentication);
SecurityContextHolder.setContext(mockSecurityContext);
}
#Test
void shouldAddNote() {
LocalDate date = LocalDate.now();
Note note = new Note(0L, date, "test", "", NoteType.NOTE, null);
noteService.addNote(note);
Mockito.verify(noteRepositoryMock).save(note);
}
}
The field user in the Note class is annotated with #NotNull and I'm passing a null user to this note but the note is still getting saved. Same thing when I pass an empty string. Any idea why that is happening? I'm new to unit testing
I'm new to unit testing - your perfectly valid question has nothing to do with unit testing.
#NotNull does nothing on it own. Its actually a contract stating the following:
A data member (or anything else annotated with #NotNull like local variables, and parameters) can't be should not be null.
For example, instead of this:
/**
* #param obj should not be null
*/
public void MyShinyMethod(Object obj)
{
// Some code goes here.
}
You can write this:
public void MyShinyMethod(#NotNull Object obj)
{
// Some code goes here.
}
P.S.
It is usually appropriate to use some kind of annotation processor at compile time, or something that processes it at runtime. But I don't really know much about annotation processing. But I am sure Google knows :-)
You need to activate validation on you service class with the #Validated annotation so the validation of parameters kicks in.
#Service
#AllArgsConstructor
#Validated
public class NoteService {
...
See Spring #Validated in service layer and Spring Boot: How to test a service in JUnit with #Validated annotation? for more details.
If for some reason you need to manually perform the validation you can always do something like this:
#Component
public class MyValidationImpl {
private final LocalValidatorFactoryBean validator;
public MyValidationImpl (LocalValidatorFactoryBean validator) {
this.validator = validator;
}
public void validate(Object o) {
Set<ConstraintViolation<Object>> set = validator.validate(o);
if (!set.isEmpty()) {
throw new IllegalArgumentException(
set.stream().map(x -> String.join(" ", x.getPropertyPath().toString(), x.getMessage())).collect(
Collectors.joining("\n\t")));
}
}
}
So your noteRepository is Mocked, so you it's not actually calling save on your repository.
Mockito.verify(noteRepositoryMock).save(note);
All you are verifying here is that a call to save is made, not that it was successful.
I am creating an application with embedded review documents inside Course documents with Spring Data Rest and MongoDB but I am unable to get reviews for a course. Here is my controller:
#Controller
#RequestMapping("/courses")
public class CourseController {
private final CourseRepository courseRepository;
public CourseController(CourseRepository courseRepository) {
this.courseRepository = courseRepository;
}
#PatchMapping("/add-review")
public List<Review> addReview(#RequestBody AddReviewDto addReviewDto) {
Course course = courseRepository.findById(addReviewDto.getCourseId()).get();
Review review = new Review(new ObjectId().toString(), addReviewDto.getReview());
List<Review> reviews = course.getReviews();
reviews.add(review);
course.setReviews(reviews);
return courseRepository.save(course).getReviews();
}
#GetMapping("/{id}/reviews")
public List<Review> getAllReviewsForCourse(#PathVariable String id) {
Course course = courseRepository.findById(id).get();
return course.getReviews();
}
}
Here is the Course model:
#Getter
#Setter
#Document(collection = "courses")
#AllArgsConstructor
#NoArgsConstructor
public class Course {
public Course(#NotNull String code, #NotNull String name,
#NotNull String type, List<Review> reviews) {
this.code = code;
this.name = name;
this.type = type;
this.reviews = reviews;
}
#Id
private String id;
#NotNull
private String code;
#NotNull
private String name;
#NotNull
private String type;
private List<Review> reviews = new ArrayList<>();
}
And Review model:
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
public class Review {
#Id
private String id;
private String reviewText;
private String userName;
private String userId;
public Review(String id, Review other) {
this.id = id;
this.reviewText = other.reviewText;
this.userId = other.userId;
this.userName = other.userName;
}
}
When I send a request to http://localhost:8888/courses/605dc41f54beac4412cabadc, I successfully get reviews inside the course object as follows:
{
"code": "CS 101",
"name": "Introduction to Programming",
"type": "Lecture",
"reviews": [
{
"reviewText": "dfgsfgdgdg",
"userName": "yigit",
"userId": "604a9382777a83b08307c7e8"
}
]
}
But when I try to send the request to localhost:8888/courses/605dc41f54beac4412cabadc/reviews, I get 404 not found.
I debugged my code and seen that the code is running the correct controller, finding the course object and its reviews are visible in debugger but when I return course.getReviews(), it doesnt work.
You should be sending to http://localhost:8888/courses/605dc41f54beac4412cabadc/reviews
Try to open this URL in your browser
of course you get 404. Because your #RequestMapping is "/courses". try send the request to http://localhost:8888/courses/{id}/reviews
Turns out, I have used #Controller instead of #RestController...
I've dynamoDB database, in which I've a table named user. I want to log auditing (CreatedBy, LastModifiedBy, CreatedDate, LastModifiedDate)in this table.
I've found JPA auditing (from here) and MongoDB auditing with annotations #EnableJpaAuditing and #EnableMongoAuditing respectively. But obviously they are not working with dynamoDB.
Here is my abstract class for auditing:
#DynamoDBDocument
public abstract class PQSSAbstractAuditingEntity implements Serializable{
#CreatedBy
#JsonIgnore
#DynamoDBAttribute
private String createdBy;
#LastModifiedBy
#JsonIgnore
#DynamoDBAttribute
private String lastModifiedBy;
#CreatedDate
#JsonIgnore
#DynamoDBAttribute
private Date createdDate;
#LastModifiedDate
#JsonIgnore
#DynamoDBAttribute
private Date lastModifiedDate = new Date();
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public void setLastModifiedBy(String lastModifiedBy) {
this.lastModifiedBy = lastModifiedBy;
}
public Date getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Date createdDate) {
this.createdDate = createdDate;
}
public Date getLastModifiedDate() {
return lastModifiedDate;
}
public void setLastModifiedDate(Date lastModifiedDate) {
this.lastModifiedDate = lastModifiedDate;
}
}
I've assigned dates to respective fields but I've to set createdBy and lastModifiedBy depending on the logged in user. So I've to fetch the dynamically at runtime, when ever new entry is added into the database.
I know how to set these fields statically but the problem is how to make annotations aware of these changes at run time.
As I mentioned, I've found AuditAware for JPA and mongo. I need the same for dynamoDB.
Any help will be appreciated. As I'm new to Spring boot.
The question is already some years old but if somebody has also that problem, following solution works..
The problem is, that #EnableDynamoDBAuditing and #EnableDynamoDBRepositories don't work properly together. To solve this problem you have to add both annotations to you configuration class, create AuditorAware and DateTimeProvider beans and add all your entities/documents to your DynamoDBMappingContext manually.
PersistenceConfiguration.java
#Configuration
#EnableDynamoDBAuditing(auditorAwareRef = "userAuditing", dateTimeProviderRef = "dateAuditing")
#EnableDynamoDBRepositories(basePackages = "your.repository.package.name")
public class PersistenceConfiguration {
#Bean
public AuditorAware<String> userAuditing() {
return () -> Optional.of("TestUser"); //get username from SecurityContext
}
#Bean
public DateTimeProvider dateAuditing() {
return CurrentDateTimeProvider.INSTANCE;
}
#Bean
public DynamoDBMappingContext dynamoDBMappingContext() {
DynamoDBMappingContext mappingContext = new DynamoDBMappingContext();
//add your 'entities' manually
mappingContext.getPersistentEntity(YourEntity.class);
return mappingContext;
}
// do further configuration stuff...
}
YourEntity.java
#DynamoDBTable(tableName = "YourEntity")
public class YourEntity {
#CreatedDate
#DynamoDBAttribute
#DynamoDBTypeConverted(converter = LocalDateTimeConverter.class)
private LocalDateTime createdOn;
#CreatedBy
#DynamoDBAttribute
private String createdBy;
#LastModifiedDate
#DynamoDBAttribute
#DynamoDBTypeConverted(converter = LocalDateTimeConverter.class)
private LocalDateTime updatedOn;
#LastModifiedBy
#DynamoDBAttribute
private String updatedBy;
// add further properties...
}
I know that there are some other solutions like #DynamoDBAutoGeneratedTimestamp and the usage of their strategies, but in my mind that's the cleanest solution regarding the use of spring.
The annotaion #DynamoDBAutoGeneratedTimestamp can be used along with DynamoDBAutoGenerateStrategy to audit the item.
Strategy CREATE (Use this for Create audit):-
#DynamoDBAutoGeneratedTimestamp(strategy=DynamoDBAutoGenerateStrategy.CREATE)
public Date getCreatedDate() { return createdDate; }
public void setCreatedDate(Date createdDate) { this.createdDate = createdDate; }
Strategy ALWAYS (Use this if you want last modified date):-
#DynamoDBAutoGeneratedTimestamp(strategy=DynamoDBAutoGenerateStrategy.ALWAYS)
public Date getLastUpdatedDate() { return lastUpdatedDate; }
public void setLastUpdatedDate(Date lastUpdatedDate) { this.lastUpdatedDate = lastUpdatedDate; }
If you want both create timestamp and last modified timestamps, please create two different attributes. One attribute should use CREATE strategy and other one should use ALWAYS strategy.
AutoGeneratedTimeStamp
I am using jackson-dataformat-xml.
I have the following classes:
public class CTHotel {
#JacksonXmlProperty(localName = "basic-info")
private HotelBaseInfo hotelBaseInfo;
//other properties and getters and setters
}
public class HotelBaseInfo {
#JacksonXmlProperty(localName = "hotel-name")
private String hotelName;
#JacksonXmlElementWrapper(localName = "hotel-amenities")
private List<HotelAmenity> hotelAmenities;
//other properties and getters/setters
}
public class HotelAmenity {
private String category;
private String description;
#JacksonXmlElementWrapper(localName = "amenities")
private List<String> amenities;
//other properties and getters/setters
}
My XML is this:
<hotels>
<hotel>
<basic-info>
<hotel-name>Hotel XYZ</hotel-name>
<hotel-amenities>
<hotel-amenity>
<category>F&B</category>
<description>Random Text</description>
<amenities>
<amenity>Cafe</amenity>
<amenity>Bar</amenity>
<amenity>Rastaurant</amenity>
</amenities>
</hotel-amenity>
<hotel-amenity>
...
</hotel-amenity>
</hotel-amenities>
</basic-info>
</hotel>
<hotel>
...
</hotel>
</hotels>
My question is, how can I map amenities as list of strings in my HotelAmenity class as mentioned above ? What annotation should I use on amenities field ?
#JacksonXmlElementWrapper annotation on hotelAmenities field of Hotel class is working just fine.
I get the below error while mapping :
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.lang.String out of START_OBJECT token
at [Source: java.io.StringReader#507bcc81; line: 3, column: 1039] (through reference chain: com.example.response.HotelSearchResponse["hotels"]->java.util.ArrayList[2]->com.example.response.CTHotel["basic-info"]->com.example.response.HotelBaseInfo["hotel-amenities"]->java.util.ArrayList[1]->com.example.response.HotelAmenity["amenities"]->java.util.ArrayList[9])
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:148) ~[jackson-databind-2.6.5.jar:2.6.5]
at com.fasterxml.jackson.databind.DeserializationContext.mappingException(DeserializationContext.java:857) ~[jackson-databind-2.6.5.jar:2.6.5]
...
Here's the code, that I hope would answer your question:
/**Class Hotels*/
#JacksonXmlRootElement(localName = "hotels")
public class Hotels {
#JacksonXmlElementWrapper(useWrapping = false)
private List<Hotel> hotel;
//Other getters and setters
}
/**Class Hotel*/
#JacksonXmlRootElement(localName = "hotel")
public class Hotel {
#JacksonXmlProperty(localName = "basic-info")
private HotelBaseInfo hotelBaseInfo;
//Other getters and setters
}
/**Class HotelBaseInfo*/
public class HotelBaseInfo {
#JacksonXmlProperty(localName = "hotel-name")
private String hotelName;
#JacksonXmlElementWrapper(localName = "hotel-amenities")
private List<HotelAmenity> hotelAmenities;
//Other getters and setters
}
/**Class HotelAmenity*/
public class HotelAmenity {
private String category;
private String description;
#JacksonXmlElementWrapper(localName = "amenities")
private List<Amenities> amenity;
static class Amenities {
#JacksonXmlText
private String value;
}
//Other getters and setters
}
Here's what worked for me:
public class JacksonXmlParsing {
#JacksonXmlRootElement(localName = "hotels")
static class HotelSearchResponse {
#JacksonXmlElementWrapper(localName = "hotel")
private List<CTHotel> hotels;
//other properties and getters and setters
}
static class CTHotel {
#JacksonXmlProperty(localName = "hotel-name")
private String hotelName;
#JacksonXmlElementWrapper(localName = "hotel-amenities")
private List<HotelAmenity> hotelAmenities;
//other properties and getters and setters
}
static class HotelAmenity {
private String category;
private String description;
#JacksonXmlElementWrapper
private List<String> amenities;
//other properties and getters/setters
}
public static void main(String[] args) throws IOException {
XmlMapper xmlMapper = new XmlMapper();
File file = new File("./src/main/resources/file.xml");
HotelSearchResponse response = xmlMapper.readValue(file, HotelSearchResponse.class);
System.out.println(response);
}
}
Output:
HotelSearchResponse(hotels=[CTHotel(hotelName=Hotel XYZ, hotelAmenities=[HotelAmenity(category=F&B, description=Random Text, amenities=[Cafe, Bar, Rastaurant])])])
But basic-info tag is missed, I could find out why.
If the createdBy references to a document with unique indexes, it fails throwing dup key error.
AbstractDocument.java
public abstract class AbstractDocument implements Auditable<User, String> {
#Version
private Long version;
#Id
private String id;
private User createdBy;
private DateTime createdDate;
private User lastModifiedBy;
private DateTime lastModifiedDate;
}
User.java
#Document(collection = "users")
public class User extends AbstractDocument {
private String name;
private String surname;
#Indexed(unique = true)
private String username;
}
Book.java
#Document(collection = "books")
public Book extends AbstractDocument {
private String title;
}
Now, I have a script (Spring Batch) which initializes the db with some books. The script defines the auditor this way:
#Configuration
#EnableMongoAuditing
public class MongoConfig {
#Bean
public AuditorAware<User> auditorProvider() {
return new AuditorAware<User>() {
#Override
public User getCurrentAuditor() {
User auditor = new User();
auditor.setUsername("init-batch");
auditor.setName("Data initializer");
auditor.setSurname("Data initializer");
return auditor;
}
};
}
}
The script in somewhere does (for each book I need to persist) bookRepository.save(book)
The first book is persisted, but the second one throws:
nested exception is com.mongodb.DuplicateKeyException: Write failed with error code 11000 and error message 'E11000 duplicate key error index: mydb.books.$createdBy.username dup key: { : "init-batch" }'
Why? The unique index is for users collection, why is it checked for audit references?