Hot questions for Using Cassandra in spring mvc

Question:

I'm quite a newbie to Spring boot, but here's the problem I'm facing now:

// Application.java
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Autowired
  private Cluster cluster = null;

  @PostConstruct
  private void migrateCassandra() {
    Database database = new Database(this.cluster, "foo");
    MigrationTask migration = new MigrationTask(database, new MigrationRepository());
    migration.migrate();
  }
}

So basically, I'm trying to bootstrap a spring application, and after that, do some cassandra migrations.

I also have defined a repository for my user model:

// UserRepo.java
public interface UserRepo extends CassandraRepository<User> {
}

Now I'm trying to test my repo class using the following simple test case:

// UserRepoTest.java
@RunWith(SpringRunner.class)
@AutoConfigureTestDatabase(replace = Replace.NONE)
@DataJpaTest
public class UserRepoTest {

  @Autowired
  private UserRepo userRepo = null;

  @Autowired
  private TestEntityManager entityManager = null;

  @Test
  public void findOne_whenUserExists_thenReturnUser() {
    String id = UUID.randomUUID().toString();
    User user = new User();
    user.setId(id);
    this.entityManager.persist(user);

    assertEquals(this.userRepo.findOne(user.getId()).getId(), id);
  }

  @Test
  public void findOne_whenUserNotExists_thenReturnNull() {
    assertNull(this.userRepo.findOne(UUID.randomUUID().toString()));
  }
}

I would expect the test to pass, but instead, I got an error saying "No qualifying bean of type 'com.datastax.driver.core.Cluster' available". It looks like spring failed to autowire the cluster object, but why is that? How do I fix this? Thanks a lot!


Answer:

The test environment needs to know where your beans are defined, so you have to tell it the location.

In your test class, add the @ContextConfiguration annotation:

@RunWith(SpringRunner.class)
@AutoConfigureTestDatabase(replace = Replace.NONE)
@DataJpaTest
@ContextConfiguration(classes = {YourBeans.class, MoreOfYourBeans.class})
public class UserRepoTest {

  @Autowired
  private UserRepo userRepo = null;

  @Autowired
  private TestEntityManager entityManager = null;

Question:

i want to change how my app access cassandra db. I was using spring data for cassandra but because it is not supporting UDT and other stuff i have decided to use datastax driver with DAO pattern.

But i have some problems, psring is not initializing the beans for datastax driver and i don't know where is the mistake.

this is the error:

Caused by: org.springframework.beans.factory.BeanCreationException: Could not autowire field: private ro.automotive.app.core.DAO.interfaces.BaseDAO ro.automotive.app.core.service.BaseServiceImpl.baseDAO; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'usersDAOImpl' defined in file [E:\Proiecte Intellij\Stefan\automotive\target\ROOT\WEB-INF\classes\ro\automotive\app\core\DAO\UsersDAOImpl.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [ro.automotive.app.core.DAO.UsersDAOImpl]: Constructor threw exception; nested exception is java.lang.NullPointerException
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:573)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:331)
... 72 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'usersDAOImpl' defined in file [E:\Proiecte Intellij\Stefan\automotive\target\ROOT\WEB-INF\classes\ro\automotive\app\core\DAO\UsersDAOImpl.class]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [ro.automotive.app.core.DAO.UsersDAOImpl]: Constructor threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1105)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1050)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:510)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
    at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.findAutowireCandidates(DefaultListableBeanFactory.java:1192)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1116)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1014)
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:545)
    ... 74 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [ro.automotive.app.core.DAO.UsersDAOImpl]: Constructor threw exception; nested exception is java.lang.NullPointerException
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:163)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:89)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1098)
    ... 85 more
Caused by: java.lang.NullPointerException
    at ro.automotive.app.core.DAO.BaseDAOImpl.<init>(BaseDAOImpl.java:29)
    at ro.automotive.app.core.DAO.UsersDAOImpl.<init>(UsersDAOImpl.java:13)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
    at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
    ... 87 more

Here is my Config files:

