Spring Boot and Spring Data Couchbase N1QL auto-generated query - spring

I am trying to create a simple prototype using Spring Boot and Spring Data Couchbase projects. I have been stymied so far by trying to use the Spring-Data’s query derivation mechanism to build a N1QL query from the method name.
This is what I have for my repository interface definition and the problem is in the findBy... line.
public interface MetricsRepository extends CrudRepository<Single, String> {
Single save(Single entity);
Single findOne(String id);
List<Single> findByServiceID(long serviceId);
}
If I exclude that method definition, the application starts without a problem. If I include it, the repository bean fails to be created with due to the following error:
Caused by: java.lang.AbstractMethodError: org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactory$CouchbaseQueryLookupStrategy.resolveQuery(Ljava/lang/reflect/Method;Lorg/springframework/data/repository/core/RepositoryMetadata;Lorg/springframework/data/repository/core/NamedQueries;)Lorg/springframework/data/repository/query/RepositoryQuery;
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:416)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:206)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.initAndReturn(RepositoryFactoryBeanSupport.java:251)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:237)
at org.springframework.data.couchbase.repository.support.CouchbaseRepositoryFactoryBean.afterPropertiesSet(CouchbaseRepositoryFactoryBean.java:96)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574)
... 36 more
If I specify a #Query (for example: #Query("#{#n1ql.selectEntity} WHERE role = $1") I get the same error.
My entity definition:
import com.couchbase.client.java.repository.annotation.Id;
import com.couchbase.client.java.repository.annotation.Field;
import org.springframework.data.couchbase.core.mapping.Document;
#Document
public class Single {
#Id private final String eventID;
#Field private final long serviceID;
#Field private final long metric;
#Field private final long timestamp;
#Field private final long previous;
public Single(String eventID, long serviceID, long metric, long timestamp, long previous) {
this.eventID = eventID;
this.serviceID = serviceID;
this.metric = metric;
this.timestamp = timestamp;
this.previous = previous;
}
public String getEventID() { return eventID; }
public long getServiceID() { return serviceID; }
public long getMetric() { return metric; }
public long getTimestamp() { return timestamp; }
public long getPrevious() { return previous; }
}
I'm using the repository via #Autowired annotation in a REST controller.
I have a #Configuration and #EnableCouchbaseRepositories config class #Import-ed into the #SpringBootApplication. I have a Couchbase Server 4.0.0 community version installed on my test instance, and if the n1ql query is not there, I can connect and store and retrieve entities.
My dependencies in gradle:
dependencies {
compile("org.springframework.data:spring-data-couchbase:2.1.1.RELEASE")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-actuator")
testCompile("org.springframework.boot:spring-boot-starter-test")
testCompile("junit:junit")
}

So I did a bit of dependency detective work:
Spring Boot 1.3.3 refers to the Spring Data BOM in version 1.7.4 (Gosling SR4)
That version of the Spring Data BOM brings in spring data-couchbase 1.4.4. That's what you'll get unless you force the version, like you did.
But what is the root of your problem is that it also brings in spring-data-commons 1.11.4 (of course, as BOM are made to bundle a coherent set of versions)
So you end up forcing the version of just the Spring Data Couchbase dependency, while relying on the BOM to choose the Spring Data Commons, resulting on incompatible artifacts.
Here is a bit of a bad news: if you want Spring Data Couchbase 2.x (the new "generation"), it is only officially supported in Spring Data Hopper. And Hopper is only officially supported in Spring Boot 1.4.0, which ** is currently in MILESTONE 2.
If you only want to start playing with Spring Data Couchbase 2.x, maybe using Spring Boot 1.4.0.M2 is acceptable?
If you don't really care about the 2.x version, you should simply remove the version in your Gradle configuration and it should work.
DANGER ZONE: If you absolutely want to do some Spring Data Couchbase 2.1.1 inside of Spring Boot 1.3.3, then note that anything could break if a dependency clashes. That said, if you only use the Couchbase data store, maybe you'll be ok. The way to do that is complicated with Gradle as you need a plugin to import the BOM. Have a look at this gist.
I've updated the project site to note that the BOM should generally be favored over forcing the version. The quick start there is for standalone projects.

