Search code examples
spring-bootscalacassandraspring-dataspring-data-cassandra

Exception about the lack of primary attribute in an entity


I am a newbie with spring boot and Cassandra and I'm trying to connect both to build an API. The project is written in Scala to learn from this too.

This is main application:

object Application {
  def main(args: Array[String]): Unit = {
    SpringApplication.run(classOf[Application], args: _*)
  }
}

@SpringBootApplication(exclude = Array(classOf[CassandraDataAutoConfiguration], classOf[CassandraAutoConfiguration]))
class Application

This is a controller:

@CrossOrigin
@RestController
@RequestMapping(value = Array("/api/user"))
class UserController @Autowired()(userService: UserService) {

  @GetMapping(value = Array("/{email}"))
  def findByEmail(@PathVariable("email") email: String): ResponseEntity[User] = {
    try {
      val user = userService.findByEmail(email)

      if(user.isEmpty) {
        return new ResponseEntity[User](HttpStatus.NOT_FOUND)
      }
      new ResponseEntity[User](user.get, HttpStatus.OK)
    }
    catch {
      case _ => new ResponseEntity[User](HttpStatus.INTERNAL_SERVER_ERROR)
    }
  }
}

This is a service:

@Service
class UserService @Autowired()(userRepository: UserRepository) {
  def findByEmail(email: String): Option[User] = userRepository.findByEmail(email)
}

This is a repository:

@Repository
trait UserRepository extends RepositoryBase[User, String] {
  def findByEmail(email: String): Option[User] = {
    this.findByPrimaryKey(email)
  }
}

This is a entity:

import javax.validation.constraints.{NotBlank, Size}
import org.springframework.data.cassandra.core.mapping.{CassandraType, Column, PrimaryKey, Table}

@Table(value = "user")
class User (

    @NotBlank
    @Column(value = "id")
    @CassandraType(`type` = CassandraType.Name.UUID)
    var id: String,

    @NotBlank
    @Size(min = 10)
    @PrimaryKey(value = "email")
    @CassandraType(`type` = CassandraType.Name.TEXT)
    var email: String,

    @NotBlank
    @Size(min = 8, max = 250)
    @Column(value = "password")
    @CassandraType(`type` = CassandraType.Name.TEXT)
    var password: String,

    @NotBlank
    @Size(min = 3)
    @Column(value = "name")
    @CassandraType(`type` = CassandraType.Name.TEXT)
    var name: String

) extends Serializable {}

This is Cassandra config:

@Configuration
@EnableCassandraRepositories
case class CassandraConfig (

  @Value(value = "${spring.data.cassandra.keyspace-name}")
  keySpace: String,

  @Value(value = "${spring.data.cassandra.contact-points}")
  contactPoints: String,

  @Value(value = "${spring.data.cassandra.port}")
  port: Int

) extends AbstractCassandraConfiguration
{
  override def getKeyspaceName: String = this.keySpace

  override def getContactPoints: String = this.contactPoints

  override def getPort: Int = this.port

  @Bean
  @throws(classOf[ClassNotFoundException])
  override def cassandraMapping(): CassandraMappingContext = {
    new BasicCassandraMappingContext()
  }
}

This is the table cql:

CREATE TABLE IF NOT EXISTS user_data.user (
    id uuid,
    email text,
    password text,
    name text,
    PRIMARY KEY (email)
) WITH comment = 'user data';

I have been reading all spring boot documentation, and in my entity I have @Table and @PrimaryKey annotations, but still I get this error:

at org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntityMetadataVerifier.fail(BasicCassandraPersistentEntityMetadataVerifier.java:117) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntityMetadataVerifier.verify(BasicCassandraPersistentEntityMetadataVerifier.java:90) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.data.cassandra.core.mapping.CompositeCassandraPersistentEntityMetadataVerifier.lambda$verify$0(CompositeCassandraPersistentEntityMetadataVerifier.java:67) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at java.util.Arrays$ArrayList.forEach(Arrays.java:3880) ~[na:1.8.0_312]
    at org.springframework.data.cassandra.core.mapping.CompositeCassandraPersistentEntityMetadataVerifier.verify(CompositeCassandraPersistentEntityMetadataVerifier.java:67) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntity.verify(BasicCassandraPersistentEntity.java:157) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.data.mapping.context.AbstractMappingContext.doAddPersistentEntity(AbstractMappingContext.java:452) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.mapping.context.AbstractMappingContext.addPersistentEntity(AbstractMappingContext.java:406) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.cassandra.core.mapping.CassandraMappingContext.addPersistentEntity(CassandraMappingContext.java:337) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:291) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:209) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentEntity(AbstractMappingContext.java:92) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$6(RepositoryFactoryBeanSupport.java:326) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at java.util.Optional.ifPresent(Optional.java:159) ~[na:1.8.0_312]
    at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:326) ~[spring-data-commons-2.7.1.jar:2.7.1]
    at org.springframework.data.cassandra.repository.support.CassandraRepositoryFactoryBean.afterPropertiesSet(CassandraRepositoryFactoryBean.java:75) ~[spring-data-cassandra-3.4.1.jar:3.4.1]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.21.jar:5.3.21]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.21.jar:5.3.21]
    ... 45 common frames omitted
    Suppressed: org.springframework.data.mapping.MappingException: @Table types must have only one primary attribute, if any; Found 0
        at org.springframework.data.cassandra.core.mapping.BasicCassandraPersistentEntityMetadataVerifier.verify(BasicCassandraPersistentEntityMetadataVerifier.java:87) ~[spring-data-cassandra-3.4.1.jar:3.4.1]

Could it be that the error is from the Cassandra configuration? I'm also open to any best practice suggestions for anything unrelated to the bug. Thanks!


Solution

  • The problem is in the entity. The attributes of the entity must not be declared in the constructor as would be done normally in Scala, so that the entity can be mapped correctly the attributes must be declared in this way:

    import javax.validation.constraints.{NotBlank, Size}
    import org.springframework.data.cassandra.core.mapping.{CassandraType, Column, PrimaryKey, Table}
    import scala.beans.BeanProperty
    
    @Table(value = "user")
    class User extends Serializable {
    
        @NotBlank
        @BeanProperty
        @Column(value = "id")
        @CassandraType(`type` = CassandraType.Name.UUID)
        var id: String = _
    
        @NotBlank
        @BeanProperty
        @Size(min = 10)
        @PrimaryKey(value = "email")
        @CassandraType(`type` = CassandraType.Name.TEXT)
        var email: String = _
    
        @NotBlank
        @BeanProperty
        @Size(min = 8, max = 250)
        @Column(value = "password")
        @CassandraType(`type` = CassandraType.Name.TEXT)
        var password: String = _
    
        @NotBlank
        @BeanProperty
        @Size(min = 3)
        @Column(value = "name")
        @CassandraType(`type` = CassandraType.Name.TEXT)
        var name: String = _
    }