@Configuration
@EnableWebMvc
@ComponentScan({
        "ro.automotive.app.core.controller",
        "ro.automotive.app.admin.controller",
        "ro.automotive.app.autoService.controller",
        "ro.automotive.app.carPartsSupplier.controller",

        "ro.automotive.app.core.service",
        "ro.automotive.app.core.DAO"
})
@Import({CassandraConfig.class, SpringSecurityConfig.class})
public class AppConfig extends WebMvcConfigurerAdapter {


    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/res/**/**.js").addResourceLocations("/WEB-RES/js/");
        registry.addResourceHandler("/res/**/**.css").addResourceLocations("/WEB-RES/css/");
        registry.addResourceHandler("/res/img/**/**/**.png").addResourceLocations("/WEB-RES/img/");
        registry.addResourceHandler("/**/**.ico").addResourceLocations("/WEB-RES/img/favicon.ico");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
    public ViewResolver viewResolver(ContentNegotiationManager manager) {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }


}

Cassandra config:

@Configuration
public class CassandraConfig{

    @Bean
    public Cluster cluster(){
        PoolingOptions poolingOptions = new PoolingOptions();
        Cluster cluster = Cluster.builder()
                .withClusterName("develop")
                .addContactPoint("localhost")
                .withPort(9042)
                .build();
        return cluster;
    }

    @Bean
    public MappingManager mappingManager() throws Exception {
        MappingManager mappingManager = new MappingManager(session());
        return mappingManager;
    }

    @Bean
    public Session session() throws Exception {
        Session session = cluster().connect("automotive");
        return session;
    }

}

And BaseDAO where mapping manager is null :

public abstract class BaseDAOImpl<T> implements BaseDAO<T> {

    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    protected MappingManager manager;

    protected Mapper<T> mapper;

    public BaseDAOImpl(Class<T> clazz) {
        mapper = manager.mapper(clazz);
    }

    @Override
    public TableMetadata getTableMetadata() {
        return mapper.getTableMetadata();
    }

    @Override
    public Statement saveQuery(T entity) {
        return mapper.saveQuery(entity);
    }

    @Override
    public Statement saveQuery(T entity, Mapper.Option... options) {
        return mapper.saveQuery(entity, options);
    }

    @Override
    public void save(T entity) {
        mapper.save(entity);
    }

    @Override
    public void save(T entity, Mapper.Option... options) {
        mapper.save(entity, options);
    }

    @Override
    public ListenableFuture<Void> saveAsync(T entity) {
        return mapper.saveAsync(entity);
    }

    @Override
    public ListenableFuture<Void> saveAsync(T entity, Mapper.Option... options) {
        return mapper.saveAsync(entity, options);
    }

    @Override
    public Statement getQuery(Object... objects) {
        return mapper.getQuery(objects);
    }

    @Override
    public T get(Object... objects) {
        return mapper.get(objects);
    }

    @Override
    public ListenableFuture<T> getAsync(Object... objects) {
        return mapper.getAsync(objects);
    }

    @Override
    public Statement deleteQuery(T entity, Mapper.Option... options) {
        return mapper.deleteQuery(entity, options);
    }

    @Override
    public Statement deleteQuery(T entity) {
        return mapper.deleteQuery(entity);
    }

    @Override
    public Statement deleteQuery(Object... objects) {
        return mapper.deleteQuery(objects);
    }

    @Override
    public void delete(T entity) {
        mapper.delete(entity);
    }

    @Override
    public void delete(T entity, Mapper.Option... options) {
        mapper.delete(entity, options);
    }

    @Override
    public ListenableFuture<Void> deleteAsync(T entity) {
        return mapper.deleteAsync(entity);
    }

    @Override
    public ListenableFuture<Void> deleteAsync(T entity, Mapper.Option... options) {
        return mapper.deleteAsync(entity, options);
    }

    @Override
    public void delete(Object... objects) {
        mapper.delete(objects);
    }

    @Override
    public ListenableFuture<Void> deleteAsync(Object... objects) {
        return mapper.deleteAsync(objects);
    }

    @Override
    public Result<T> map(ResultSet resultSet) {
        return mapper.map(resultSet);
    }

