Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 2.2.0

* Implemented ``@Query`` annotation with simple SQL-Selects

# 2.1.0

* Implemented auto-id-generation for UUIDs.
Expand Down
6 changes: 3 additions & 3 deletions docs/antora.yml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
name: ROOT
title: Spring-Data-Eclipse-Store
version: master
display_version: '2.1.0'
display_version: '2.2.0'
start_page: index.adoc
nav:
- modules/ROOT/nav.adoc
asciidoc:
attributes:
product-name: 'Spring-Data-Eclipse-Store'
display-version: '2.1.0'
maven-version: '2.1.0'
display-version: '2.2.0'
maven-version: '2.2.0'
page-editable: false
page-out-of-support: false
4 changes: 3 additions & 1 deletion docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
* xref:configuration.adoc[Configuration]
* xref:working-copies.adoc[Working Copies]
* xref:features/features.adoc[Features]
** xref:features/ids.adoc[IDs]
** xref:features/lazies.adoc[Lazy References]
** xref:features/queries.adoc[Queries]
** xref:features/transactions.adoc[Transactions]
** xref:features/versions.adoc[Versions]
* xref:migration.adoc[Migration]
* xref:migration.adoc[Migration from JPA]
* xref:known-issues.adoc[Known issues]
2 changes: 2 additions & 0 deletions docs/modules/ROOT/pages/features/features.adoc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
= Features

* xref:features/ids.adoc[IDs]
* xref:features/lazies.adoc[Lazy References]
* xref:features/queries.adoc[Queries]
* xref:features/transactions.adoc[Transactions]
* xref:features/versions.adoc[Versions]
73 changes: 73 additions & 0 deletions docs/modules/ROOT/pages/features/queries.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
= Queries

== Keywords

It is possible to use **most of the standard query keywords** for repositories defined in Spring Data JPA: https://docs.spring.io/spring-data/jpa/reference/repositories/query-keywords-reference.html[Spring Data JPA - Repository query keywords].

Here are a few examples:

[source,java]
----
@Repository
public interface UserRepository extends EclipseStoreRepository<User, Long>
{
List<User> findByFirstName(String firstName, String lastName);
List<User> findByFirstNameAndLastName(String firstName, String lastName);
List<User> findByDateOfBirthBefore(LocalDate date);
List<User> findByAgeIn(List<Integer> ages);
List<User> findByIsActiveFalse();
}
----

More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/string/UserRepository.java[test-cases].

== Query by Example

Developers can also use https://docs.spring.io/spring-data/jpa/reference/repositories/query-by-example.html[Query by Example] if preferred.

An example:

[source,java]
----
public List<User> findAllUsersNamedMick()
{
final User probe = new User(1, "Mick", BigDecimal.TEN);
return userRepository.findAll(Example.of(probe));
}
----

More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/by/example/QueryByExampleTest.java[test-cases].

== @Query annotation

The support for a ``@Query``-Annotation is currently quite limited, but useful nonetheless.

To keep parse and execute SQL-Queries we use the https://github.com/npgall/cqengine[cqengine] by https://github.com/npgall[Niall Gallagher].
It offers rudimentary support of some SQL-Queries, but not all.

[NOTE]
====
https://github.com/npgall/cqengine[cqengine] parses the SQL String as a SQLite-SQL-String and is therefore different from the https://docs.spring.io/spring-data/jpa/reference/jpa/query-methods.html#jpa.query-methods.at-query[HQL or JPQL] of Spring Data JPA.
====

Here are some working examples:

[source,java]
----
public interface MyEntityRepository extends ListCrudRepository<MyEntity, Long>
{
@Query("SELECT * FROM MyEntity WHERE name = '?1'")
List<MyEntity> findByName(String name);

@Query("SELECT * FROM MyEntity WHERE (name = '?1' AND age > ?2)")
List<MyEntity> findByNameAndAgeGreaterThan(String name, int age);

@Query("SELECT * FROM MyEntity WHERE 'name' LIKE '%?1%'")
List<MyEntity> findByNameContaining(String keyword);

@Query("SELECT * FROM MyEntity WHERE otherEntity IS NOT NULL")
List<MyEntity> findWhereOtherEntityIsNotNull();
}
----

More examples are in the https://github.com/xdev-software/spring-data-eclipse-store/blob/develop/spring-data-eclipse-store/src/test/java/software/xdev/spring/data/eclipse/store/integration/isolated/tests/query/hsql/MyEntityRepository.java[test-cases].
12 changes: 0 additions & 12 deletions docs/modules/ROOT/pages/known-issues.adoc
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
= Known issues

== Query annotations

In Spring-Data-JPA you can write a Query over methods of repositories like this:

[source,java,title="From https://github.com/spring-projects/spring-petclinic/blob/main/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java[spring-petclinic]"]
----
@Query("SELECT ptype FROM PetType ptype ORDER BY ptype.name")
List<PetType> findPetTypes();
----

We created https://github.com/xdev-software/spring-data-eclipse-store/issues/32[an issue] for that but right now we *do not support Query annotations*.

== Data changes

There are two basic ways to keep your data up to date.
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/migration.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
= Migration
= Migration from JPA

Migrating from Spring Data JPA is very easy.
We implemented a https://github.com/xdev-software/spring-data-eclipse-store-migration[OpenRewrite recipe] for that.
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>software.xdev</groupId>
<artifactId>spring-data-eclipse-store-root</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
<packaging>pom</packaging>

<organization>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-eclipse-store-benchmark/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>software.xdev</groupId>
<artifactId>spring-data-eclipse-store-root</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
</parent>

<artifactId>spring-data-eclipse-store-benchmark</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-eclipse-store-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>software.xdev</groupId>
<artifactId>spring-data-eclipse-store-root</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
</parent>

