I am currently working on a project to move a bunch of data from SQL to Cassandra as the datastore. We have created a Cassandra Framework that looks very similar to the Spring Data JPA Framework that we use for SQL. Our Cassandra Framework we annotate data with @CassandraEntity
and @CassandraRepository
instead of @Entity
and @Repository
. For a time the data will live in both the SQL database as well as the Cassandra Cluster at the same time before we drop the SQL table. This will allow us to write to both tables and gradually switch over to the new cluster without as much risk if the cluster falls over.
I came across a weird and interesting issue today. We currently use field level injection to inject our components into our code. In the case of JPA we have something like a @Service
which injects our component and into the service and then uses it for the database operations. We do the same thing our custom cassandra annotations. While we are working on this project we are going to for a time be writing to both the SQL database and the Cassandra datastore. At some point we will then switch to reading the data out of Cassandra and then later on drop the SQL tables and remove the JPA Entities entirely. The problem I am seeing is lets say we have 2 Entities:
package com.haskovec.persistence.jpa;
@Entity
public class Person {
@Id
Integer personId;
@Column
String name;
}
package com.haskovec.persistence.cassandra;
@CassandraEntity
public class Person {
@Id
Integer personId;
@Column
String name;
}
We have 2 distinct objects based on package name, but the objects have the same name. In this case we have 2 Repositories as well based on them for the @CassandraRepository
and @Repository
. The problem I am seeing is that Spring is having trouble qualifying which repository to inject. According to the documents for @Inject
and @Autowired
they are supposed to wire based on type. Instead I am getting an error where it can’t qualify which bean to wire which makes it look like it is wiring by name. I was able to fix it with the @Qualifier
annotation and naming the @CassandraRepository
with an @Component("cassandraPersonRepository")
annotation.
I don’t really like this fix though. In theory since these are distinct types Spring should be able to auto wire them correctly. According to the Spring documentation wiring by name is done primarily through @Resource
but if that were the case then a service injecting 2 repositories of the same name from a different package should work. Then I found this in the documentation:
If no name is specified explicitly, the default name is derived from the field name or setter method. In case of a field, it takes the field name; in case of a setter method, it takes the bean property name.
This makes me think it is wiring by name inspite of what the documentation says about wiring by type for @Autowired
and @Inject
. Then I found section 6.10.6 of the documentation. The following quote is telling:
When a component is autodetected as part of the scanning process, its bean name is generated by the BeanNameGenerator strategy known to that scanner. By default, any Spring stereotype annotation ( @Component, @Repository, @Service, and @Controller) that contains a name value will thereby provide that name to the corresponding bean definition.
If such an annotation contains no name value or for any other detected component (such as those discovered by custom filters), the default bean name generator returns the uncapitalized non-qualified class name.
So based on this both classes would be named personrepository hence the problem qualifying the bean. So now my thinking is that to fix this without using @Qualifier
in my code I will need to implement a custom bean naming strategy by implementing BeanNameGenerator
and passing that to the component scanner. The obvious fix to me seems to be to generate the name with the package name plus the class name so that classes of the same name resolve differently under spring. I will need to test it out to see if that works but I am guessing it will.