MongoDb insert data to different DBs based on request SpringBoot Java - java

Hi I have mongoDb database which contains separate db for different shops, but the collections inside all the dbs have same structure, when I get the request from post service I want to insert the data to the respective database based in the id in the request. Please advice how to do this in springboot java or Kotlin
AMAZON
- ProductDetails
FLIPKART
- ProductDetails
EBAY
- ProductDetails
Now I have a single database and insert all product details in a single database and I want to add different databases for different shops
spring.data.mongodb.host=mongo
spring.data.mongodb.port=27017
spring.data.mongodb.authentication-database=admin
spring.data.mongodb.username=admin
spring.data.mongodb.password=pass
spring.data.mongodb.database=admin

Since you are new to Spring boot and MongoDB, I am providing you the detailed steps as follows to connect multiple mongo DB in single application. This is one of the most simple ways to configure and connect multiple mongo DB. Hopefully, it will be helpful (don't forget to vote up if it is :-)) -
1) Package Structure -
2) Create an abstract MongoDB Config class -
package com.akash.mongo.multidb.config;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
/**
* Abstract class for configuring different MongoTemplate for different DB
* #author Akash
*
*/
public abstract class AbstractMongoDbConfig {
private String host;
private String username;
private String password;
private String database;
private int port;
public void setHost(String host) {
this.host = host;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public void setDatabase(String database) {
this.database = database;
}
public void setPort(int port) {
this.port = port;
}
public MongoDbFactory mongoDbFactory() {
MongoCredential mongoCredential = MongoCredential.createCredential(username, database, password.toCharArray());
MongoClient mongoClient = new MongoClient(new ServerAddress(host, port), mongoCredential, new MongoClientOptions.Builder().build());
return new SimpleMongoDbFactory(mongoClient, database);
}
public abstract MongoTemplate getMongoTemplate() throws Exception;
}
3) Extend the abstract class to create configuration for each DB
AmazonDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for Amazon DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="amazon.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.amazon"}, mongoTemplateRef="amazonMongoTemplate")
public class AmazonDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(AmazonDbConfig.class);
#Override
#Bean(name="amazonMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Amazon DB");
return new MongoTemplate(mongoDbFactory());
}
}
EbayDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for ebay DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="ebay.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.ebay"}, mongoTemplateRef="ebayMongoTemplate")
public class EbayDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(EbayDbConfig.class);
#Override
#Bean(name="ebayMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Ebay DB");
return new MongoTemplate(mongoDbFactory());
}
}
FlipkartDbConfig
package com.akash.mongo.multidb.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
/**
* Configuration class for Flipkart DB
* #author Akash
*
*/
#Configuration
#ConfigurationProperties(prefix="flipkart.mongodb")
#EnableMongoRepositories(basePackages= {"com.akash.mongo.multidb.repository.flipkart"}, mongoTemplateRef="flipkartMongoTemplate")
public class FlipkartDbConfig extends AbstractMongoDbConfig {
private static final Logger logger = LoggerFactory.getLogger(FlipkartDbConfig.class);
#Override
#Primary
#Bean(name="flipkartMongoTemplate")
public MongoTemplate getMongoTemplate() throws Exception {
logger.info("Creating MongoTemplate for Flipkart DB");
return new MongoTemplate(mongoDbFactory());
}
}
Notice that each of these configuration class is creating its own MongoTemplate and it is enabling its own MongoRepository. Also one of these needs to be #Primary otherwise the spring boot will throw error. It doesn't matter which of these is primary; ultimately these will be connecting to their own repository
4) Create entities and a repository for each DB.
You need to create a repository for each DB now. Given that your collection is same for all the DBs, I have created following sample entity -
package com.akash.mongo.multidb.entity;
import java.io.Serializable;
import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
/**
* Sample Entity class
* #author Akash
*
*/
#Document(collection="productDetails")
public class ProductDetails implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private ObjectId id;
#Field("productName")
private String productName;
#Field("productDesc")
private String productDesc;
#Field("productQuantity")
private String productQuantity;
public ObjectId getId() {
return id;
}
public void setId(ObjectId id) {
this.id = id;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getProductDesc() {
return productDesc;
}
public void setProductDesc(String productDesc) {
this.productDesc = productDesc;
}
public String getProductQuantity() {
return productQuantity;
}
public void setProductQuantity(String productQuantity) {
this.productQuantity = productQuantity;
}
}
You can create/modify the entity class as per your collection details.
AmazonRepository
package com.akash.mongo.multidb.repository.amazon;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
*
* #author Akash
*
*/
#Repository
public interface AmazonRepository extends MongoRepository<ProductDetails, ObjectId> {
}
FlipkartRepository
package com.akash.mongo.multidb.repository.flipkart;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
#Repository
public interface FlipkartRepository extends MongoRepository<ProductDetails, ObjectId> {
}
EbayRepository
package com.akash.mongo.multidb.repository.ebay;
import org.bson.types.ObjectId;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
*
* #author Akash
*
*/
#Repository
public interface EbayRepository extends MongoRepository<ProductDetails, ObjectId> {
}
Again, each repository needs to be its own package otherwise there will be errors while running the application. This is the one disadvantage of this solution where you have to create as many repository packages as no of DBs you want to connect.
5) Service implementation and connecting to different repositories
ProductDetailsService Interface
package com.akash.mongo.multidb.service;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
* Sample interface with one add method
* #author Akash
*
*/
public interface ProductDetailsService {
/**
*
* #param productOrigin - the shop name i.e. Amazon, Flipkart or ebay.
* #param productDetails - the product details to add
*/
public void addProductDetails(String productOrigin, ProductDetails productDetails) throws RuntimeException;
}
ProductDetailsServiceImpl Class -
package com.akash.mongo.multidb.service;
import java.util.Map;
import org.bson.types.ObjectId;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.repository.MongoRepository;
import com.akash.mongo.multidb.entity.ProductDetails;
/**
* Implementation of ProductDetailsService interface
* #author Akash
*
*/
public class ProductDetailsServiceImpl implements ProductDetailsService {
private static final Logger logger = LoggerFactory.getLogger(ProductDetailsServiceImpl.class);
/*
* Spring boot will autowire all the repositories along with their name
* amazonRepository - amazon repository instance
* ebayRepository - ebay repository instance and so on
*/
#Autowired
Map<String, MongoRepository<ProductDetails, ObjectId>> repositories;
#Override
public void addProductDetails(String productOrigin, ProductDetails productDetails) throws RuntimeException {
logger.info("Adding product details into {} db", productOrigin);
//if productOrigin is Amazon; repositoryName will be amazonRepository which is already present in spring boot
String repositoryName = productOrigin.toLowerCase()+"Repository";
if(repositories.containsKey(repositoryName)) {
repositories.get(repositoryName).save(productDetails);
} else {
logger.error("{} shop is undefined in DB. Check and try again", productOrigin);
throw new RuntimeException("Shop doesnot exist in MongoDb");
}
}
}
ProductOrigin you can derive from your request or headers whatever information is available to you.
6) Lastly, application.properties
Change the database, username and password details for each DB. Try not to use Admin credentials; Instead create username & password for each DB separately and update application.properties.
#MongoDb connection properties for Flipkart DB
flipkart.mongodb.database=flipkart
flipkart.mongodb.host=http://127.0.0.1
flipkart.mongodb.port=27017
flipkart.mongodb.username=flipkart
flipkart.mongodb.password=flipkart
#MongoDb connection properties for Amazon DB
amazon.mongodb.database=amazon
amazon.mongodb.host=http://127.0.0.1
amazon.mongodb.port=27017
amazon.mongodb.username=amazon
amazon.mongodb.password=amazon
#MongoDb connection properties for ebay DB
ebay.mongodb.database=ebay
ebay.mongodb.host=http://127.0.0.1
ebay.mongodb.port=27017
ebay.mongodb.username=ebay
ebay.mongodb.password=ebay
Now, if you need to add any new database, you just need to add one config class similar to AmazonDbConfig, one more package with the required repositories for that DB and connection details in application.properties. No change is required in service till your collection is same for all the DBs.
If you have multiple collections, you can add entity and repository for each collection (group all the respositories for single shop in one package) and solution should still hold good.