<artifactId>spring-data-eclipse-store-demo</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion spring-data-eclipse-store-jpa/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<parent>
<groupId>software.xdev</groupId>
<artifactId>spring-data-eclipse-store-root</artifactId>
<version>2.1.0-SNAPSHOT</version>
<version>2.2.0-SNAPSHOT</version>
</parent>

<artifactId>spring-data-eclipse-store-jpa</artifactId>
Expand Down
21 changes: 21 additions & 0 deletions spring-data-eclipse-store/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
<maven.compiler.release>${javaVersion}</maven.compiler.release>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

<!-- Should be in sync with org.eclipse.store:integrations-spring-boot3 -->
Expand Down Expand Up @@ -163,6 +164,26 @@
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>com.googlecode.cqengine</groupId>
<artifactId>cqengine</artifactId>
<version>3.6.0</version>
<exclusions>
<exclusion>
<artifactId>kryo</artifactId>
<groupId>com.esotericsoftware</groupId>
</exclusion>
<exclusion>
<artifactId>kryo-serializers</artifactId>
<groupId>de.javakaffee</groupId>
</exclusion>
<exclusion>
<artifactId>sqlite-jdbc</artifactId>
<groupId>org.xerial</groupId>
</exclusion>
</exclusions>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,5 @@
@QueryAnnotation
public @interface Query
{
@SuppressWarnings("unused") String value() default "";
String value() default "";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright © 2024 XDEV Software (https://xdev.software)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package software.xdev.spring.data.eclipse.store.repository.query;

import java.util.Objects;

import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.repository.query.RepositoryQuery;

import software.xdev.spring.data.eclipse.store.core.EntityListProvider;
import software.xdev.spring.data.eclipse.store.repository.query.antlr.HSqlQueryExecutor;
import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier;


public class HSqlQueryProvider<T> implements RepositoryQuery
{
private final HSqlQueryExecutor<T> executor;
private final String sqlValue;
private final QueryMethod queryMethod;

public HSqlQueryProvider(
final String sqlValue,
final QueryMethod queryMethod,
final Class<T> domainClass,
final EntityListProvider entityListProvider,
final WorkingCopier<T> copier
)
{
this.queryMethod = queryMethod;
this.executor = new HSqlQueryExecutor<>(
Objects.requireNonNull(domainClass),
Objects.requireNonNull(entityListProvider),
copier
);
this.sqlValue = sqlValue;
}

@Override
public Object execute(final Object[] parameters)
{
return this.executor.execute(this.sqlValue, parameters);
}

@Override
public QueryMethod getQueryMethod()
{
return this.queryMethod;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/*
* Copyright © 2024 XDEV Software (https://xdev.software)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package software.xdev.spring.data.eclipse.store.repository.query.antlr;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

import com.googlecode.cqengine.ConcurrentIndexedCollection;
import com.googlecode.cqengine.IndexedCollection;
import com.googlecode.cqengine.attribute.Attribute;
import com.googlecode.cqengine.attribute.ReflectiveAttribute;
import com.googlecode.cqengine.query.parser.sql.SQLParser;
import com.googlecode.cqengine.resultset.ResultSet;

import software.xdev.spring.data.eclipse.store.core.EntityListProvider;
import software.xdev.spring.data.eclipse.store.repository.access.AccessHelper;
import software.xdev.spring.data.eclipse.store.repository.support.copier.working.WorkingCopier;


public class HSqlQueryExecutor<T>
{
private final SQLParser<T> parser;
private final EntityListProvider entityListProvider;
private final Class<T> domainClass;
private final WorkingCopier<T> copier;

public HSqlQueryExecutor(
final Class<T> domainClass,
final EntityListProvider entityListProvider,
final WorkingCopier<T> copier)
{
this.domainClass = domainClass;
this.parser = SQLParser.forPojoWithAttributes(domainClass, this.createAttributes(domainClass));
this.entityListProvider = entityListProvider;
this.copier = copier;
}

public List<T> execute(final String sqlValue, final Object[] parameters)
{
final IndexedCollection<T> entities = new ConcurrentIndexedCollection<>();
entities.addAll(this.entityListProvider.getEntityProvider(this.domainClass).toCollection());
final String sqlStringWithReplacedValues = this.replacePlaceholders(sqlValue, parameters);
final ResultSet<T> retrieve = this.parser.retrieve(entities, sqlStringWithReplacedValues);
final List<T> results = retrieve.stream().toList();
return this.copier.copy(results);
}

private String replacePlaceholders(final String sqlValue, final Object[] parameters)
{
String stringWithReplacedValues = sqlValue;
// Replace positional placeholders with actual parameter values
for(int i = 0; i < parameters.length; i++)
{
final String placeholder = "\\?" + (i + 1);
String value = parameters[i].toString();
if(parameters[i] instanceof final Collection<?> collection)
{
value =
collection.stream()
.map(o -> "'" + o.toString() + "'")
.collect(Collectors.joining(", ", "(", ")"));
}
if(parameters[i] instanceof final LocalDate localDate)
{
value = localDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
stringWithReplacedValues = stringWithReplacedValues.replaceAll(placeholder, value);
}
return stringWithReplacedValues;
}

private <O> Map<String, ? extends Attribute<O, ?>> createAttributes(final Class<O> domainClass)
{
final Map<String, Attribute<O, ?>> attributes = new TreeMap<>();
AccessHelper.getInheritedPrivateFieldsByName(domainClass).forEach(
(fieldName, field) -> attributes.put(
fieldName,
new ReflectiveAttribute<>(
domainClass,
field.getType(),
fieldName
)
)
);
return attributes;
}
}
Loading