    @Override
    public ListenableFuture<Result<T>> mapAsync(ResultSetFuture resultSetFuture) {
        return mapper.mapAsync(resultSetFuture);
    }

    @Override
    public void setDefaultSaveOptions(Mapper.Option... options) {
        mapper.setDefaultSaveOptions(options);
    }

    @Override
    public void resetDefaultSaveOptions() {
        mapper.resetDefaultSaveOptions();
    }

    @Override
    public void setDefaultGetOptions(Mapper.Option... options) {
        mapper.setDefaultGetOptions(options);
    }

    @Override
    public void resetDefaultGetOptions() {
        mapper.resetDefaultGetOptions();
    }

    @Override
    public void setDefaultDeleteOptions(Mapper.Option... options) {
        mapper.setDefaultDeleteOptions(options);
    }

    @Override
    public void resetDefaultDeleteOptions() {
        mapper.resetDefaultDeleteOptions();
    }

    @Override
    public T get(List objects) {
        return mapper.get(objects.toArray());
    }

    @Override
    public ListenableFuture<T> getAsync(List objects) {
        return mapper.getAsync(objects.toArray());
    }
}

Here is UserDAoImpl:

@Repository
public class UsersDAOImpl extends BaseDAOImpl<Users> implements UsersDAO {

    private UsersDAO accessor;

    public UsersDAOImpl() {
        super(Users.class);
        accessor = manager.createAccessor(UsersDAO.class);
    }

    @Override
    public Users findByUsername(String username) {
        return accessor.findByUsername(username);
    }
}

Answer:

You problem is:

[ro.automotive.app.core.DAO.UsersDAOImpl]: Constructor threw exception; nested exception is java.lang.NullPointerException

If spring instantiate a UsersDAOImpl the autowire of the manager in your BaseDAOImpl

@Autowired
protected MappingManager manager;

is not executed at that moment. That's why you are getting NPE.

So do the initialization of your mapper in a @PostConstruct method:

@PostConstruct
public void init(){
    [...]
}

@PostConstruct is something like AfterBeanIsReadyAndAllFieldsAreAutowired

EDIT: Do something like this:

@Repository
public abstract class BaseDAOImpl<T> implements BaseDAO<T> {

    protected final Class<T> ENTITY_CLASS;
    protected final Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    protected MappingManager manager;

    protected Mapper<T> mapper;

    public BaseDAOImpl() {
        // get type of <T> hack...
        this.ENTITY_CLASS = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

    @PostConstruct
    public void init(){
        mapper = manager.mapper(ENTITY_CLASS);
    }

    [...]
}

Question:

my service take a json body and saves it into db my json body looks like : { "id": "1059", "firstName": "max", "lastName": "sam", "emailAddress": "abc@gmail.com", "joiningDate": "2018-07-02T09:55:24.600", "endingDate": "2018-07-02T04:55:24.677" }

in my case date format is "yyyy-MM-dd'T'HH:mm:ss.SSS" and should not allow 'z' at the end of the date. but when i stored it into cassandra the field(timestamp) has the value "2018-07-02 09:55:24.600Z"

i am taking json date fields as string in the pojo and parsing as date then validating them and my validation looks like

public class DateValidator  {

    @Override
    public boolean isValid(String field) {

        if (field == null) {
            return ignoreNull ? true : false;
        }

        boolean isValid = true;
        try {
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
            LocalDateTime.parse(field, formatter);
        } catch (Exception exp) {
            isValid = false;
        }
        return isValid;
    }

Answer:

Even if you don't specify a timezone, when the timestamp is stored into Cassandra, the timezone will be added.

If no time zone is specified, the time zone of the Cassandra coordinator node handling the write request is used.

The recommendation is to specify the timezone and not rely on the timezone specified on the Cassandra nodes.

If you need to display the timestamp in cqlsh without the timezone you could change your cqlshrc file (the default format for cqlsh is yyyy-mm-dd HH:mm:ssZ)

[ui]
          datetimeformat = %Y-%m-%d %H:%M

In java you could easily remove the timezone or you could just store the timestamp as string if timezone is an issue.

Some stuff related to this:1, 2, 3.