Related

Axon Aggregate Not Found When Trying to Update An Existing Record

I am using Axon and MongoDB to implement a microservice using CQRS and Event Sourcing patterns. When I first start the application, everything works fine. I can create an order and update that order. After I restart the application I can still create an order and update THAT order but when I try to update previous orders, Axon throws "Command 'com.example.demo.command.command.UpdateOrderCommand' resulted in org.axonframework.modelling.command.AggregateNotFoundException(The aggregate was not found in the event store)". My theory is that when I create an order it creates an instance of OrderAggregate and uses that instance to update. But when I restart the application that instance goes away and when updating I am not creating an OrderAggregate instance. Here is my OrderAggregate.java file:
package com.example.demo.command.aggreagate;
import com.example.demo.command.command.CreateOrderCommand;
import com.example.demo.command.command.UpdateOrderCommand;
import com.example.demo.common.event.OrderCreatedEvent;
import com.example.demo.common.event.OrderUpdatedEvent;
import com.example.demo.query.entity.Address;
import com.example.demo.query.entity.Product;
import lombok.extern.slf4j.Slf4j;
import org.axonframework.commandhandling.CommandHandler;
import org.axonframework.eventsourcing.EventSourcingHandler;
import org.axonframework.modelling.command.AggregateIdentifier;
import org.axonframework.modelling.command.AggregateLifecycle;
import org.axonframework.spring.stereotype.Aggregate;
#Aggregate
#Slf4j
public class OrderAggregate {
#AggregateIdentifier
private String _id;
private String customerId;
private int quantity;
private double price;
private String status;
private Product product;
private Address address;
private String createdAt;
private String updatedAt;
public OrderAggregate() {
}
#CommandHandler
public OrderAggregate(CreateOrderCommand command) {
log.info("CreateOrderCommand received.");
AggregateLifecycle.apply(new OrderCreatedEvent(command.getId(), command.getCustomerId(),
command.getQuantity(), command.getPrice(), "ORDERED",
command.getProduct(), command.getAddress(), command.getCreatedAt(), command.getUpdatedAt()));
}
#EventSourcingHandler
public void on(OrderCreatedEvent event) {
log.info("An OrderCreatedEvent occurred.");
this._id = event.getId();
this.customerId = event.getCustomerId();
this.quantity = event.getQuantity();
this.price = event.getPrice();
this.status = "CREATED";
this.product = event.getProduct();
this.address = event.getAddress();
this.createdAt = event.getCreatedAt();
this.updatedAt = event.getUpdatedAt();
}
#CommandHandler
public void on(UpdateOrderCommand command) {
log.info("UpdateOrderCommand received.");
AggregateLifecycle.apply(new OrderUpdatedEvent(command.getId(), command.getQuantity(), command.getPrice(),
"UPDATED", command.getProduct(), command.getAddress(), updatedAt));
}
#EventSourcingHandler
public void on(OrderUpdatedEvent event) {
log.info("An OrderUpdatedEvent occurred.");
this.quantity = event.getQuantity();
this.price = event.getPrice();
this.status = event.getStatus();
this.product = event.getProduct();
this.address = event.getAddress();
this.updatedAt = event.getUpdatedAt();
}
}
I tried making on(UpdateOrderCommand) an OrderAggregate constructor. It kind of works but deletes updatedAt field when updating to database. Any help is appreciated!
Edit:
These are my AxonConfig.java and MongoConfig.java files:
AxonConfig.java
import com.mongodb.client.MongoClient;
import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore;
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.axonframework.extensions.mongo.DefaultMongoTemplate;
import org.axonframework.extensions.mongo.MongoTemplate;
import org.axonframework.extensions.mongo.eventsourcing.eventstore.MongoEventStorageEngine;
import org.axonframework.spring.config.AxonConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
public class AxonConfig {
#Bean
public EmbeddedEventStore eventStore(EventStorageEngine storageEngine, AxonConfiguration configuration) {
return EmbeddedEventStore.builder()
.storageEngine(storageEngine)
.messageMonitor(configuration.messageMonitor(EventStore.class, "eventStore"))
.build();
}
// The `MongoEventStorageEngine` stores each event in a separate MongoDB document
#Bean
public EventStorageEngine storageEngine(MongoClient client) {
return MongoEventStorageEngine.builder().mongoTemplate(DefaultMongoTemplate.builder().mongoDatabase(client).build()).build();
}
}
MongoConfig.java:
package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.mapping.event.ValidatingMongoEventListener;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
#Configuration
public class MongoConfig {
#Bean
ValidatingMongoEventListener validatingMongoEventListener(LocalValidatorFactoryBean validator) {
return new ValidatingMongoEventListener(validator);
}
}
Edit #2:
I updated AxonConfig.java file and this solved my problem:
package com.example.demo.config;
import com.mongodb.client.MongoClient;
import org.axonframework.config.Configurer;
import org.axonframework.config.DefaultConfigurer;
import org.axonframework.eventhandling.tokenstore.TokenStore;
import org.axonframework.eventsourcing.EventCountSnapshotTriggerDefinition;
import org.axonframework.eventsourcing.SnapshotTriggerDefinition;
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
import org.axonframework.extensions.mongo.DefaultMongoTemplate;
import org.axonframework.extensions.mongo.eventsourcing.eventstore.MongoEventStorageEngine;
import org.axonframework.extensions.mongo.eventsourcing.tokenstore.MongoTokenStore;
import org.axonframework.serialization.Serializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class AxonConfig {
#Autowired
public void configuration(Configurer configurer, MongoClient client) {
configurer.configureEmbeddedEventStore(configuration -> storageEngine(client)).eventProcessing(conf -> {
conf.registerTokenStore(configuration -> tokenStore(client, configuration.serializer()));
});
}
#Bean
public EventStorageEngine storageEngine(MongoClient client) {
return MongoEventStorageEngine.builder().mongoTemplate(DefaultMongoTemplate.builder().mongoDatabase(client).build()).build();
}
#Bean
public TokenStore tokenStore(MongoClient client, Serializer serializer) {
return MongoTokenStore.builder().mongoTemplate(DefaultMongoTemplate.builder().mongoDatabase(client).build()).serializer(serializer).build();
}
#Bean
public SnapshotTriggerDefinition snapshotTriggerDefinition(org.axonframework.config.Configuration configuration) {
return new EventCountSnapshotTriggerDefinition(configuration.snapshotter(), 5);
}
#Bean
public Configurer configurer() {
return DefaultConfigurer.defaultConfiguration();
}
}
As was mentioned in the comments on the original post, it was a configuration issue. So I went ahead and dig a little more and found github repository which had an example: (https://github.com/AxonFramework/extension-mongo/tree/master/mongo-axon-example). I implemented the configuration file as was implemented in the repository above and it worked like a charm. Thanks to Lucas Campos and Jan Gelinski for pointing me in the right direction. I will edit the original post accordingly with my solution.

Replace usage of WebServerFactoryCustomizer with TomcatWebServerFactoryCustomizer

tl:dr; I want to be able to have square brackets in URL parameters http://localhost:8080/controller/square?parameter=PL&parameter[wow]=wow and following three Java files are doing that correctly. My question is how I can rewrite my code to be using https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/web/embedded/TomcatWebServerFactoryCustomizer.html?
Following three Java files are working correctly, they allow me to have [] character in the URL. But I would like to rewrite the code to use TomcatWebServerFactoryCustomizer but I do not know how and from where I should get values for its constructor: TomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) .
Controller.java, file 1 of 3
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(value = "controller", produces = MediaType.APPLICATION_JSON_VALUE)
public class Controller {
#GetMapping("/square")
public ResponseEntity<String> requiredFields(
#RequestParam(name = "parameter") final String parameter) {
return ResponseEntity.status(HttpStatus.OK).body("OK");
}
}
CustomWebServerAllowingSquareWebBrackets.java, file 2 of 3
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
class CustomWebServerAllowingSquareBracketsInParameters implements
WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
/**
* {#inheritDoc}
*/
#Override
public void customize(TomcatServletWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setProperty("relaxedQueryChars", "[]");
});
}
}
DemoForParametersWithSquareBracketsApplication.java, file 3 of 3
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class DemoForParametersWithSquareBracketsApplication {
/**
* Bean for {#link WebServerFactoryCustomizer}.
* #return static class with customised web server.
*/
#Bean
public CustomWebServerAllowingSquareBracketsInParameters containerCustomizer() {
return new CustomWebServerAllowingSquareBracketsInParameters();
}
public static void main(String[] args) {
SpringApplication.run(DemoForParametersWithSquareBracketsApplication.class, args);
}
}
These three Java files above, after running allow you to execute successfully following GET request http://localhost:8080/controller/square?parameter=PL&parameter[wow]=wow.
Below you will find my modified classes, currently not working, of custom web server class, using TomcatWebServerFactoryCustomizer.
Modified CustomWebServerAllowingSquareBracketsInParameters.java, file 2 of 3
package com.example.demo;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.embedded.TomcatWebServerFactoryCustomizer;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.core.env.Environment;
class CustomWebServerAllowingSquareBracketsInParameters extends TomcatWebServerFactoryCustomizer {
public CustomWebServerAllowingSquareBracketsInParameters(Environment environment, ServerProperties serverProperties) {
super(environment, serverProperties);
}
/**
* {#inheritDoc}
*/
#Override
public void customize(ConfigurableTomcatWebServerFactory factory) {
factory.addConnectorCustomizers(connector -> {
connector.setProperty("relaxedQueryChars", "[]");
});
super.customize(factory);
}
}
Modified DemoForParametersWithSquareBracketsApplication.java, file 3 of 3
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class DemoForParametersWithSquareBracketsApplication {
/**
* Bean for {#link WebServerFactoryCustomizer}.
* #return static class with customised web server.
*/
#Bean
public CustomWebServerAllowingSquareBracketsInParameters containerCustomizer() {
// TODO it will NOT compile
return new CustomWebServerAllowingSquareBracketsInParameters(null, null);
}
public static void main(String[] args) {
SpringApplication.run(DemoForParametersWithSquareBracketsApplication.class, args);
}
}
My question is, from where and how I could get values Environment environment, ServerProperties serverProperties for the constructor?
Access Environment by #Autowired tag.
#Autowired
protected Environment env;

