-
Notifications
You must be signed in to change notification settings - Fork 192
DATACOUCH-625 - Save doesn't return updated entity if immutable. #274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -541,6 +541,7 @@ public void doWithPersistentProperty(final CouchbasePersistentProperty prop) { | |
generatedValueInfo = idProperty.findAnnotation(GeneratedValue.class); | ||
String generatedId = generateId(generatedValueInfo, prefixes, suffixes, idAttributes); | ||
target.setId(generatedId); | ||
// this is not effective if id is Immutable, and accessor.setProperty() returns a new object in getBean() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Id population should happen on the Template API level, see MongoDB for reference. Note, newer versions have the Id population extracted because it happened in multiple places. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After adding the update via CouchbaseTemplateSupport.applyUpdatedId() ( called by Reactive[Insert/Upsert]ByIdSupport.one(entity) which is called from the template), I left the update to the sourceObject here for two reasons - the first being that a number of unit tests depend on it (those can be fixed), the second is that if it is removed from here, it would appear to be a regression for anyone calling My first idea was to have the Couchbase implementation of EntityWriter.write(source, target) return the updated object - but the signature returns void. (updating here and returning accessor.getBean() would have worked for that). There could be one benefit from updating the source object with the id. For async/reactive calls - the generated id would be available immediately from the source object. The id from the saved object is not available until the save operation completes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's schedule any removal for the next version. We're in the RC phase with the GA release coming up Oct 28. In any case in an async/reactive API usage scenario, it's hard to reason about when the model is being updated. In-place updates could have nasty side effects therefore we generally recommend using immutable types if you want to work with the saved/updated object instance that is returned by |
||
accessor.setProperty(idProperty, generatedId); | ||
} else { | ||
target.setId(id); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
/* | ||
* Copyright 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.lang.reflect.Field; | ||
|
||
/** | ||
* Comparable entity base class for tests | ||
* | ||
* @author Michael Reiche | ||
*/ | ||
public class ComparableEntity { | ||
|
||
/** | ||
* equals() method that recursively calls equals on on fields | ||
* | ||
* @param that | ||
* @return | ||
* @throws RuntimeException | ||
*/ | ||
@Override | ||
public boolean equals(Object that) throws RuntimeException { | ||
if (this == that) { | ||
return true; | ||
} | ||
if (that == null | ||
|| !(this.getClass().isAssignableFrom(that.getClass()) || that.getClass().isAssignableFrom(this.getClass()))) { | ||
return false; | ||
} | ||
// check that all the fields in this have an equal field in that | ||
for (Field f : this.getClass().getFields()) { | ||
if (!same(f, this, that)) { | ||
return false; | ||
} | ||
} | ||
// check that all the fields in that have an equal field in this | ||
for (Field f : that.getClass().getFields()) { | ||
if (!same(f, that, this)) { | ||
return false; | ||
} | ||
} | ||
// check that all the declared fields in this have an equal field in that | ||
for (Field f : this.getClass().getDeclaredFields()) { | ||
if (!same(f, this, that)) { | ||
return false; | ||
} | ||
} | ||
// check that all the declared fields in that have an equal field in this | ||
for (Field f : that.getClass().getDeclaredFields()) { | ||
if (!same(f, that, this)) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
private static boolean same(Field f, Object a, Object b) { | ||
Object thisField = null; | ||
Object thatField = null; | ||
|
||
try { | ||
thisField = f.get(a); | ||
thatField = f.get(b); | ||
} catch (IllegalAccessException e) { | ||
// assume that the important fields are in toString() | ||
thisField = a.toString(); | ||
thatField = b.toString(); | ||
} | ||
if (thisField == null && thatField == null) { | ||
return true; | ||
} | ||
if (thisField == null && thatField != null) { | ||
return false; | ||
} | ||
if (!thisField.equals(thatField)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
/* | ||
* Copyright 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 lombok.Value; | ||
import lombok.With; | ||
import org.springframework.data.annotation.Id; | ||
import org.springframework.data.annotation.Version; | ||
import org.springframework.data.couchbase.core.mapping.Document; | ||
import org.springframework.data.couchbase.core.mapping.Field; | ||
import org.springframework.data.couchbase.core.mapping.id.GeneratedValue; | ||
import org.springframework.data.couchbase.core.mapping.id.GenerationStrategy; | ||
|
||
/** | ||
* PersonValue entity for tests | ||
* | ||
* @author Michael Reiche | ||
*/ | ||
|
||
@Value | ||
@Document | ||
public class PersonValue { | ||
@Id @GeneratedValue(strategy = GenerationStrategy.UNIQUE) | ||
@With String id; | ||
// @Version @With | ||
long version; | ||
@Field String firstname; | ||
@Field String lastname; | ||
|
||
public PersonValue(String id, long version, String firstname, String lastname) { | ||
this.id = id; | ||
this.version = version; | ||
this.firstname = firstname; | ||
this.lastname = lastname; | ||
} | ||
|
||
public String toString() { | ||
StringBuilder sb = new StringBuilder(); | ||
sb.append("PersonValue : {"); | ||
sb.append(" id : " + getId()); | ||
sb.append(", version : " + version); | ||
sb.append(", firstname : " + firstname); | ||
sb.append(", lastname : " + lastname); | ||
sb.append(" }"); | ||
return sb.toString(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
* Copyright 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 org.springframework.data.repository.CrudRepository; | ||
|
||
/** | ||
* PersonValue repository for tests | ||
* | ||
* @author Michael Reiche | ||
*/ | ||
public interface PersonValueRepository extends CrudRepository<PersonValue, String> { | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would it make sense to move both apply methods into the upsert map callback so they are done in one spot?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For both id and cas updates, they are done as soon as the value is available.
There is no efficiency benefit to doing them at the same time even for immutable entities - each update will require that a new object be created.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After a lot of thinking, I made this change.