I had the same problem (java.lang.AbstractMethodError) with Spring Data Couchbase 2.1.1. But in this case I was using other dependencies in the project, using a different required version of Spring Commons, and they were in conflict. Try to use a different version, maybe 2.1.0.

Related

The method search(BoolQueryBuilder) is undefined for the type

I just migrate my spring boot from 2.2.4-RELEASE with spring-data-elastic-search 3.2.4 to spring boot 2.6.5 with spring-data-elastic-search 4.3.3.
My code here
public Iterable findInstallHistoryByApkId(int intervalle, Long apkId) {
LocalDate today = LocalDate.now();
LocalDate beginningDate = today.minusDays(intervalle);
BoolQueryBuilder query = QueryBuilders.boolQuery();
query.must(QueryBuilders.rangeQuery(dateName)
.gte(beginningDate)
.lte(today));
query.must(QueryBuilders.matchQuery(apkIdName, apkId));
query.must(QueryBuilders.matchQuery(event, Event.INSTALL));
return apkHistoryRepo.search(query);
}
raises error
The method search(BoolQueryBuilder) is undefined for the type ApkHistoryRepository
ApkHistoryRepository is just a repository, with a classic definition.
#Repository
public interface ApkHistoryRepository extends ElasticsearchRepository<ApkHistory, Long>
Before migrating the code works well, so how could i do to correct it ?
Thanks in advance.
I think it was in 4.0 that the search(Query) method was removed (or deprecated and later removed). Repository methods define their queries by their names.
If you have a Query and want this executed, use a ElasticsearchOperations instance for that.
Btw, Spring Data Elasticsearch 4.3.x is out of maintenance since November of last year.

Spring Boot migration from 1.5 to 2.0 and quotes around parameters in JPA repository queries

