I tried to create an abstract Dao. I use Spring + Hibernate.
Here's my code.
Main class with configuration:
package ru.makaek.growbox.api;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.hibernate5.HibernateTransactionManager;
import org.springframework.orm.hibernate5.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
#ComponentScan(value = "ru.makaek.growbox")
#EnableAutoConfiguration(exclude = HibernateJpaAutoConfiguration.class)
#EnableTransactionManagement
#SpringBootApplication
public class Application {
#Autowired
private Environment env;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty("datasource.driver"));
dataSource.setUrl(env.getRequiredProperty("datasource.url"));
dataSource.setUsername(env.getRequiredProperty("datasource.username"));
dataSource.setPassword(env.getRequiredProperty("datasource.password"));
return dataSource;
}
#Bean
public LocalSessionFactoryBean getSessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setPackagesToScan(new String[]{"ru.makaek.growbox"});
return sessionFactory;
}
#Bean
public HibernateTransactionManager getTransactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
}
Rest controller
package ru.makaek.growbox.api.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import ru.makaek.growbox.api.model.data.entities.Device;
import ru.makaek.growbox.api.service.IStructureService;
#RestController
public class DeviceController extends AbstractController {
#Autowired
IStructureService structureService;
#RequestMapping(value = "/devices", method = RequestMethod.POST)
public Answer addDevice(#RequestBody Device device) {
structureService.addDevice(device);
return ok("Device has been added");
}
#RequestMapping(value = "/devices", method = RequestMethod.GET)
public Answer getDevices() {
return ok(structureService.getDevices());
}
#RequestMapping(value = "/devices/{deviceId}", method = RequestMethod.GET)
public Answer getDevice(#PathVariable Long deviceId) {
return ok(structureService.getDevice(deviceId));
}
}
Service layer. Interface
package ru.makaek.growbox.api.service;
import ru.makaek.growbox.api.model.data.entities.Device;
import java.util.List;
public interface IStructureService {
void addDevice(Device device);
List<Device> getDevices();
Device getDevice(Long deviceId);
}
Service layer. Implementation
package ru.makaek.growbox.api.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import ru.makaek.growbox.api.model.data.dao.base.IDao;
import ru.makaek.growbox.api.model.data.entities.Device;
import java.util.List;
#Service
#Transactional
public class StructureService implements IStructureService {
IDao<Device> deviceDao;
#Autowired
public void setDao(IDao<Device> dao) {
deviceDao = dao;
dao.setClazz(Device.class);
}
#Override
public void addDevice(Device device) {
deviceDao.create(device);
}
#Override
public List<Device> getDevices() {
return deviceDao.findAll();
}
#Override
public Device getDevice(Long deviceId) {
return deviceDao.findOne(deviceId);
}
}
Entity
package ru.makaek.growbox.api.model.data.entities;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
#Entity(name = "devices")
#Data public class Device extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
DAO. Interface
package ru.makaek.growbox.api.model.data.dao.base;
import ru.makaek.growbox.api.model.data.entities.BaseEntity;
import java.util.List;
public interface IDao<T extends BaseEntity> {
T findOne(final long id);
void setClazz(Class<T> clazz);
List<T> findAll();
void create(final T entity);
T update(final T entity);
void delete(final T entity);
void deleteById(final long entityId);
}
Abstract DAO
package ru.makaek.growbox.api.model.data.dao.base;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import ru.makaek.growbox.api.model.data.entities.BaseEntity;
import ru.makaek.growbox.api.util.GBException;
import java.util.List;
public abstract class AbstractDao<T extends BaseEntity> implements IDao<T> {
private Class<T> clazz;
#Autowired
private SessionFactory sessionFactory;
public final void setClazz(Class<T> clazz) {
this.clazz = clazz;
}
public T findOne(long id) {
try {
return (T) getCurrentSession().get(clazz, id);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public List<T> findAll() {
try {
return getCurrentSession().createQuery("from " + clazz.getName()).list();
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void create(T entity) {
try {
getCurrentSession().persist(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public T update(T entity) {
try {
return (T) getCurrentSession().merge(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void delete(T entity) {
try {
getCurrentSession().delete(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
public void deleteById(long entityId) {
try {
T entity = findOne(entityId);
delete(entity);
} catch (Exception e) {
throw new GBException.InternalError(e.getMessage());
}
}
protected final Session getCurrentSession() {
return sessionFactory.getCurrentSession();
}
}
DAO. Implementation
package ru.makaek.growbox.api.model.data.dao;
import org.springframework.stereotype.Repository;
import ru.makaek.growbox.api.model.data.dao.base.AbstractDao;
import ru.makaek.growbox.api.model.data.entities.Device;
#Repository
public class DeviceDao extends AbstractDao<Device> {
}
I have one trouble. When I call GET http://host:port/devices API method I have null in the clazz variable in the AbstractDao.findAll() method. When I was debugging the code i found one interesting thing: in the service layer method deviceDao.getClazz() returned needed clazz (not null). But in method AbstractDao.findAll() I have null in clazz variable. Why? Please help.
Sorry for my English and formulation. I'm new in this site, Spring and English
You are overcomplicating things. Because you are using Spring Boot it is possible to just create generic interface that extends CrudRepository and add the methods you need and are not already present in there.
Take a look here https://docs.spring.io/spring-data/data-commons/docs/1.6.1.RELEASE/reference/html/repositories.html
Related
I am trying to get my OrderController to work, but MVC can't seem to find it. Does anyone know why this happens?
Initializer class. The getServletMapping method keeps notifying me about Not annotated method overrides method annotated with #NonNullApi
package configs;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{Config.class};
}
#Override
protected String[] getServletMappings() {
return new String[] { "/api/*" };
}
}
The config class.
package configs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.sql.DataSource;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"model"})
#PropertySource("classpath:/application.properties")
public class Config {
#Bean
public JdbcTemplate getTemplate(DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean
public DataSource dataSource(Environment env) {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.hsqldb.jdbcDriver");
ds.setUrl(env.getProperty("hsql.url"));
var populator = new ResourceDatabasePopulator(
new ClassPathResource("schema.sql"),
new ClassPathResource("data.sql")
);
DatabasePopulatorUtils.execute(populator, ds);
return ds;
}
}
Then the controller.
package controllers;
import model.Order;
import model.OrderDAO;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class OrderController {
private OrderDAO orderdao;
public OrderController(OrderDAO orderDAO) {
this.orderdao = orderDAO;
}
#PostMapping("orders")
#ResponseStatus(HttpStatus.CREATED)
public Order saveOrder(#RequestBody Order order) {
return orderdao.addOrder(order);
}
#GetMapping("orders/{id}")
public Order getOrderById(#PathVariable Long id) {
return orderdao.getOrderById(id);
}
#GetMapping("orders")
public List<Order> getOrders() {
return orderdao.getAllOrders();
}
#DeleteMapping("orders/{id}")
public void deleteOrderById(#PathVariable Long id) {
orderdao.deleteOrderById(id);
}
}
Everything seems fine, I can't find the issue.
Since the OrderController is in the controllers package, I forgot to add the package in the configs componentScan parameters.
Your controller registers the endpoints at /orders/*, but in getServletMappings you specify the /api/*. You should add /api prefix to your controller, like this
#RestController
#RequestMapping("api")
public class OrderController {
...
I'm trying to persist a cassandra entity but on startup I get:
Caused by: org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type class com.service.model.Cart!
This is my entity class:
package com.service.model;
import io.vavr.collection.LinkedHashMap;
import io.vavr.collection.Map;
import lombok.Getter;
import lombok.ToString;
import org.springframework.data.cassandra.core.mapping.PrimaryKey;
import org.springframework.data.cassandra.core.mapping.Table;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
#ToString
#Table("carts")
public class Cart {
#Getter
#PrimaryKey
private final UUID uuid;
private final Map<CartItemKey, CartItem> items;
public Cart(UUID uuid) {
this(uuid, LinkedHashMap.empty());
}
private Cart(UUID uuid, Map<CartItemKey, CartItem> items) {
this.uuid = Objects.requireNonNull(uuid, "Cart's uuid cannot be null");
this.items = Objects.requireNonNull(items, "Cart's items cannot be null");
}
}
This is my CassandraConfig:
package com.service.configuration;
import com.service.model.Cart;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.cassandra.config.AbstractClusterConfiguration;
import org.springframework.data.cassandra.core.convert.CassandraCustomConversions;
import org.springframework.data.cassandra.core.cql.keyspace.CreateKeyspaceSpecification;
import org.springframework.data.cassandra.core.cql.keyspace.KeyspaceOption;
import org.springframework.util.StringUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import static java.util.Collections.singletonList;
#Configuration
public class CassandraConfig extends AbstractClusterConfiguration {
#Value("${spring.data.cassandra.keyspace-name}")
private String keyspaceName;
#Value("${spring.data.cassandra.contact-points}")
private String contactPoints;
#Override
protected List<CreateKeyspaceSpecification> getKeyspaceCreations() {
return singletonList(
CreateKeyspaceSpecification.createKeyspace(keyspaceName)
.ifNotExists()
.with(KeyspaceOption.DURABLE_WRITES, true)
.withSimpleReplication());
}
#Override
protected boolean getMetricsEnabled() {
return false;
}
#Override
protected String getContactPoints() {
return contactPoints;
}
#Bean
public CassandraCustomConversions customConversions() {
List<Converter<?, ?>> converters = new ArrayList<>();
converters.add(new CartWriteConverter());
converters.add(new CartReadConverter());
return new CassandraCustomConversions(converters);
}
static class CartWriteConverter implements Converter<Cart, String> {
public String convert(Cart source) {
try {
return new ObjectMapper().writeValueAsString(source);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
static class CartReadConverter implements Converter<String, Cart> {
public Cart convert(String source) {
if (StringUtils.hasText(source)) {
try {
return new ObjectMapper().readValue(source, Cart.class);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return null;
}
}
}
And lastly my Application:
package com.service.cart;
import org.axonframework.springboot.autoconfig.AxonServerAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.data.cassandra.repository.config.EnableCassandraRepositories;
import org.springframework.scheduling.annotation.EnableAsync;
#EnableCaching
#EnableAsync
#EnableFeignClients
#SpringBootApplication
#EnableAutoConfiguration(exclude = {
DataSourceAutoConfiguration.class,
DataSourceTransactionManagerAutoConfiguration.class,
HibernateJpaAutoConfiguration.class,
AxonServerAutoConfiguration.class})
#EnableCassandraRepositories(basePackages = "com.service.repository")
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
What seems puzzling to me is that when I remove customConversions() bean it fails with a different error - not being able to map vavr Map, so spring must have scanned and registered this entity so that it got inspected. This is expected while it is not cassandra data type but in my understanding adding my custom conversions should solve this problem.
I also tried experimenting with #EntityScan with the same results.
Any help would be appreciated.
Here , I want to make a SpringBoot and MyBatis application use dynamic datasource by AOP; But the AOP is always execute after query from database, so switch datasource is invalid because select is finished.
All my code is in https://github.com/helloworlde/SpringBoot-DynamicDataSource/tree/aspect_dao
My dependence is
compile('org.mybatis.spring.boot:mybatis-spring-boot-starter:1.3.1')
compile('org.springframework.boot:spring-boot-starter-web')
compile('org.springframework.boot:spring-boot-starter-aop')
runtime('mysql:mysql-connector-java')
And application.properties
application.server.db.master.driver-class-name=com.mysql.jdbc.Driver
application.server.db.master.url=jdbc:mysql://localhost/redisapi?useSSL=false
application.server.db.master.port=3306
application.server.db.master.username=root
application.server.db.master.password=ihaveapen*^##
#application.server.db.master.database=123456
#
## application common config
application.server.db.slave.driver-class-name=com.mysql.jdbc.Driver
application.server.db.slave.url=jdbc:mysql:/localhost/redisapi2?useSSL=false
application.server.db.slave.port=3306
application.server.db.slave.username=root
application.server.db.slave.password=123456
#application.server.db.slave.database=redisapi
mybatis.type-aliases-package=cn.com.hellowood.dynamicdatasource.mapper
mybatis.mapper-locations=mappers/**Mapper.xml
Table
CREATE TABLE product(
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
price DOUBLE(10,2) NOT NULL DEFAULT 0
);
DataSourceConfigur.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
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.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
#Configuration
public class DataSourceConfigurer {
#Bean("master")
#Primary
#ConfigurationProperties(prefix = "application.server.db.master")
public DataSource master() {
return DataSourceBuilder.create().build();
}
#Bean("slave")
#ConfigurationProperties(prefix = "application.server.db.slave")
public DataSource slave() {
return DataSourceBuilder.create().build();
}
#Bean("dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicRoutingDataSource dynamicRoutingDataSource = new DynamicRoutingDataSource();
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("master", master());
dataSourceMap.put("slave", slave());
// Set master datasource as default
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
// Set master and slave datasource as target datasource
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
// To put datasource keys into DataSourceContextHolder to judge if the datasource is exist
DynamicDataSourceContextHolder.dataSourceKeys.addAll(dataSourceMap.keySet());
return dynamicRoutingDataSource;
}
#Bean
#ConfigurationProperties(prefix = "mybatis")
public SqlSessionFactoryBean sqlSessionFactoryBean() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// Here is very important, if don't config this, will can't switch datasource
// put all datasource into SqlSessionFactoryBean, then will autoconfig SqlSessionFactory
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
}
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
DynamicRoutingDataSource.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicRoutingDataSource extends AbstractRoutingDataSource {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Override
protected Object determineCurrentLookupKey() {
logger.info("Current DataSource is [{}]", DynamicDataSourceContextHolder.getDataSourceKey());
return DynamicDataSourceContextHolder.getDataSourceKey();
}
}
DynamicDataSourceContextHolder.java
package cn.com.hellowood.dynamicdatasource.configuration;
import java.util.ArrayList;
import java.util.List;
public class DynamicDataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>() {
#Override
protected String initialValue() {
return "master";
}
};
public static List<Object> dataSourceKeys = new ArrayList<>();
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
public static String getDataSourceKey() {
return contextHolder.get();
}
public static void clearDataSourceKey() {
contextHolder.remove();
}
public static boolean containDataSourceKey(String key) {
return dataSourceKeys.contains(key);
}
}
DynamicDataSourceAspect.java
package cn.com.hellowood.dynamicdatasource.configuration;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
#Aspect
#Order(-100) // To ensure execute before #Transactional
#Component
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
private final String QUERY_PREFIX = "select";
#Pointcut("execution( * cn.com.hellowood.dynamicdatasource.mapper.*.*(..))")
public void daoAspect() {
}
#Before("daoAspect()")
public void switchDataSource(JoinPoint point) {
if (point.getSignature().getName().startsWith(QUERY_PREFIX)) {
DynamicDataSourceContextHolder.setDataSourceKey("slave");
logger.info("Switch DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
#After("daoAspect())")
public void restoreDataSource(JoinPoint point) {
DynamicDataSourceContextHolder.clearDataSourceKey();
logger.info("Restore DataSource to [{}] in Method [{}]",
DynamicDataSourceContextHolder.getDataSourceKey(), point.getSignature());
}
}
And have Controller, Service and Dao for query, But although I set Order of aspect as -100, it still execute query before AOP, could anyone find where is wrong, Thank you very much.
This is log screenshot
Finally I fixed this issue, Because I injected Bean of DataSourceTransactionManager, So transaction will be open in Service, so the aspect of DAO is not work until transaction finished.
Delete this code:
#Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
I need to implement soft delete functionality(Maintain a boolean field in table and filter all query based on this).
Below link has solution for hibernate only.
Handling soft-deletes with Spring JPA
Since my application is very old, I don't want to change each existing query. I am looking for solution like one place change in spring data classes.
Spring mongo data version: 1.5.0.RELEASE
Add Boolean Field active to every class which is mapped with Collection
set the same true for all valid Documents and false for non valid documnets
private Boolean active = Boolean.TRUE;
and can chnage your Query to
Long countByActiveTrueAndAccountStatusNot(AccountStatus status);
First Step. Override default methods like as findAll(), findById(), exists().... For this you should override mongoTemplate, it simple).
Add to your entities field "deletedAt":
#Document("costAreas")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#FieldDefaults(level = AccessLevel.PRIVATE)
#Builder
public class User{
#Id
String id;
String name;
LocalDateTime deletedAt;
}
PS: Filed "deletedAt" contains the date of deletion (if this field is null then document wasn't deleted).
Create CustomMongoTemplate:
import com.mongodb.client.MongoClient;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.MongoConverter;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
public class CustomMongoTemplate extends MongoTemplate {
public CustomMongoTemplate(MongoTemplate mongoTemplate) {
super(mongoTemplate.getMongoDatabaseFactory());
}
public CustomMongoTemplate(MongoClient mongoClient, String databaseName) {
super(mongoClient, databaseName);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory) {
super(mongoDbFactory);
}
public CustomMongoTemplate(MongoDatabaseFactory mongoDbFactory, MongoConverter mongoConverter) {
super(mongoDbFactory, mongoConverter);
}
#Override
public <T> List<T> find(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.find(query, entityClass, collectionName);
}
#Nullable
#Override
public <T> T findById(Object id, Class<T> entityClass, String collectionName) {
T t = super.findById(id, entityClass, collectionName);
try {
Field field = entityClass.getDeclaredField("deletedAt");
field.setAccessible(Boolean.TRUE);
if (Objects.nonNull(field.get(t))) {
return null;
}
} catch (NoSuchFieldException | IllegalAccessException ignored) {
}
return t;
}
#Nullable
#Override
public <T> T findOne(Query query, Class<T> entityClass, String collectionName) {
Assert.notNull(query, "Query must not be null!");
Assert.notNull(entityClass, "EntityClass must not be null!");
Assert.notNull(collectionName, "CollectionName must not be null!");
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.findOne(query, entityClass, collectionName);
}
#Override
#SuppressWarnings("ConstantConditions")
public boolean exists(Query query, #Nullable Class<?> entityClass, String collectionName) {
if (query == null) {
throw new InvalidDataAccessApiUsageException("Query passed in to exist can't be null");
}
query.addCriteria(Criteria.where("deletedAt").exists(Boolean.FALSE));
return super.exists(query, entityClass, collectionName);
}
// You can also override ```delete()``` method, but I decided to not to do this
// Maybe here should add other methods: count, findAndModify and ect. It depends which methods you going to use.
}
Then create Bean in configuration class:
#Configuration
public class MyConfiguration {
//...
#Bean(name = "mongoTemplate")
CustomMongoTemplate customMongoTemplate(MongoDatabaseFactory databaseFactory, MappingMongoConverter converter) {
return new CustomMongoTemplate(databaseFactory, converter);
}
//...
}
And allow Spring to override default MongoTemplate bean. Add next thing to your application.properties file:
spring.main.allow-bean-definition-overriding=true
Replace delete() with set deletedAt:
// Deletion method
// Before
User = userRepository.findById(id);
userRepository.delete(user);
// Now
User = userRepository.findById(id);
user.setDeletedAt(LocalDateTime.now());
userRepository.save(user);
Second Step. Implement soft delete for method in Repositories (generated by JPA) like as findAllByEmail(String email), existsByNameAndUsername(String name, String username)....
Resource: https://blog.rpuch.com/2019/10/27/spring-data-mongo-soft-delete-repositories.html
SoftDeleteMongoQueryLookupStrategy
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor;
import org.springframework.data.mongodb.repository.query.PartTreeMongoQuery;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import java.lang.reflect.Method;
public class SoftDeleteMongoQueryLookupStrategy implements QueryLookupStrategy {
private final QueryLookupStrategy strategy;
private final MongoOperations mongoOperations;
private final QueryMethodEvaluationContextProvider evaluationContextProvider;
public SoftDeleteMongoQueryLookupStrategy(QueryLookupStrategy strategy,
MongoOperations mongoOperations,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
this.strategy = strategy;
this.mongoOperations = mongoOperations;
this.evaluationContextProvider = evaluationContextProvider;
}
#Override
public RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory,
NamedQueries namedQueries) {
RepositoryQuery repositoryQuery = strategy.resolveQuery(method, metadata, factory, namedQueries);
// revert to the standard behavior if requested
if (method.getAnnotation(SeesSoftlyDeletedRecords.class) != null) {
return repositoryQuery;
}
if (!(repositoryQuery instanceof PartTreeMongoQuery)) {
return repositoryQuery;
}
PartTreeMongoQuery partTreeQuery = (PartTreeMongoQuery) repositoryQuery;
return new SoftDeletePartTreeMongoQuery(partTreeQuery);
}
private Criteria notDeleted() {
return new Criteria().andOperator(
Criteria.where("deletedAt").exists(false)
);
}
private class SoftDeletePartTreeMongoQuery extends PartTreeMongoQuery {
SoftDeletePartTreeMongoQuery(PartTreeMongoQuery partTreeQuery) {
super(partTreeQuery.getQueryMethod(), mongoOperations, new SpelExpressionParser(), evaluationContextProvider);
}
#Override
protected Query createQuery(ConvertingParameterAccessor accessor) {
Query query = super.createQuery(accessor);
return withNotDeleted(query);
}
#Override
protected Query createCountQuery(ConvertingParameterAccessor accessor) {
Query query = super.createCountQuery(accessor);
return withNotDeleted(query);
}
private Query withNotDeleted(Query query) {
return query.addCriteria(notDeleted());
}
}
}
SeesSoftlyDeletedRecords (if you marked method annotation then method will ignore soft-deletion)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface SeesSoftlyDeletedRecords {
}
SoftDeleteMongoRepositoryFactory
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactory;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import java.util.Optional;
public class SoftDeleteMongoRepositoryFactory extends MongoRepositoryFactory {
private final MongoOperations mongoOperations;
public SoftDeleteMongoRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.mongoOperations = mongoOperations;
}
#Override
protected Optional<QueryLookupStrategy> getQueryLookupStrategy(QueryLookupStrategy.Key key,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
Optional<QueryLookupStrategy> optStrategy = super.getQueryLookupStrategy(key,
evaluationContextProvider);
return Optional.of(createSoftDeleteQueryLookupStrategy(optStrategy.get(), evaluationContextProvider));
}
private SoftDeleteMongoQueryLookupStrategy createSoftDeleteQueryLookupStrategy(QueryLookupStrategy strategy,
QueryMethodEvaluationContextProvider evaluationContextProvider) {
return new SoftDeleteMongoQueryLookupStrategy(strategy, mongoOperations, evaluationContextProvider);
}
}
SoftDeleteMongoRepositoryFactoryBean
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.data.mongodb.repository.support.MongoRepositoryFactoryBean;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import java.io.Serializable;
public class SoftDeleteMongoRepositoryFactoryBean<T extends Repository<S, ID>, S, ID extends Serializable>
extends MongoRepositoryFactoryBean<T, S, ID> {
public SoftDeleteMongoRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new SoftDeleteMongoRepositoryFactory(operations);
}
}
Add it to configuration
#Configuration
#EnableMongoRepositories(basePackages = {"path to package with your repositories"}, repositoryFactoryBeanClass = SoftDeleteMongoRepositoryFactoryBean.class)
public class MyConfiguration {
//...
}
Hope it will help someone)
I am trying to write basic spring project. However, I have a big issue using `#Autowired through the Service. I didn't find the problem and got the error bellow:
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [com.blackfirday.services.UserService] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoSuchBeanDefinitionException(DefaultListableBeanFactory.java:1373) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1119) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:813) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.2.5.RELEASE.jar:4.2.5.RELEASE]
... 19 common frames omitted
Here is my code:
UserServiceInterface
package com.blackfirday.services;
import com.blackfriday.generated.User;
public interface UserService {
public User userLogin(final String userId, final String userPassword);
}
UserServiceImpl
package com.blackfirday.services;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.blackfriday.dao.UserDao;
import com.blackfriday.generated.User;
#Service("userService")
public class UserServiceImpl implements UserService {
#Autowired(required=true)
#Qualifier(value="userDao")
private UserDao userDao_;
#Override
public User userLogin(String userId, String userPassword) {
// TODO: Varify the user password in the returned user object.
return userDao_.getUserDetails(userId);
}
}
UserDao Interface
package com.blackfriday.dao;
import com.blackfriday.generated.User;
public interface UserDao {
public User getUserDetails(final String userId);
}
UserDao Impl
package com.blackfriday.dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.springframework.stereotype.Repository;
import com.blackfriday.generated.User;
#Repository("userDao")
public class UserDaoImpl implements UserDao {
private final String connectionURL = "jdbc:msql://localhost:3306/blackfridaybank";
private static final String DRIVER_PREFIX = "jdbc:apache:commons:dbcp:";
#Override
public User getUserDetails(String userId) {
// TODO Make the database as centralized connection pool.
Connection conn = null;
User user = new User();
try {
conn = getConnection(connectionURL);
conn.setAutoCommit(true);
Statement stmnt = conn.createStatement();
ResultSet rs = stmnt.executeQuery("select * from user where id="+userId);
while(rs.next()){
user.setUserId(rs.getString("id"));
user.setUserName(rs.getString("name"));
user.setUserGender(rs.getString("gender"));
user.setUserStatus(rs.getString("status"));
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try{
conn.close();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return user;
}
private Connection getConnection(final String connectURL) throws SQLException {
return DriverManager.getConnection(DRIVER_PREFIX + connectURL, "blackfriday", "blackfriday");
}
}
UserServicesEndPoint
package com.blackfriday.webservices.endpoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;
import com.blackfirday.services.UserService;
import com.blackfriday.generated.UserLoginRequest;
import com.blackfriday.generated.UserLoginResponse;
#Endpoint
public class UserServicesEndPoint {
private static final String USER_LOGIN_REQUEST = "http://www.blackfridaybank.org/webservices/UserOperation";
private UserService userService;
#Autowired
public UserServicesEndPoint(UserService userService_i){
userService = userService_i;
}
#PayloadRoot(localPart = "UserLoginRequest", namespace = USER_LOGIN_REQUEST)
#ResponsePayload
public UserLoginResponse getUserDetails(#RequestPayload UserLoginRequest request) {
// 1.we need to make an instance of a response
UserLoginResponse response = new UserLoginResponse();
// 2.use the request data to in the services class and populated to
// response
response.setUserDetails(userService.userLogin(request.getUserId(), request.getPassword()));
// 3.return a data populated response.
return response;
}
}
Startup class
package com.blackfriday.startup;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan(basePackages={"com.blackfriday"})
public class Startup {
public static void main(String[] args) {
SpringApplication.run(Startup.class, args);
}
}
Web Config
package com.blackfriday.startup;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ws.config.annotation.EnableWs;
import org.springframework.ws.config.annotation.WsConfigurerAdapter;
import org.springframework.ws.transport.http.MessageDispatcherServlet;
import org.springframework.ws.wsdl.wsdl11.DefaultWsdl11Definition;
import org.springframework.xml.xsd.SimpleXsdSchema;
import org.springframework.xml.xsd.XsdSchema;
#EnableWs
#Configuration
public class WebSerivceConfig extends WsConfigurerAdapter{
#Bean
public ServletRegistrationBean messageDispatcherServlet (ApplicationContext context) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(context);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean(servlet, "/ws/*");
}
#Bean(name="useroperation")
public DefaultWsdl11Definition defaultWsdl11Definition (XsdSchema UserOperation) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("useroperationport");
wsdl11Definition.setLocationUri("/ws");
wsdl11Definition.setTargetNamespace("http://www.blackfridaybank.org/webservices/UserOperation");
wsdl11Definition.setSchema(UserOperation);
return wsdl11Definition;
}
#Bean
public XsdSchema UserOperation() {
return new SimpleXsdSchema(new ClassPathResource("UserOperation.xsd"));
}
//adding the following method to return the correct xsd file before loaded to the soapui.
#Bean
public XsdSchema UserDetails() {
return new SimpleXsdSchema(new ClassPathResource("UserDetails.xsd"));
}
}