Field userDAOService in com.restApi.java.jpa.UserDaoServiceCommandLineRunner required a bean of type 'service.UserDAOService' that could not be found

I'm creating a simple persistance jpa based on a curse, but the code is not working and I can't find the problem.
This is the error:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2019-09-06 14:21:19.692 ERROR 8280 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :
APPLICATION FAILED TO START
Description:
Field userDAOService in com.restApi.java.jpa.UserDaoServiceCommandLineRunner required a bean of type 'service.UserDAOService' that could not be found.
The injection point has the following annotations:
#org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'service.UserDAOService' in your configuration.
Process finished with exit code 1
I'm using java openjdk 12 with springboot and Intellij idea. everything on windows 10.
User.java
package entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class User {
#Id
#GeneratedValue
private long id;
private String name;
private String role;
protected User(){
}
public User(String name, String role) {
super();
this.name = name;
this.role = role;
}
public long getId() {
return id;
}
public String getName() {
return name;
}
public String getRole() {
return role;
}
#Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", role='" + role + '\'' +
'}';
}
}
UserDAOService.java
package service;
import entity.User;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
#Service
public class UserDAOService {
#PersistenceContext
private EntityManager entityManager;
public long insert(User user){
entityManager.persist(user);
return user.getId();
}
}
UserDaoServiceCommandLineRunner.java
package com.restApi.java.jpa;
import entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import service.UserDAOService;
#Component
public class UserDaoServiceCommandLineRunner implements CommandLineRunner
{
#Autowired
private UserDAOService userDAOService;
private static final Logger log =
LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class);
#Override
public void run(String... args) throws Exception {
User user = new User("Tom","Admin");
long insert = userDAOService.insert(user);
log.info("User Created"+ user);
}
}
JpaApplication.java
package com.restApi.java.jpa;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
}
The code should show the message "User Created {user id}"
Sorry if the question and the info is not uploaded properly, this is my first question.
Best regards.
UPDATE
Thanks to user czpona comment the code now is running, but is still not showing the message.
The code for JpaApplication.java is now the following:
package com.restApi.java.jpa;
import entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import service.UserDAOService;
#SpringBootApplication
public class JpaApplication {
public static void main(String[] args) {
SpringApplication.run(JpaApplication.class, args);
}
public class UserDaoServiceCommandLineRunner implements
CommandLineRunner {
#Autowired
private UserDAOService userDAOService;
private final Logger log =
LoggerFactory.getLogger(UserDaoServiceCommandLineRunner.class);
#Override
public void run(String... args) throws Exception {
User user = new User("Toto","Admin");
long insert = userDAOService.insert(user);
log.info("User Created"+ user);
}
}
}
I see two problems with your code
First is your project structure
All classes you scan ( entities, components, services ..) must be located in the same package ( or in sub-packages ) your main class JpaApplication located in, to make the Spring boot app able to scan them.
So, class User should be under com.restApi.java.jpa.entity package instead of entity
and UserDAOService should be under com.restApi.java.jpa.service package instead of service
just like :
package com.restApi.java.jpa.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
#Entity
public class User {
*
*
}
and UserDAOService class like:
package com.restApi.java.jpa.service;
import com.restApi.java.jpa.entity.User;
*
import javax.transaction.Transactional;
#Service
public class UserDAOService {
*
*
Another thing,
In order to make the entityManager.persist(user) works, you have to open a transaction, so you can annotate your method with #Transational, like:
import org.springframework.transaction.annotation.Transactional;
*
#Transactional
public long insert(User user){
entityManager.persist(user);
return user.getId();
}
In your JpaApplication you should mention the packages to be scanned for beans
#SpringBootApplication(scanBasePackages = {"service"})
But you are not executing your CommandLineRunner. Is it what you want to do?

Spring Boot with multiple data sources using same repositories and model classes?

I have to do a Spring Boot version 1.5 application that can do like this: it creates an object and try to persist to both data sources (example: 2 databases named: test_book_1 and test_book_2 in Postgresql).
I have found an example that could work for 2 different objects (Author: A, Book: B) which can be stored in different databases (A goes to test_book_1 and B goes to test_book_2). This is a good example but it is not what I wanted.
Store separate objects to different data sources
I got the idea that I need to define 2 custom JPA DatabaseConfigurations and need to config them to manage the same repository and domain class. However, Spring only use the second class as Qualifier to inject for JPA repository (I understand that when both configurations point to same class then the second one can override).
The question is, how can I tell Spring to let it knows that when it should inject the correct Bean (BookRepository) from the wanted data source (I wanted to persist the object to both data sources, not just the second one).
Here is the modified code from the example link above.
An application.properties file which is modified to create 2 database in Postgresql instead of 1 in Postgresql and 1 in Mysql.
server.port=8082
# -----------------------
# POSTGRESQL DATABASE CONFIGURATION
# -----------------------
spring.postgresql.datasource.url=jdbc:postgresql://localhost:5432/test_book_db
spring.postgresql.datasource.username=petauser
spring.postgresql.datasource.password=petapasswd
spring.postgresql.datasource.driver-class-name=org.postgresql.Driver
# ------------------------------
# POSTGRESQL 1 DATABASE CONFIGURATION
# ------------------------------
spring.mysql.datasource.url=jdbc:postgresql://localhost:5432/test_author_db
spring.mysql.datasource.username=petauser
spring.mysql.datasource.password=petapasswd
spring.mysql.datasource.driver-class-name=org.postgresql.Driver
package: com.roufid.tutorial.configuration
class APostgresqlConfiguration
package com.roufid.tutorial.configuration;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.roufid.tutorial.entity.postgresql.Book;
/**
* Spring configuration of the "PostgreSQL" database.
*
* #author Radouane ROUFID.
*
*/
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "postgresqlEntityManager",
transactionManagerRef = "postgresqlTransactionManager",
basePackages = "com.roufid.tutorial.dao.postgresql"
)
public class APostgresqlConfiguration {
/**
* PostgreSQL datasource definition.
*
* #return datasource.
*/
#Bean
#Primary
#ConfigurationProperties(prefix = "spring.postgresql.datasource")
public DataSource postgresqlDataSource() {
return DataSourceBuilder
.create()
.build();
}
/**
* Entity manager definition.
*
* #param builder an EntityManagerFactoryBuilder.
* #return LocalContainerEntityManagerFactoryBean.
*/
#Primary
#Bean(name = "postgresqlEntityManager")
public LocalContainerEntityManagerFactoryBean postgresqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(postgresqlDataSource())
.properties(hibernateProperties())
.packages(Book.class)
.persistenceUnit("postgresqlPU")
.build();
}
#Primary
#Bean(name = "postgresqlTransactionManager")
public PlatformTransactionManager postgresqlTransactionManager(#Qualifier("postgresqlEntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
private Map<String, Object> hibernateProperties() {
Resource resource = new ClassPathResource("hibernate.properties");
try {
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
return properties.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey().toString(),
e -> e.getValue())
);
} catch (IOException e) {
return new HashMap<String, Object>();
}
}
}
package: com.roufid.tutorial.configuration
class MysqlConfiguration
package com.roufid.tutorial.configuration;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PropertiesLoaderUtils;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.roufid.tutorial.entity.mysql.Author;
import com.roufid.tutorial.entity.postgresql.Book;
/**
* Spring configuration of the "mysql" database.
*
* #author Radouane ROUFID.
*
*/
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "mysqlEntityManager",
transactionManagerRef = "mysqlTransactionManager",
basePackages = "com.roufid.tutorial.dao.postgresql"
)
public class MysqlConfiguration {
/**
* MySQL datasource definition.
*
* #return datasource.
*/
#Bean
#ConfigurationProperties(prefix = "spring.mysql.datasource")
public DataSource mysqlDataSource() {
return DataSourceBuilder
.create()
.build();
}
/**
* Entity manager definition.
*
* #param builder an EntityManagerFactoryBuilder.
* #return LocalContainerEntityManagerFactoryBean.
*/
#Bean(name = "mysqlEntityManager")
public LocalContainerEntityManagerFactoryBean mysqlEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(mysqlDataSource())
.properties(hibernateProperties())
.packages(Book.class)
.persistenceUnit("mysqlPU")
.build();
}
/**
* #param entityManagerFactory
* #return
*/
#Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager mysqlTransactionManager(#Qualifier("mysqlEntityManager") EntityManagerFactory entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
private Map<String, Object> hibernateProperties() {
Resource resource = new ClassPathResource("hibernate.properties");
}
} try {
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
return properties.entrySet().stream()
.collect(Collectors.toMap(
e -> e.getKey().toString(),
e -> e.getValue())
);
} catch (IOException e) {
return new HashMap<String, Object>();
}
}
}
package com.roufid.tutorial.dao.postgresql
class BookRepository
package com.roufid.tutorial.dao.postgresql;
import org.springframework.data.repository.CrudRepository;
import com.roufid.tutorial.entity.postgresql.Book;
/**
* Book repository.
*
* #author Radouane ROUFID.
*
*/
public interface BookRepository extends CrudRepository<Book, Long> {
}
package com.roufid.tutorial.entity.postgresql
class Book
package com.roufid.tutorial.entity.postgresql;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
#Entity
#Table(name = "BOOK")
public class Book implements Serializable {
private static final long serialVersionUID = -9019470250770543773L;
#Id
private Long id;
#Column
private String name;
#Column
private Long authorId;
...
// Setters, Getters
}
And a test class to inject the BookRepository which will use the MysqlConfiguration class (second datasource) only.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ApplicationTest {
#Autowired
private BookRepository bookRepository;
#Before
public void init() {
Book book = new Book();
book.setId(bookId);
book.setName("Spring Boot Book");
// How can it persist to the first datasource?
bookRepository.save(book);
}
}
Looks like you need multitenancy support.
There is a Spring based solution for this
You need to implement CurrentTenantIdentifierResolver interface
public String resolveCurrentTenantIdentifier()
And extend
AbstractDataSourceBasedMultiTenantConnectionProviderImpl
to return DataSource for the tenant
See more here
So I think I got an answer myself (I want to stick with Spring JPA and Hibernate only). So here is what I did, inspired from Spring Booth with 2 different data sources
The most important class is the config class to manually create 2 data sources (2 databases in Postgresql)
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "sourceEntityManagerFactory",
basePackages = "application"
)
public class PersistenceConfig {
#Autowired
private JpaVendorAdapter jpaVendorAdapter;
private String databaseUrl = "jdbc:postgresql://localhost:5432/test_book_db";
private String targetDatabaseUrl = "jdbc:postgresql://localhost:5432/test_author_db";
private String username = "petauser";
private String password = "petapasswd";
private String driverClassName = "org.postgresql.Driver";
private String dialect = "org.hibernate.dialect.PostgreSQLDialect";
private String ddlAuto = "update";
#Bean
public EntityManager sourceEntityManager() {
return sourceEntityManagerFactory().createEntityManager();
}
#Bean
public EntityManager targetEntityManager() {
return targetEntityManagerFactory().createEntityManager();
}
#Bean
#Primary
public EntityManagerFactory sourceEntityManagerFactory() {
return createEntityManagerFactory("source", databaseUrl);
}
#Bean
public EntityManagerFactory targetEntityManagerFactory() {
return createEntityManagerFactory("target", targetDatabaseUrl);
}
#Bean(name = "transactionManager")
#Primary
public PlatformTransactionManager sourceTransactionManager() {
return new JpaTransactionManager(sourceEntityManagerFactory());
}
#Bean
public PlatformTransactionManager targetTransactionManager() {
return new JpaTransactionManager(targetEntityManagerFactory());
}
private EntityManagerFactory createEntityManagerFactory(final String persistenceUnitName,
final String databaseUrl) {
final LocalContainerEntityManagerFactoryBean entityManagerFactory = new LocalContainerEntityManagerFactoryBean();
final DriverManagerDataSource dataSource = new DriverManagerDataSource(databaseUrl, username, password);
dataSource.setDriverClassName(driverClassName);
entityManagerFactory.setDataSource(dataSource);
entityManagerFactory.setJpaVendorAdapter(jpaVendorAdapter);
entityManagerFactory.setPackagesToScan("application.domain");
entityManagerFactory.setPersistenceUnitName(persistenceUnitName);
final Properties properties = new Properties();
properties.setProperty("hibernate.dialect", dialect);
properties.setProperty("hibernate.hbm2ddl.auto", ddlAuto);
entityManagerFactory.setJpaProperties(properties);
entityManagerFactory.afterPropertiesSet();
return entityManagerFactory.getObject();
}
}
Because of I want to copy a stored entity from source database to a target database. So I used Spring JPA to read the object from source database
public interface StorageEntryRepository extends CrudRepository<StorageEntry, Long> {
}
And I made a service class to check if the entity which is existed by value (someValue contain a substring "Book") in target database before persisting it in target database by Hibernate (the StorageEntry here is a domain class from the example link above).
#Service
#Transactional(rollbackFor = Exception.class)
public class StorageEntryService {
#Autowired
private StorageEntryRepository storageEntryRepository;
#PersistenceContext(unitName = "target")
private EntityManager targetEntityManager;
public void save(StorageEntry storageEntry) throws Exception {
// this.storageEntryRepository.save(storageEntry);
// Load an stored entry from the source database
StorageEntry storedEntry = this.storageEntryRepository.findOne(12L);
//this.storageEntryRepository.save(storageEntry);
// Save also to a different database
final Session targetHibernateSession = targetEntityManager.unwrap(Session.class);
Criteria criteria = targetHibernateSession.createCriteria(StorageEntry.class);
criteria.add(Restrictions.like("someValue", "%Book1%"));
List<StorageEntry> storageEntries = criteria.list();
if (storageEntries.isEmpty()) {
targetEntityManager.merge(storedEntry);
// No flush then nodata is saved in the different database
targetHibernateSession.flush();
System.out.println("Stored the new object to target database.");
} else {
System.out.println("Object already existed in target database.");
}
}
}
So it ends up with I can use both JPA from the current working application and just need to make another application with a config class and a service class to do this migration of existing objects to a new database.