I'm upgrading an application from Spring Boot 1.5 to 2.0 and my Spring Data JPA repositories are broken. I turned on query logging in my PostgreSQL 9.6 db to see what was different in the queries before and after the application upgrade and observed that as of 2.0, query parameters are being wrapped in double quotes which is unnecessary and breaking. Here's what I'm seeing in the query log:
Spring Boot 1.5.22
LOG: execute <unnamed>: select siteentity0_.site_id as site_id1_14_, siteentity0_.description as descript2_14_, siteentity0_.directory as director3_14_, siteentity0_.ip_address as ip_addre4_14_, siteentity0_.name as name5_14_, siteentity0_.server as server6_14_, siteentity0_.status as status7_14_, siteentity0_.type as type8_14_ from site siteentity0_ where siteentity0_.ip_address=$1
DETAIL: parameters: $1 = '127.0.0.1'
Spring Boot 2.0.9
LOG: execute <unnamed>: select siteentity0_.site_id as site_id1_14_, siteentity0_.description as descript2_14_, siteentity0_.directory as director3_14_, siteentity0_.ip_address as ip_addre4_14_, siteentity0_.name as name5_14_, siteentity0_.server as server6_14_, siteentity0_.status as status7_14_, siteentity0_.type as type8_14_ from site siteentity0_ where siteentity0_.ip_address=$1
DETAIL: parameters: $1 = '"127.0.0.1"'
I've checked all the release notes and migration guide and can't find anything that would explain this, nor can I find any similar reports. Any ideas?
EDIT:
The repository:
import java.util.Collection;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SiteRepository extends JpaRepository<SiteEntity, Integer> {
SiteEntity findByName(String siteName);
Collection<SiteEntity> findByIpAddress(String ipAddress);
Collection<SiteEntity> findByStatus(String status);
Collection<SiteEntity> findByType(String type);
}
You can replace if the parameters contains double quote to empty
I figured it out. I had an attribute converter for converting JSON to string with autoApply set to true. I changed it to false and now it works. I suppose autoApply is more permissive in 2.x.
#Converter(autoApply = false)
public class JpaConverterJson implements AttributeConverter<Object, String> {

Spring data with multiple modules not working

I'm trying to set up a project with two data sources, one is MongoDB and the other is Postgres. I have repositories for each data source in different packages and I annotated my main class as follows:
#Import({MongoDBConfiguration.class, PostgresDBConfiguration.class})
#SpringBootApplication(exclude = {
MongoRepositoriesAutoConfiguration.class,
JpaRepositoriesAutoConfiguration.class
})
public class TemporaryRunner implements CommandLineRunner {
...
}
MongoDBConfiguration:
#Configuration
#EnableMongoRepositories(basePackages = {
"com.example.datastore.mongo",
"com.atlassian.connect.spring"})
public class MongoDBConfiguration {
...
}
PostgresDBConfiguration:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.example.datastore.postgres"
})
public class PostgresDBConfiguration {
...
}
And even though I specified the base packages as described in documentation, I still get those messages in the console:
13:10:44.238 [main] [] INFO o.s.d.r.c.RepositoryConfigurationDelegate - Multiple Spring Data modules found, entering strict repository configuration mode!
13:10:44.266 [main] [] INFO o.s.d.r.c.RepositoryConfigurationExtensionSupport - Spring Data MongoDB - Could not safely identify store assignment for repository candidate interface com.atlassian.connect.spring.AtlassianHostRepository.
I managed to solve this issue for all my repositories by using MongoRepository and JpaRepository but AtlassianHostRepository comes from an external lib and it is a regular CrudRepository (which totally makes sense because the consumer of the lib can decide what type of DB he would like to use). Anyway it looks that basePackages I specified are completely ignored and not used in any way, even though I specified com.atlassian.connect.spring package only in #EnableMongoRepositories Spring Data somehow can't figure out which data module should be used.
Am I doing something wrong? Is there any other way I could tell spring data to use mongo for AtlassianHostRepository without changing the AtlassianHostRepository.class itself?
The only working solution I found was to let spring data ignore AtlassianHostRepository (because it couldn't figure out which data source to use) then create a separate configuration for it, and simply create it by hand:
#Configuration
#Import({MongoDBConfiguration.class})
public class AtlassianHostRepositoryConfiguration {
private final MongoTemplate mongoTemplate;
#Autowired
public AtlassianHostRepositoryConfiguration(final MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}
#Bean
public AtlassianHostRepository atlassianHostRepository() {
RepositoryFactorySupport factory = new MongoRepositoryFactory(mongoTemplate);
return factory.getRepository(AtlassianHostRepository.class);
}
}
This solution works fine for a small or limited number of repositories used from a library, it would be rather cumbersome to create all the repositories by hand when there are more of them, but after reading the source code of spring-data I see no way to make it work with basePackages as stated in documentation (I may be wrong though).

How to fix java.lang.IllegalStateException when using spring-data-neo4j

I have a simple test project where checking spring-data-neo4j with spring boot version: 2.1.0.RELEASE (https://github.com/tomkasp/neo4j-playground/blob/master/src/main/java/com/athleticspot/neo4jplayground/domain/AthleteRepository.java)
spring-data-neo4j (version: 5.1.4.RELEASE) dependency is injected by spring-boot-starter-data-neo4j.
My goal was to create a repository method which fetches data with containing and ingnorecase functionalities. In order to do that I've created below method within repository:
public interface AthleteRepository extends CrudRepository<Athlete, Long> {
List<Athlete> findByNameContainingIgnoreCase(String name);
}
When I run above functions I'm getting:
java.lang.IllegalStateException: Unable to ignore case of java.lang.String types, the property 'name' must reference a String
at org.springframework.util.Assert.state(Assert.java:73) ~[spring-core-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.data.neo4j.repository.query.filter.PropertyComparisonBuilder.applyCaseInsensitivityIfShouldIgnoreCase(PropertyComparisonBuilder.java:101) ~[spring-data-neo4j-5.1.2.RELEASE.jar:5.1.2.RELEASE]
Doesn't spring-data-neo4j support Containing and IgnoreCase together? Am I missing something?
At the moment it seems not possible because the referenced org.springframework.data.neo4j.repository.query.filter.PropertyComparisonBuilder seems to allow ignoring case only for "SIMPLE_PROERTY" (is, or equals). See method canIgnoreCase in same class:
private boolean canIgnoreCase(Part part) {
return part.getType() == SIMPLE_PROPERTY && String.class.equals(part.getProperty().getLeafType());
}
Fix is coming with spring 5.2 (Moore): https://jira.spring.io/browse/DATAGRAPH-1190

Multiple datasources migrations using Flyway in a Spring Boot application

We use Flyway for db migration in our Spring Boot based app and now we have a requirement to introduce multi tenancy support while using multiple datasources strategy. As part of that we also need to support migration of multiple data sources. All data sources should maintain the same structure so same migration scripts should be used for migrating of all data sources. Also, migrations should occur upon application startup (as opposed to build time, whereas it seems that the maven plugin can be configured to migrate multiple data sources). What is the best approach to use in order to achieve this? The app already has data source beans defined but Flyway executes the migration only for the primary data source.
To make #Roger Thomas answer more the Spring Boot way:
Easiest solution is to annotate your primary datasource with #Primary (which you already did) and just let bootstrap migrate your primary datasource the 'normal' way.
For the other datasources, migrate those sources by hand:
#Configuration
public class FlywaySlaveInitializer {
#Autowired private DataSource dataSource2;
#Autowired private DataSource dataSource3;
//other datasources
#PostConstruct
public void migrateFlyway() {
Flyway flyway = new Flyway();
//if default config is not sufficient, call setters here
//source 2
flyway.setDataSource(dataSource2);
flyway.setLocations("db/migration_source_2");
flyway.migrate();
//source 3
flyway.setDataSource(dataSource3);
flyway.setLocations("db/migration_source_3");
flyway.migrate();
}
}
Flyway supports migrations coded within Java and so you can start Flyway during your application startup.
https://flywaydb.org/documentation/migration/java
I am not sure how you would config Flyway to target a number of data sources via the its config files. My own development is based around using Java to call Flyway once per data source I need to work against. Spring Boot supports the autowiring of beans marked as #FlywayDataSource, but I have not looked into how this could be used.
For an in-java solution the code can be as simple as
Flyway flyway = new Flyway();
// Set the data source
flyway.setDataSource(dataSource);
// Where to search for classes to be executed or SQL scripts to be found
flyway.setLocations("net.somewhere.flyway");
flyway.setTarget(MigrationVersion.LATEST);
flyway.migrate();
Having your same problem... I looked into the spring-boot-autoconfigure artifact for V 2.2.4 in the org.springframework.boot.autoconfigure.flyway package and I found an annotation FlywayDataSource.
Annotating ANY datasource you want to be used by Flyway should do the trick.
Something like this:
#FlywayDataSource
#Bean(name = "someDatasource")
public DataSource someDatasource(...) {
<build and return your datasource>
}
Found an easy solution for that - I added the step during the creation of my emf:
#Qualifier(EMF2)
#Bean(name = EMF2)
public LocalContainerEntityManagerFactoryBean entityManagerFactory2(
final EntityManagerFactoryBuilder builder
) {
final DataSource dataSource = dataSource2();
Flyway.configure()
.dataSource(dataSource)
.locations("db/migration/ds2")
.load()
.migrate();
return builder
.dataSource(dataSource)
.packages(Role.class)
.properties(jpaProperties2().getProperties())
.persistenceUnit("domain2")
.build();
}
I disabled spring.flyway.enabled for that.
SQL files live in resources/db/migration/ds1/... and resources/db/migration/ds2/...
This worked for me.
import javax.annotation.PostConstruct;
import org.flywaydb.core.Flyway;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
#Configuration
public class FlywaySlaveInitializer {
#Value("${firstDatasource.db.url}")
String firstDatasourceUrl;
#Value("${firstDatasource.db.user}")
String firstDatasourceUser;
#Value("${firstDatasource.db.password}")
String firstDatasourcePassword;
#Value("${secondDatasource.db.url}")
String secondDatasourceUrl;
#Value("${secondDatasource.db.user}")
String secondDatasourceUser;
#Value("${secondDatasource.db.password}")
String secondDatasourcePassword;
#PostConstruct
public void migrateFlyway() {
Flyway flywayIntegration = Flyway.configure()
.dataSource(firstDatasourceUrl, firstDatasourceUser, firstDatasourcePassword)
.locations("filesystem:./src/main/resources/migration.first")
.load();
Flyway flywayPhenom = Flyway.configure()
.dataSource(secondDatasourceUrl, secondDatasourceUser, secondDatasourcePassword)
.locations("filesystem:./src/main/resources/migration.second")
.load();
flywayIntegration.migrate();
flywayPhenom.migrate();
}
}
And in my application.yml this property:
spring:
flyway:
enabled: false

Resources