I am working on a spring boot project. In that, i have an entity called ProductMap which i want to keep in cache. I did that using MapLoader and defining the configuration for the map as below.
#Bean
public Config hazelcastConfig() {
return new Config().setInstanceName("hazelcast-instance").addMapConfig(
new MapConfig().setName("ProductMap")
.setMapStoreConfig(
new MapStoreConfig().setEnabled(true).setInitialLoadMode(MapStoreConfig.InitialLoadMode.EAGER)
.setClassName("com.hazelcast.example.HzTest.config.ProductMapLoader")
));
}
ProductMap entity:
#Data
#Entity
#KeySpace("ProductMap")
#Table
public class ProductMap implements Serializable {
#Id
#org.springframework.data.annotation.Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer Id;
private String name;
private Integer category;
private Integer productType;
}
ProductMapLoader:
#Log4j2
#Component
public class ProductMapLoader implements MapLoader<Integer, ProductMap>, ApplicationContextAware {
private static ProductMapRepository productMapRepository;
#Override
public synchronized ProductMap load(Integer integer) {
System.out.println("Load::" + integer);
return productMapRepository.findById(integer).get();
}
#Override
public synchronized Map<Integer, ProductMap> loadAll(Collection<Integer> collection) {
Map<Integer, ProductMap> result = new HashMap<>();
for (Integer key : collection) {
ProductMap productMap = this.load(key);
if (productMap != null) {
result.put(key, productMap);
}
}
return result;
}
#Override
public synchronized Iterable<Integer> loadAllKeys() {
System.out.println("load all keys" + productMapRepository);
return productMapRepository.findAllProdMapKeys();
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
productMapRepository = applicationContext.getBean(ProductMapRepository.class);
}
}
I am loading the cache on startup,
#PostConstruct
public void Init() {
IMap map = hazelcastInstance.getMap("ProductMap"); // this will load the cache
}
In also created a HazelcastRepository,
public interface ProductMapKvRepo extends KeyValueRepository<ProductMap, Integer> {
List<ProductMap> findByProductType(Integer productType);
}
In one of my service methods, it calls productMapKvRepo.findAll() and productMapKvRepo.findByProductType(1). But the repository still queries the database.
Hibernate: select productmap0_.id as id1_0_, productmap0_.category as category2_0_, productmap0_.name as name3_0_, productmap0_.product_type as product_4_0_ from product_map productmap0_
Hibernate: select productmap0_.id as id1_0_, productmap0_.category as category2_0_, productmap0_.name as name3_0_, productmap0_.product_type as product_4_0_ from product_map productmap0_ where productmap0_.product_type=?
dependencies used:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>3.12.7</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast-client</artifactId>
<version>3.12.7</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>spring-data-hazelcast</artifactId>
<version>2.2.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.13</version>
</dependency>
</dependencies>
Can anyone tell me what is wrong here and what can i do?
Your logs indicate that your Spring Data repository is Hibernate-backed which means your project is misconfigured. Spring Data Hazelcast doesn't use Hibernate to read data from the Hazelcast IMDG cluster.
If the use of Hibernate is intended(most likely), then you should consider using its native second-level cache capabilities with Hazelcast instead of wrapping Spring Data repositories inside a MapLoader. You can find the example here.
However, if you want to apply the read-through caching pattern with a MapLoader, you'd need to use a spring-data-hazelcast artifact to read data from Hazelcast cluster directly.
While I was working with Spring Boot v2.1.3.RELEASE and Spring v5.1.5.RELEASE , spring was injecting SimpleJpaRepository type for both repositories :
MyStandardRepository And MyHazelCastRepository, with the presence only of #EnableHazelcastRepositories
By adding also #EnableJpaRepositories to my HazelcastConfiguration class :
spring then injected SimpleKeyValueRepository bean type into MyHazelCastRepository :
#Configuration
#EnableHazelcastRepositories(basePackages = {"com.test.repository.hazelcast"})
#EnableJpaRepositories(basePackages = {"com.test.repository.dao"})
public class HazelcastConfiguration {
#Bean
HazelcastInstance hazelcastInstance() {
return Hazelcast.newHazelcastInstance();
}
#Bean
public KeyValueOperations keyValueTemplate() {
return new KeyValueTemplate(new HazelcastKeyValueAdapter(hazelcastInstance()));
}
#Bean
public HazelcastKeyValueAdapter hazelcastKeyValueAdapter(HazelcastInstance hzInstance) {
return new HazelcastKeyValueAdapter(hzInstance);
}
}
Related
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-narayana-jta</artifactId>
</dependency>
//repo
#ApplicationScoped
public class ProjectRepository implements
PanacheMongoRepositoryBase<Project, String> {
....
}
//transaction method
#Inject
ProjectRepository projectRepository
....
#Transactional
public Project import(Order order, User user) {
Project project = factor.from(order);
projectRepository.persist(project);
if (project.id != null){
//try to throw exception manually
throw new RuntimeException("testing");
}
return project;
}
I have tried #transactional annotation with mongodb 4.4.10 and quarkus 2.5.1. Transaction still has no rollback effect. any ideas ? thanks
I'm currently facing an issue with integrating multi-tenancy in my spring-boot project.
Here is the situation: I'm linking my spring boot project with a DB source which is Postgresql: one of the database of the pool is known, the "master", which is the default DB the spring-boot project will connect to. The other DB of the pool are unknown, and I want to be able to switch the DB connection to them during the app runtime with an API call. In a TablesController, I created a GET METHOD getting in the query a parameter "envname" and tries to switch the current DB connected to the one with the envname, and to return the list of its tables.
I tried this tutorial, but i can't find why it's not working. Here's my code:
pom.xml
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
application.properties
# General properties
server.port = 9090
# Hibernate properties
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=none
spring.jpa.hibernate.show-sql=true
spring.jpa.hibernate.tenant_identifier_resolver= com.example.myproject.util.CurrentTenantIdentifierResolverImp
# Postgresql properties
spring.datasource.url=jdbc:postgresql://localhost:5432/master
spring.datasource.username=postgres
spring.datasource.password=password
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.initialization-mode=always
spring.datasource.initialize=true
spring.datasource.continue-on-error=true
TenantContext
public class TenantContext {
final public static String DEFAULT_TENANT = "master";
private static ThreadLocal<String> currentTenant = new ThreadLocal<String>()
{
#Override
protected String initialValue() {
return DEFAULT_TENANT;
}
};
public static void setCurrentTenant(String tenant) {
currentTenant.set(tenant);
}
public static String getCurrentTenant() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
TablesController
#RestController
public class TablesController {
#Autowired
GetTablesName getTablesNames;
#Autowired
DataSource dataSource;
#Autowired
private JdbcTemplate jdbcTemplate;
#Autowired
private ApplicationContext appContext;
#Autowired
public BackXPressConfig backXPressConfig;
#GetMapping("/{envname}/tables")
public ResponseEntity<?> getTablesByEnv(#PathVariable("envname") String envname){
TenantContext.setCurrentTenant(envname);
try {
System.out.println(TenantContext.getCurrentTenant());
Object o = JdbcUtils.extractDatabaseMetaData(dataSource, getTablesNames);
System.out.println(o);
return ResponseEntity.ok(o);
} catch (MetaDataAccessException e) {
System.out.println(e);
return null;
}
}
}
TenantIdentifier
#Component
public class TenantIdentifier implements CurrentTenantIdentifierResolver{
#Override
public String resolveCurrentTenantIdentifier() {
return TenantContext.getCurrentTenant();
}
#Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
TenantInterceptor
#Component
public class TenantInterceptor extends HandlerInterceptorAdapter {
#Override
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
TenantContext.clear();
}
}
HibernateConfig
#Configuration
public class HibernateConfig {
#Autowired
private JpaProperties jpaProperties;
#Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) {
Map<String, Object> properties = new HashMap<>();
properties.putAll(jpaProperties.getProperties());
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.example.myproject");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
}
}
GetTables
#Service
public class GetTablesName implements DatabaseMetaDataCallback {
public List<String> processMetaData(DatabaseMetaData metaData) throws SQLException {
ResultSet rawDataSet = metaData.getTables(metaData.getUserName(), null, null, new String[]{"TABLE"});
List<String> listTable = new ArrayList<String>();
while (rawDataSet.next()) {
listTable.add(rawDataSet.getString(3));
}
return listTable;
}
}
MultiTenantConnectionProviderImpl
#Component
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider {
private static final long serialVersionUID = 6246085840652870138L;
#Autowired
private DataSource dataSource;
#Override
public Connection getAnyConnection() throws SQLException {
return dataSource.getConnection();
}
#Override
public void releaseAnyConnection(Connection connection) throws SQLException {
connection.close();
}
#Override
public Connection getConnection(String tenantIdentifier) throws SQLException {
final Connection connection = getAnyConnection();
try {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
return connection;
}
#Override
public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
try {
connection.createStatement().execute( "USE " + tenantIdentifier );
}
catch ( SQLException e ) {
throw new HibernateException(
"Could not alter JDBC connection to specified schema [" + tenantIdentifier + "]",
e
);
}
connection.close();
}
#SuppressWarnings("rawtypes")
#Override
public boolean isUnwrappableAs(Class unwrapType) {
return false;
}
#Override
public <T> T unwrap(Class<T> unwrapType) {
return null;
}
#Override
public boolean supportsAggressiveRelease() {
return true;
}
}
I have created a SpringBoot CXF soap service from an existing WSDL file. I can now access WSDL from my service running on embedded tomcat from the springboot application. When I am trying to send a request to the service from soap-ui, the control reaches the implementation of the service method and then while it access the #Autowired service layer, it throws all NPE, as the service object is null (I have seen this while debugging). If I manually create (new Service() by commenting out the #Autowired object), the flow can reach the service implementation and then the DAO object fails as it cannot #Autowire the DAO Impl, further when i create the manually DAO Impl object it fails at #PersistenceContext, as it the entity manager is null.
I have made sure service is annotated with #Service, DAO layer with #Repository, still the issue persists. Also i have added #ComponentScan and giving all the package names, still i am getting null for all #Autowired.
Code below: Configuration class
#SpringBootApplication
public class EmWebSvcBootApplication extends SpringBootServletInitializer{
public static void main(String[] args) {
SpringApplication.run(EmWebSvcBootApplication.class, args);
}
public static final String SERVICE_NAME_URL_PATH = "/em";
public static final String EM_ISSUER_SERVICE_NAME_URL_PATH = "/EntitlementIssuer";
#Bean(name=Bus.DEFAULT_BUS_ID)
public SpringBus springBus() {
return new SpringBus();
}
#Bean
public ServletRegistrationBean cxfServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new CXFServlet(), SERVICE_NAME_URL_PATH + "/*");
// Add custom Title to CXF´s ServiceList
Map<String, String> initParameters = servletRegistrationBean.getInitParameters();
initParameters.put("service-list-title", "My Test service");
return servletRegistrationBean;
}
#Bean
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(springBus(), new EntitlementIssuerEndpointImpl());
endpoint.setServiceName(entitlementIssuer_Service().getServiceName());
endpoint.setWsdlLocation(entitlementIssuer_Service().getWSDLDocumentLocation().toString());
endpoint.publish(EM_ISSUER_SERVICE_NAME_URL_PATH);
return endpoint;
}
#Bean EntitlementIssuer_Service entitlementIssuer_Service(){
return new EntitlementIssuer_Service();
}
}
Service class:
#Service
public class EntitlementIssuerServiceImpl implements EntitlementIssuerService {
private static final Logger logger = LoggerFactory.getLogger(EntitlementIssuerServiceImpl.class);
#Autowired
private EntitlementIssuerDAO entitlementIssuerDAO;
#Transactional(readOnly=true)
public List<EntitlementIssuerResponseWrapper> getEntitlementIssuers(EntitlementIssuerRequestWrapper requestWrapper)
throws EMSystemException, EMBusinessException {
try{
daoResponse = entitlementIssuerDAO.findEntitlementIssuers(requestWrapper);
}catch(Throwable t){
logger.error("Error while getting entitlement issuers: " + t.getMessage());
throw new EMSystemException("Error while getting entitlement issuers: " + t.getMessage());
}
}
DAO layer:
#Repository
public class EntitlementIssuerDaoImpl implements EntitlementIssuerDAO{
#PersistenceContext
private EntityManager entityManager;
public EntityManager getEntityManager() {
return entityManager;
}
#Override
public List<EntitlementIssuer> findEntitlementIssuers(EntitlementIssuerRequestWrapper request) {
Session session = (Session) entityManager.getDelegate();
Criteria criteria = session.createCriteria(EntitlementIssuer.class, "entitlementIssuer");
setupCriteria(request,criteria);
List<EntitlementIssuer> output = criteria.list();
return output;
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.1.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<em.wsdl.version>2.2.0</em.wsdl.version>
<cxf.version>3.1.7</cxf.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- Apache CXF -->
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-frontend-jaxws</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cxf</groupId>
<artifactId>cxf-rt-transports-http</artifactId>
<version>${cxf.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
I have added all the configurations that i am using, any help to fix this is highly appreciated. Basically all the #Autowired services, data layers, and entity managers are not loaded (as i see them as null in the debug).
I have been able to fix the issue based on the info from https://github.com/codecentric/cxf-spring-boot-starter
/**
* Configuration of SOAP Web services
*/
#Configuration
public class EntitlementIssuerServiceSOAPConfig {
#Autowired
private Bus bus;
#Bean
public EntitlementIssuerService getEntitlementIssuerServiceWebServiceImpl() {
return new EntitlementIssuerServiceWebServiceImpl();
}
#Bean (name = "EntitlementIssuerServiceWebService")
public Endpoint endpoint() {
EndpointImpl endpoint = new EndpointImpl(bus, getEntitlementIssuerServiceWebServiceImpl());
endpoint.publish("/EntitlementIssuerService");
return endpoint;
}
}
I write some code.I want to make questionId field in BaseQuestion Class as Autogenerated.Any solution for that? I am not using jpa jar.so i can't use #Generatedvalue annotation.So how we show here this field is auto generated.
code is below.
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>audit_project</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
BaseQuestion.java
package model;
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "basequestion")
public class BaseQuestion {
#ID
private String id;
private int questionId;
private String responseType;
private boolean required;
private boolean active;
private String questionCode;
private QuestionText questionText;
private String category;
private List<Responses> responses;
public QuestionText getQuestionText() {
return questionText;
}
public void setQuestionText(QuestionText questionText) {
this.questionText = questionText;
}
public List<Responses> getResponses() {
return responses;
}
public void setResponses(List<Responses> responses) {
this.responses = responses;
}
public int getQuestionId() {
return questionId;
}
public void setQuestionId(int questionId) {
this.questionId = questionId;
}
public String getResponseType() {
return responseType;
}
public void setResponseType(String responseType) {
this.responseType = responseType;
}
public boolean getRequired() {
return required;
}
public void setRequired(boolean required) {
this.required = required;
}
public String getQuestionCode() {
return questionCode;
}
public void setQuestionCode(String questionCode) {
this.questionCode = questionCode;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
AuditProjectRepository.java
package repository;
import org.springframework.data.mongodb.repository.MongoRepository;
import model.BaseQuestion;
public interface AuditProjectRepository extends MongoRepository<BaseQuestion, String> {
public BaseQuestion findByQuestionId(int questionId);
public BaseQuestion findByQuestionCode(String questionCode);
public Long deleteByQuestionId(int questionid);
}
MongoDB came with all sophisticated ObjectId generation feature, but often you just jumped the ship from relational database, and you still want an easy to read / communicate numeric identifier field which automatically increments every time new record is inserted.
One neat suggestion from MongoDB tutorial is to use a counter collection with a ‘counter name’ as its id, and a ‘seq’ field to store the last used number.
When developing using Spring Data MongoDB, this neat trick can be written as a simple service. Here I used the collection name as the counter name so it’s easy to guess / remember.
import static org.springframework.data.mongodb.core.FindAndModifyOptions.options;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;
import com.model.CustomSequences;
#Service
public class NextSequenceService {
#Autowired private MongoOperations mongo;
public int getNextSequence(String seqName)
{
CustomSequences counter = mongo.findAndModify(
query(where("_id").is(seqName)),
new Update().inc("seq",1),
options().returnNew(true).upsert(true),
CustomSequences.class);
return counter.getSeq();
}
}
CustomSequences is just a simple class representing the collection. Please beware the usage of int data type, this will limit to 2^31 entries maximum.
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "customSequences")
public class CustomSequences {
#Id
private String id;
private int seq;
// getters and setters
}
Then when inserting a new entry (with help of Spring MongoDB Repository support), just set the id field like this before you save it
BaseQuestion baseQuestion = new BaseQuestion();
baseQuestion.setQuestionId(nextSequenceService.getNextSequence("customSequences"));
/* Rest all values */
baseQuestionRepository.save(baseQuestion);
If you don't like this way then you need to use MongoDBEvents and use onBeforeConvert to generate automated value using same above approach.
Also above approach is threadsafe as findAndModify() is a thread safe atomic method
Using a String for your primary key would be a good idea. With this, you do not configure anything since the strings are automatically generated.
In my Spring Boot web application I use MongoDB to store data. In the application I access the database using interfaces that extend MongoRepository.
How do I set up a unit test for such a repository class? What I would like is to
start an embedded/in memory instance of MongoDB
insert testdata from JSON or XML
use an autowired repository to perform queries on the testdata
I have tried using Embedded MongoDB, but I can't figure out how to insert testdata from a file.
I've also tried using NoSQLUnit, but the SpringApplicationConfiguration conflicts with the unit test configuration, resulting in different databases for reading and writing.
Just use #DataMongoTest from Spring Boot.
#RunWith(SpringRunner.class)
#DataMongoTest
public class FooRepositoryTest {
#Autowired
FooRepository fooRepository;
#Before
public void setUp() throws Exception {
fooRepository.save(new Foo());
}
#Test
public void shouldBeNotEmpty() {
assertThat(fooRepository.findAll()).isNotEmpty();
}
}
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
An update for 2018 and Spring Boot 2. First of all, you can have data-only integration tests with the embedded db as per the documentation. This uses the DataMongoTest annotation. This configures only the necessary dependencies that make mongoDB tests possible.
If you want to do full integration tests, add the AutoConfigureDataMongo annotation instead:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#AutoConfigureDataMongo
public class PriceApiControllerIT {
Dependencies you should have in your pom.xml:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
This is what I did.
public interface UserRepository extends MongoRepository<Users, Long> {
public void deleteByUserId(String userId);
public List<Users> findAllByUserStatus(String userStatus);
}
#Document
public class Users {
#Id
private long id;
#Transient
public static final String SEQUENCE_NAME = "users_sequence";
#Indexed
#NotNull
private String userId;
private String firstName;
private String lastName;
private String userType;
private String userStatus;
#Email
private String emailId;
#Size(min = 10, max = 10)
#NumberFormat
private String phoneNumber;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getUserType() {
return userType;
}
public void setUserType(String userType) {
this.userType = userType;
}
public String getEmailId() {
return emailId;
}
public void setEmailId(String emailId) {
this.emailId = emailId;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public static String getSequenceName() {
return SEQUENCE_NAME;
}
public String getUserStatus() {
return userStatus;
}
public void setUserStatus(String userStatus) {
this.userStatus = userStatus;
}
}
Here is the junit
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = MockodsApplication.class)
#SpringBootTest
#AutoConfigureMockMvc
public class UserControllerIT {
#Autowired
private UserRepository userRepository;
#Autowired
MongoTemplate mongoTemplate;
#Autowired
private MockMvc mvc;
#After
public void tearDown() {
}
#Test
public void test1() {
Users user = new Users();
long userId = 1L;
user.setId(userId);
user.setFirstName("FirstName");
user.setLastName("FirstName");
user.setEmailId("fisrtname.secondname#gmail.com");
user.setPhoneNumber("1234567890");
assertEquals(user, userRepository.save(user));
}
#Test
public void test2() {
List<Users> persistedUser = userRepository.findAll();
assertEquals("fisrtname.secondname#gmail.com", persistedUser.get(0).getEmailId());
}
}
This link helped me to implement https://dzone.com/articles/spring-integration-tests
I faced the same problem and we used a separate MongoConfiguration class to specify a particular configuration for our tests.
You can create an embedded mongo instance by using EmbeddedMongoBuilder like this :
import com.mongodb.Mongo;
import cz.jirutka.spring.embedmongo.EmbeddedMongoBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
import java.net.ServerSocket;
#Configuration
public class MongoConfiguration {
#Bean
public Mongo mongo() throws IOException {
System.setProperty("DB.TRACE","true");
return new EmbeddedMongoBuilder()
.version("2.13.1")
.bindIp("127.0.0.1")
.port(allocateRandomPort())
.build();
}
}
Then in your test class, specify that you want to use that particular configuration with the #Import annotation :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = Application.class)
#Import(MongoConfiguration.class)
Hope that helps.
Check out https://mongoUnit.org
From its docs:
It's a data driven Integration testing framework for Spring Boot
based applications that use MongoDB for persistence. The framework
enables the developer to test the data access logic with relative
ease.
First, make sure that you have added the following Spring Boot parent to your project:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.2.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
Since we added Spring Boot parent, we can add required dependencies without specifying their versions:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
spring-boot-starter-data-mongodb will enable Spring support for
MongoDB
<dependency>
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
<scope>test</scope>
</dependency>
de.flapdoodle.embed.mongo provides embedded MongoDB for integration
tests
After adding de.flapdoodle.embed.mongo dependency Spring Boot will automatically try to download and start the embedded MongoDB when running tests. The following snippet shows how you can configure the embedded MongoDB instance manually
class EmbeddedMongoDbIntegrationTest {
private MongodExecutable mongodExecutable;
private MongoTemplate mongoTemplate;
#After
void clean() {
mongodExecutable.stop();
}
#Before
void setup() throws Exception {
String ip = "localhost";
int port = 27017;
IMongodConfig mongodConfig = new MongodConfigBuilder().version(Version.Main.PRODUCTION)
.net(new Net(ip, port, Network.localhostIsIPv6()))
.build();
MongodStarter starter = MongodStarter.getDefaultInstance();
mongodExecutable = starter.prepare(mongodConfig);
mongodExecutable.start();
mongoTemplate = new MongoTemplate(new MongoClient(ip, port), "test");
}
#Test
void test() throws Exception {
// given
DBObject objectToSave = BasicDBObjectBuilder.start()
.add("key", "value")
.get();
// when
mongoTemplate.save(objectToSave, "collection");
// then
assertThat(mongoTemplate.findAll(DBObject.class, "collection")).extracting("key")
.containsOnly("value");
}
}
Note, that we can quickly create MongoTemplate bean configured to use
our manually configured embedded database and register it inside the
Spring container, so your mongo repository will start leveraging this
mongoTemplate as well.