Update an entity with Morphia and its BasicDAO

I make a REST webservice with SpringBoot web and the Morphia DAO (to use MongoDB).
As I did when I used Hibernate on MySQL, I would like to use Generic entities, repositories and endpoints so that I just have to set my entities, inherit Repositories and Services and let use the generated CRUD with REST calls.
It almost done, but I encounter a problem with the generic update of my entities with Morphia. Everything I've seen so far talks about manually setting a request with the fields that have to change ; but in the Hibernate way, we just setted the Id field, called persist() and it automatically knowed what changed and applied changes in database.
Here is some code.
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 (one of my final entities)
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 (my base repository, inheriting from the Morphia one)
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 (my user repository)
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 (the generic service, used from the Rest endpoints)
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 (my base Rest Endpoint)
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();
}
}
You've run into one of the difficult questions with Morphia. Based on the code you posted above you should look into the merge method here.
The important thing to remember is that this is not a deep merge, only top level fields, if you have complicated data objects this probably won't help.
It essentially works like this:
T Entity -> Map and then takes the map and runs a recursive update over the non-null fields like so: update({_id:#Id-field},{$set:mapOfEntityFields})
The standard transformation rules from T Entity apply ->
Map, like it does for save.
For a deep merging of any generic entity it would require you to handle this yourself with a custom method.
This is a good example from another question on SO dealing with deep merging using JSON partials in Spring over complex entities using the org.codehaus.jackson.map.ObjectMapper. It should be easily adapted to your problem.
If neither of these help you, please comment in my answer and we can work out a custom recursive method that should work for you. Hope that helps.

Categories

Resources