diff --git a/spring-boot-data/pom.xml b/spring-boot-data/pom.xml
index 9c11e09f4a..83c4c5610f 100644
--- a/spring-boot-data/pom.xml
+++ b/spring-boot-data/pom.xml
@@ -6,44 +6,42 @@
war
spring-boot-data
Spring Boot Data Module
-
parent-boot-2
com.baeldung
0.0.1-SNAPSHOT
../parent-boot-2
-
org.springframework.boot
spring-boot-starter-data-redis
2.1.6.RELEASE
-
+
+ org.javers
+ javers-spring-boot-starter-sql
+ ${javers.version}
+
org.springframework.boot
spring-boot-starter-data-mongodb
2.1.6.RELEASE
-
org.springframework.boot
spring-boot-starter-data-jpa
2.1.6.RELEASE
-
com.h2database
h2
1.4.197
-
org.springframework.boot
spring-boot-starter-web
-
org.springframework.boot
spring-boot-starter-tomcat
@@ -54,8 +52,8 @@
spring-boot-starter-test
test
-
+
spring-boot
@@ -98,6 +96,14 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 8
+
+
@@ -106,6 +112,22 @@
autoconfiguration
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+ com.baeldung.javers.SpringBootJaVersApplication
+ JAR
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 8
+
+
org.apache.maven.plugins
maven-surefire-plugin
@@ -133,14 +155,21 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+
+
- com.baeldung.SpringBootDataApplication
+ 5.6.3
2.2.4
-
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/SpringBootJaVersApplication.java b/spring-boot-data/src/main/java/com/baeldung/javers/SpringBootJaVersApplication.java
new file mode 100644
index 0000000000..91c9b11af5
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/SpringBootJaVersApplication.java
@@ -0,0 +1,31 @@
+package com.baeldung.javers;
+
+import com.baeldung.javers.domain.Address;
+import com.baeldung.javers.domain.Product;
+import com.baeldung.javers.domain.Store;
+import com.baeldung.javers.repo.StoreRepository;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.context.event.ApplicationReadyEvent;
+import org.springframework.context.event.EventListener;
+
+@SpringBootApplication
+public class SpringBootJaVersApplication {
+ @Autowired
+ StoreRepository storeRepository;
+
+ public static void main(String[] args) {
+ SpringApplication.run(SpringBootJaVersApplication.class, args);
+ }
+
+ @EventListener
+ public void appReady(ApplicationReadyEvent event) {
+ Store store = new Store("Baeldung store", new Address("Some street", 22222));
+ for (int i = 1; i < 3; i++) {
+ Product product = new Product("Product #" + i, 100 * i);
+ store.addProduct(product);
+ }
+ storeRepository.save(store);
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/config/JaversConfiguration.java b/spring-boot-data/src/main/java/com/baeldung/javers/config/JaversConfiguration.java
new file mode 100644
index 0000000000..b230dcec1d
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/config/JaversConfiguration.java
@@ -0,0 +1,20 @@
+package com.baeldung.javers.config;
+
+import org.javers.spring.auditable.AuthorProvider;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class JaversConfiguration {
+ @Bean
+ public AuthorProvider provideJaversAuthor() {
+ return new SimpleAuthorProvider();
+ }
+
+ private static class SimpleAuthorProvider implements AuthorProvider {
+ @Override
+ public String provide() {
+ return "Baeldung Author";
+ }
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/domain/Address.java b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Address.java
new file mode 100644
index 0000000000..930276b3ee
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Address.java
@@ -0,0 +1,33 @@
+package com.baeldung.javers.domain;
+
+import javax.persistence.Embeddable;
+
+@Embeddable
+public class Address {
+ private String address;
+ private Integer zipCode;
+
+ public Address(String address, Integer zipCode) {
+ this.address = address;
+ this.zipCode = zipCode;
+ }
+
+ public Address() {
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+ public Integer getZipCode() {
+ return zipCode;
+ }
+
+ public void setZipCode(Integer zipCode) {
+ this.zipCode = zipCode;
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/domain/Product.java b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Product.java
new file mode 100644
index 0000000000..61a2993bb9
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Product.java
@@ -0,0 +1,61 @@
+package com.baeldung.javers.domain;
+
+import javax.persistence.*;
+
+@Entity
+public class Product {
+ @Id
+ @GeneratedValue
+ private int id;
+
+ private String name;
+ private double price;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "store_id")
+ private Store store;
+
+ public Product(String name, double price) {
+ this.name = name;
+ this.price = price;
+ }
+
+ public Product() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public double getPrice() {
+ return price;
+ }
+
+ public void setPrice(double price) {
+ this.price = price;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public Store getStore() {
+ return store;
+ }
+
+ public void setStore(Store store) {
+ this.store = store;
+ }
+
+ public void setNamePrefix(String prefix) {
+ this.name = prefix + this.name;
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/domain/Store.java b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Store.java
new file mode 100644
index 0000000000..5aa6686261
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/domain/Store.java
@@ -0,0 +1,62 @@
+package com.baeldung.javers.domain;
+
+import javax.persistence.*;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+public class Store {
+ @Id
+ @GeneratedValue
+ private int id;
+ private String name;
+ @Embedded
+ private Address address;
+ @OneToMany(
+ mappedBy = "store",
+ cascade = CascadeType.ALL,
+ orphanRemoval = true
+ )
+ private List products = new ArrayList<>();
+
+ public Store(String name, Address address) {
+ this.name = name;
+ this.address = address;
+ }
+
+ public Store() {
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Address getAddress() {
+ return address;
+ }
+
+ public void setAddress(Address address) {
+ this.address = address;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ public void addProduct(Product product) {
+ product.setStore(this);
+ this.products.add(product);
+ }
+
+ public List getProducts() {
+ return this.products;
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/repo/ProductRepository.java b/spring-boot-data/src/main/java/com/baeldung/javers/repo/ProductRepository.java
new file mode 100644
index 0000000000..090ebe6742
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/repo/ProductRepository.java
@@ -0,0 +1,11 @@
+package com.baeldung.javers.repo;
+
+import com.baeldung.javers.domain.Product;
+import org.javers.spring.annotation.JaversAuditable;
+import org.springframework.data.repository.CrudRepository;
+
+public interface ProductRepository extends CrudRepository {
+ @Override
+ @JaversAuditable
+ S save(S s);
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/repo/StoreRepository.java b/spring-boot-data/src/main/java/com/baeldung/javers/repo/StoreRepository.java
new file mode 100644
index 0000000000..aa9d07c4c8
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/repo/StoreRepository.java
@@ -0,0 +1,9 @@
+package com.baeldung.javers.repo;
+
+import com.baeldung.javers.domain.Store;
+import org.javers.spring.annotation.JaversSpringDataAuditable;
+import org.springframework.data.repository.CrudRepository;
+
+@JaversSpringDataAuditable
+public interface StoreRepository extends CrudRepository {
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/service/StoreService.java b/spring-boot-data/src/main/java/com/baeldung/javers/service/StoreService.java
new file mode 100644
index 0000000000..2977f715cb
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/service/StoreService.java
@@ -0,0 +1,59 @@
+package com.baeldung.javers.service;
+
+
+import com.baeldung.javers.domain.Product;
+import com.baeldung.javers.domain.Store;
+import com.baeldung.javers.repo.ProductRepository;
+import com.baeldung.javers.repo.StoreRepository;
+import org.springframework.stereotype.Service;
+
+import java.util.Optional;
+import java.util.Random;
+
+@Service
+public class StoreService {
+ private final ProductRepository productRepository;
+ private final StoreRepository storeRepository;
+
+ public StoreService(ProductRepository productRepository, StoreRepository storeRepository) {
+ this.productRepository = productRepository;
+ this.storeRepository = storeRepository;
+ }
+
+ public void updateProductPrice(Integer productId, Double price) {
+ Optional productOpt = productRepository.findById(productId);
+ productOpt.ifPresent(product -> {
+ product.setPrice(price);
+ productRepository.save(product);
+ });
+ }
+
+ public void rebrandStore(int storeId, String updatedName) {
+ Optional storeOpt = storeRepository.findById(storeId);
+ storeOpt.ifPresent(store -> {
+ store.setName(updatedName);
+ store.getProducts().forEach(product -> {
+ product.setNamePrefix(updatedName);
+ });
+ storeRepository.save(store);
+ });
+ }
+
+ public void createRandomProduct(Integer storeId) {
+ Optional storeOpt = this.storeRepository.findById(storeId);
+ storeOpt.ifPresent(store -> {
+ Random random = new Random();
+ Product product = new Product("Product#" + random.nextInt(), random.nextDouble() * 100);
+ store.addProduct(product);
+ storeRepository.save(store);
+ });
+ }
+
+ public Store findStoreById(int storeId) {
+ return storeRepository.findById(storeId).get();
+ }
+
+ public Product findProductById(int id) {
+ return this.productRepository.findById(id).get();
+ }
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/web/RebrandStoreDto.java b/spring-boot-data/src/main/java/com/baeldung/javers/web/RebrandStoreDto.java
new file mode 100644
index 0000000000..c9681add40
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/web/RebrandStoreDto.java
@@ -0,0 +1,5 @@
+package com.baeldung.javers.web;
+
+public class RebrandStoreDto {
+ public String name;
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/web/StoreController.java b/spring-boot-data/src/main/java/com/baeldung/javers/web/StoreController.java
new file mode 100644
index 0000000000..89c3c03a11
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/web/StoreController.java
@@ -0,0 +1,73 @@
+package com.baeldung.javers.web;
+
+import com.baeldung.javers.domain.Product;
+import com.baeldung.javers.domain.Store;
+import com.baeldung.javers.service.StoreService;
+import org.javers.core.Changes;
+import org.javers.core.Javers;
+import org.javers.core.metamodel.object.CdoSnapshot;
+import org.javers.repository.jql.JqlQuery;
+import org.javers.repository.jql.QueryBuilder;
+import org.javers.shadow.Shadow;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+public class StoreController {
+ private final StoreService storeService;
+ private final Javers javers;
+
+ public StoreController(StoreService customerService, Javers javers) {
+ this.storeService = customerService;
+ this.javers = javers;
+ }
+
+ @PostMapping("/stores/{storeId}/products/random")
+ public void createRandomProduct(@PathVariable final Integer storeId) {
+ storeService.createRandomProduct(storeId);
+ }
+
+ @PostMapping("/stores/{storeId}/rebrand")
+ public void rebrandStore(@PathVariable final Integer storeId, @RequestBody RebrandStoreDto rebrandStoreDto) {
+ storeService.rebrandStore(storeId, rebrandStoreDto.name);
+ }
+
+ @PostMapping(value = "/stores/{storeId}/products/{productId}/price", consumes = MediaType.APPLICATION_JSON_VALUE)
+ public void updateProductPrice(@PathVariable final Integer productId, @PathVariable String storeId, @RequestBody UpdatePriceDto priceDto) {
+ storeService.updateProductPrice(productId, priceDto.price);
+ }
+
+ @GetMapping("/products/{productId}/changes")
+ public String getProductChanges(@PathVariable int productId) {
+ Product product = storeService.findProductById(productId);
+ QueryBuilder jqlQuery = QueryBuilder.byInstance(product);
+ Changes changes = javers.findChanges(jqlQuery.build());
+ return javers.getJsonConverter().toJson(changes);
+ }
+
+ @GetMapping("/products/snapshots")
+ public String getProductSnapshots() {
+ QueryBuilder jqlQuery = QueryBuilder.byClass(Product.class);
+ List snapshots = javers.findSnapshots(jqlQuery.build());
+ return javers.getJsonConverter().toJson(snapshots);
+ }
+
+ @GetMapping("/stores/{storeId}/shadows")
+ public String getStoreShadows(@PathVariable int storeId) {
+ Store store = storeService.findStoreById(storeId);
+ JqlQuery jqlQuery = QueryBuilder.byInstance(store)
+ .withChildValueObjects().build();
+ List> shadows = javers.findShadows(jqlQuery);
+ return javers.getJsonConverter().toJson(shadows.get(0));
+ }
+
+ @GetMapping("/stores/snapshots")
+ public String getStoresSnapshots() {
+ QueryBuilder jqlQuery = QueryBuilder.byClass(Store.class);
+ List snapshots = javers.findSnapshots(jqlQuery.build());
+ return javers.getJsonConverter().toJson(snapshots);
+ }
+
+}
diff --git a/spring-boot-data/src/main/java/com/baeldung/javers/web/UpdatePriceDto.java b/spring-boot-data/src/main/java/com/baeldung/javers/web/UpdatePriceDto.java
new file mode 100644
index 0000000000..02808a8134
--- /dev/null
+++ b/spring-boot-data/src/main/java/com/baeldung/javers/web/UpdatePriceDto.java
@@ -0,0 +1,5 @@
+package com.baeldung.javers.web;
+
+public class UpdatePriceDto {
+ public double price;
+}
diff --git a/spring-boot-data/src/main/resources/application.properties b/spring-boot-data/src/main/resources/application.properties
index 845b783634..6378a48506 100644
--- a/spring-boot-data/src/main/resources/application.properties
+++ b/spring-boot-data/src/main/resources/application.properties
@@ -1,2 +1,25 @@
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
-spring.jackson.time-zone=Europe/Zagreb
\ No newline at end of file
+spring.jackson.time-zone=Europe/Zagreb
+spring.h2.console.path=/h2
+spring.h2.console.enabled=true
+spring.datasource.url=jdbc:h2:mem:testdb
+spring.datasource.driver-class-name=org.h2.Driver
+spring.datasource.username=sa
+spring.datasource.password=
+spring.main.allow-bean-definition-overriding=true
+javers.mappingStyle=FIELD
+javers.algorithm=SIMPLE
+javers.commitIdGenerator=synchronized_sequence
+javers.prettyPrint=true
+javers.typeSafeValues=false
+javers.newObjectSnapshot=true
+javers.packagesToScan=
+javers.auditableAspectEnabled=true
+javers.springDataAuditableRepositoryAspectEnabled=true
+javers.sqlSchema=
+javers.sqlSchemaManagementEnabled=true
+javers.prettyPrintDateFormats.localDateTime=dd MMM yyyy, HH:mm:ss
+javers.prettyPrintDateFormats.zonedDateTime=dd MMM yyyy, HH:mm:ssZ
+javers.prettyPrintDateFormats.localDate=dd MMM yyyy
+javers.prettyPrintDateFormats.localTime=HH:mm:ss
+