Restore projection of class. (#1319)

Restore projection of class in queries. This is necessary for abstract repositories.

Closes #1315.
This commit is contained in:
Michael Reiche
2022-02-03 06:19:32 -08:00
committed by GitHub
parent 8f8efbd16f
commit 895b2b270e
10 changed files with 361 additions and 97 deletions

View File

@@ -0,0 +1,105 @@
/*
* Copyright 2012-2020 the original author or authors
*
* 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
*
* https://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 org.springframework.data.couchbase.domain;
import java.util.Objects;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* User entity for tests
*
* @author Michael Reiche
*/
public abstract class AbstractUser extends ComparableEntity {
@Version protected long version;
@Id protected String id;
protected String firstname;
protected String lastname;
@Transient protected String transientInfo;
@CreatedBy protected String createdBy;
@CreatedDate protected long createdDate;
@LastModifiedBy protected String lastModifiedBy;
@LastModifiedDate protected long lastModifiedDate;
public String getId() {
return id;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public long getLastModifiedDate() {
return lastModifiedDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public int hashCode() {
return Objects.hash(getId(), firstname, lastname);
}
public String getTransientInfo() {
return transientInfo;
}
public void setTransientInfo(String something) {
transientInfo = something;
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2012-2021 the original author or authors
*
* 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
*
* https://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 org.springframework.data.couchbase.domain;
import java.util.List;
import java.util.stream.Stream;
import com.couchbase.client.java.query.QueryScanConsistency;
import org.springframework.data.couchbase.repository.CouchbaseRepository;
import org.springframework.data.couchbase.repository.Query;
import org.springframework.data.couchbase.repository.ScanConsistency;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import com.couchbase.client.java.json.JsonArray;
/**
* AbstractUser Repository for tests
*
* @author Michael Reiche
*/
@Repository
@ScanConsistency(query=QueryScanConsistency.REQUEST_PLUS)
public interface AbstractUserRepository extends CouchbaseRepository<AbstractUser, String> {
@Query("#{#n1ql.selectEntity} where (meta().id = $1)")
AbstractUser myFindById(String id);
List<User> findByFirstname(String firstname);
Stream<User> findByLastname(String lastname);
List<User> findByFirstnameIn(String... firstnames);
List<User> findByFirstnameIn(JsonArray firstnames);
List<User> findByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and firstname = $1 and lastname = $2")
List<User> getByFirstnameAndLastname(String firstname, String lastname);
@Query("#{#n1ql.selectEntity} where #{#n1ql.filter} and (firstname = $first or lastname = $last)")
List<User> getByFirstnameOrLastname(@Param("first") String firstname, @Param("last") String lastname);
List<User> findByIdIsNotNullAndFirstnameEquals(String firstname);
List<User> findByVersionEqualsAndFirstnameEquals(Long version, String firstname);
}

View File

@@ -35,7 +35,7 @@ import javax.validation.constraints.Max;
@Document
@TypeAlias("airport")
public class Airport extends ComparableEntity {
@Id String id;
@Id String key;
String iata;
@@ -49,14 +49,14 @@ public class Airport extends ComparableEntity {
long size;
@PersistenceConstructor
public Airport(String id, String iata, String icao) {
this.id = id;
public Airport(String key, String iata, String icao) {
this.key = key;
this.iata = iata;
this.icao = icao;
}
public String getId() {
return id;
return key;
}
public String getIata() {

View File

@@ -174,6 +174,9 @@ public interface AirportRepository extends CouchbaseRepository<Airport, String>,
+ " #{#planIds != null ? 'AND blahblah IN $2' : ''} " + " #{#active != null ? 'AND false = $3' : ''} ")
Long countOne();
@ScanConsistency(query = QueryScanConsistency.REQUEST_PLUS)
Airport findByKey(String id);
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
// @Meta

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2022 the original author or authors
*
* 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
*
* https://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 org.springframework.data.couchbase.domain;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.couchbase.core.mapping.Document;
/**
* OtherUser entity for tests. Both User and OtherUser extend AbstractUser
*
* @author Michael Reiche
*/
@Document
public class OtherUser extends AbstractUser {
@PersistenceConstructor
public OtherUser(final String id, final String firstname, final String lastname) {
this.id = id;
this.firstname = firstname;
this.lastname = lastname;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors
* Copyright 2012-2022 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,16 +16,7 @@
package org.springframework.data.couchbase.domain;
import java.util.Objects;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.annotation.Transient;
import org.springframework.data.annotation.Version;
import org.springframework.data.couchbase.core.mapping.Document;
/**
@@ -36,17 +27,7 @@ import org.springframework.data.couchbase.core.mapping.Document;
*/
@Document
public class User extends ComparableEntity {
@Version long version;
@Id private String id;
private String firstname;
private String lastname;
@Transient private String transientInfo;
@CreatedBy private String createdBy;
@CreatedDate private long createdDate;
@LastModifiedBy private String lastModifiedBy;
@LastModifiedDate private long lastModifiedDate;
public class User extends AbstractUser {
@PersistenceConstructor
public User(final String id, final String firstname, final String lastname) {
@@ -55,60 +36,4 @@ public class User extends ComparableEntity {
this.lastname = lastname;
}
public String getId() {
return id;
}
public String getFirstname() {
return firstname;
}
public String getLastname() {
return lastname;
}
public long getCreatedDate() {
return createdDate;
}
public void setCreatedDate(long createdDate) {
this.createdDate = createdDate;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public long getLastModifiedDate() {
return lastModifiedDate;
}
public String getLastModifiedBy() {
return lastModifiedBy;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
@Override
public int hashCode() {
return Objects.hash(id, firstname, lastname);
}
public String getTransientInfo() {
return transientInfo;
}
public void setTransientInfo(String something) {
transientInfo = something;
}
}

View File

@@ -0,0 +1,115 @@
/*
* Copyright 2017-2022 the original author or authors.
*
* 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
*
* https://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 org.springframework.data.couchbase.repository;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Optional;
import java.util.UUID;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.couchbase.config.AbstractCouchbaseConfiguration;
import org.springframework.data.couchbase.domain.AbstractUser;
import org.springframework.data.couchbase.domain.AbstractUserRepository;
import org.springframework.data.couchbase.domain.OtherUser;
import org.springframework.data.couchbase.domain.User;
import org.springframework.data.couchbase.repository.config.EnableCouchbaseRepositories;
import org.springframework.data.couchbase.util.ClusterAwareIntegrationTests;
import org.springframework.data.couchbase.util.ClusterType;
import org.springframework.data.couchbase.util.IgnoreWhen;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
/**
* Abstract Repository tests
*
* @author Michael Reiche
*/
@SpringJUnitConfig(CouchbaseAbstractRepositoryIntegrationTests.Config.class)
@IgnoreWhen(clusterTypes = ClusterType.MOCKED)
public class CouchbaseAbstractRepositoryIntegrationTests extends ClusterAwareIntegrationTests {
@Autowired AbstractUserRepository abstractUserRepository;
@Test
void saveAndFindAbstract() {
// User extends AbstractUser
// OtherUser extends Abstractuser
AbstractUser user = null;
{
user = new User(UUID.randomUUID().toString(), "userFirstname", "userLastname");
assertEquals(User.class, user.getClass());
abstractUserRepository.save(user);
{
// Queries on repositories for abstract entities must be @Query and not include
// #{#n1ql.filter} (i.e. _class = <classname> ) as the classname will not match any document
AbstractUser found = abstractUserRepository.myFindById(user.getId());
assertEquals(user, found);
assertEquals(user.getClass(), found.getClass());
}
{
Optional<AbstractUser> found = abstractUserRepository.findById(user.getId());
assertEquals(user, found.get());
}
abstractUserRepository.delete(user);
}
{
user = new OtherUser(UUID.randomUUID().toString(), "userFirstname", "userLastname");
assertEquals(OtherUser.class, user.getClass());
abstractUserRepository.save(user);
{
AbstractUser found = abstractUserRepository.myFindById(user.getId());
assertEquals(user, found);
assertEquals(user.getClass(), found.getClass());
}
{
Optional<AbstractUser> found = abstractUserRepository.findById(user.getId());
assertEquals(user, found.get());
}
abstractUserRepository.delete(user);
}
}
@Configuration
@EnableCouchbaseRepositories("org.springframework.data.couchbase")
static class Config extends AbstractCouchbaseConfiguration {
@Override
public String getConnectionString() {
return connectionString();
}
@Override
public String getUserName() {
return config().adminUsername();
}
@Override
public String getPassword() {
return config().adminPassword();
}
@Override
public String getBucketName() {
return bucketName();
}
}
}

View File

@@ -249,11 +249,11 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
java.util.Collection<String> icaos = new LinkedList<String>();
icaos.add(vie.getIcao());
icaos.add("blue");
PageRequest pageable = PageRequest.of( 0, 1, Sort.by("iata"));
List<Airport>airports = airportRepository.findByIataInAndIcaoIn(iatas, icaos, pageable);
PageRequest pageable = PageRequest.of(0, 1, Sort.by("iata"));
List<Airport> airports = airportRepository.findByIataInAndIcaoIn(iatas, icaos, pageable);
assertEquals(1, airports.size());
List<Airport>airports2 = airportRepository.findByIataInAndIcaoIn(iatas, icaos, pageable);
List<Airport> airports2 = airportRepository.findByIataInAndIcaoIn(iatas, icaos, pageable);
assertEquals(1, airports2.size());
} finally {
@@ -624,11 +624,12 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
airportRepository.saveAll(
Arrays.stream(iatas).map((iata) -> new Airport("airports::" + iata, iata, iata.toLowerCase(Locale.ROOT)))
.collect(Collectors.toSet()));
List<Airport> airports = airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS)).findAll(Sort.by("iata"));
List<Airport> airports = airportRepository.withOptions(QueryOptions.queryOptions().scanConsistency(REQUEST_PLUS))
.findAll(Sort.by("iata"));
String[] sortedIatas = iatas.clone();
System.out.println(""+iatas.length+" "+sortedIatas.length);
System.out.println("" + iatas.length + " " + sortedIatas.length);
Arrays.sort(sortedIatas);
for(int i=0; i< sortedIatas.length; i++){
for (int i = 0; i < sortedIatas.length; i++) {
assertEquals(sortedIatas[i], airports.get(i).getIata());
}
} finally {
@@ -924,6 +925,15 @@ public class CouchbaseRepositoryQueryIntegrationTests extends ClusterAwareIntegr
.all(Arrays.asList(address1.getId(), address2.getId(), address3.getId(), user.getId()));
}
@Test
void findByKey() {
Airport airport = new Airport(UUID.randomUUID().toString(), "iata1038", "icao");
airportRepository.save(airport);
Airport found = airportRepository.findByKey(airport.getId());
assertEquals(airport, found);
airportRepository.delete(airport);
}
private void sleep(int millis) {
try {
Thread.sleep(millis); // so they are executed out-of-order

View File

@@ -85,7 +85,7 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
Query query = creator.createQuery();
assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2",
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and firstname = $1 and lastname = $2",
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
}
@@ -104,7 +104,7 @@ class StringN1qlQueryCreatorMockedTests extends ClusterAwareIntegrationTests {
Query query = creator.createQuery();
assertEquals(
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)",
"SELECT META(`travel-sample`).id AS __id, META(`travel-sample`).cas AS __cas, `_class`, `firstname`, `lastname`, `createdBy`, `createdDate`, `lastModifiedBy`, `lastModifiedDate` FROM `travel-sample` where `_class` = \"org.springframework.data.couchbase.domain.User\" and (firstname = $first or lastname = $last)",
query.toN1qlSelectString(couchbaseTemplate.reactive(), User.class, false));
}