#816 - Error saving circular relationships (when generated Id values)

This commit is contained in:
Robin Bygrave
2016-08-25 22:44:22 +12:00
parent d1e2176a5a
commit 244275fd72
20 changed files with 358 additions and 135 deletions
@@ -1,27 +0,0 @@
package com.avaje.ebeaninternal.api;
public final class DerivedRelationshipData {
private final Object assocBean;
private final String logicalName;
private final Object bean;
public DerivedRelationshipData(Object assocBean, String logicalName, Object bean) {
this.assocBean = assocBean;
this.logicalName = logicalName;
this.bean = bean;
}
public Object getAssocBean() {
return assocBean;
}
public String getLogicalName() {
return logicalName;
}
public Object getBean() {
return bean;
}
}
@@ -8,13 +8,13 @@ import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.core.PersistDeferredRelationship;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import javax.persistence.PersistenceException;
import javax.persistence.RollbackException;
import java.io.IOException;
import java.sql.Connection;
import java.util.List;
/**
* Wrapper of a ScopeTrans request and it's underlying transaction.
@@ -145,13 +145,8 @@ public class ScopedTransaction implements SpiTransaction {
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData assocBean) {
transaction.registerDerivedRelationship(assocBean);
}
@Override
public List<DerivedRelationshipData> getDerivedRelationship(Object bean) {
return transaction.getDerivedRelationship(bean);
public void registerDeferred(PersistDeferredRelationship derived) {
transaction.registerDeferred(derived);
}
@Override
@@ -7,10 +7,10 @@ import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.core.PersistDeferredRelationship;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import java.sql.Connection;
import java.util.List;
/**
* Extends Transaction with additional API required on server.
@@ -48,15 +48,9 @@ public interface SpiTransaction extends Transaction {
void logSummary(String msg);
/**
* Register a "Derived Relationship" (that requires an additional update).
* Register a "Deferred Relationship" that requires an additional update later.
*/
void registerDerivedRelationship(DerivedRelationshipData assocBean);
/**
* Return the list of "Derived Relationships" that must be maintained after
* insert.
*/
List<DerivedRelationshipData> getDerivedRelationship(Object bean);
void registerDeferred(PersistDeferredRelationship derived);
/**
* Add a deleting bean to the registered list.
@@ -0,0 +1,53 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
/**
* Deferred update of a relationship where an Id value is not initially available
* so instead we execute this later as a SqlUpdate statement.
*/
public class PersistDeferredRelationship {
private final SpiEbeanServer ebeanServer;
private final BeanDescriptor<?> beanDescriptor;
private final EntityBean assocBean;
private final ImportedId importedId;
private final EntityBean bean;
public PersistDeferredRelationship(SpiEbeanServer ebeanServer, BeanDescriptor<?> beanDescriptor, EntityBean assocBean, ImportedId importedId, EntityBean bean) {
this.ebeanServer = ebeanServer;
this.beanDescriptor = beanDescriptor;
this.assocBean = assocBean;
this.importedId = importedId;
this.bean = bean;
}
/**
* Build and execute a SqlUpdate to set the importId value (as it will be available now).
* <p>
* This is executed later (deferred) until after JDBC batch flush or prior to commit.
* </p>
*/
public void execute(SpiTransaction transaction) {
String sql = beanDescriptor.getUpdateImportedIdSql(importedId);
SqlUpdate sqlUpdate = ebeanServer.createSqlUpdate(sql);
// bind the set clause for the importedId
int pos = importedId.bind(1, sqlUpdate, assocBean);
// bind the where clause for the bean
Object[] idValues = beanDescriptor.getIdBinder().getIdValues(bean);
for (int j = 0; j < idValues.length; j++) {
sqlUpdate.setParameter(pos + j, idValues[j]);
}
ebeanServer.execute(sqlUpdate, transaction);
}
}
@@ -1,16 +1,15 @@
package com.avaje.ebeaninternal.server.core;
import com.avaje.ebean.ValuePair;
import com.avaje.ebean.bean.PreGetterCallback;
import com.avaje.ebeaninternal.api.ConcurrencyMode;
import com.avaje.ebean.annotation.DocStoreMode;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.PreGetterCallback;
import com.avaje.ebean.event.BeanPersistController;
import com.avaje.ebean.event.BeanPersistListener;
import com.avaje.ebean.event.BeanPersistRequest;
import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.ConcurrencyMode;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEvent;
@@ -19,12 +18,13 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanManager;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import com.avaje.ebeaninternal.server.persist.PersistExecute;
import com.avaje.ebeaninternal.server.transaction.BeanPersistIdMap;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdateContext;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdates;
import com.avaje.ebeanservice.docstore.api.DocStoreUpdate;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
@@ -180,7 +180,7 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
/**
* Return the document store event that should be used for this request.
*
* <p>
* Used to check if the Transaction has set the mode to IGNORE when doing large batch inserts that we
* don't want to send to the doc store.
*/
@@ -874,8 +874,11 @@ public final class PersistRequestBean<T> extends PersistRequest implements BeanP
}
}
public List<DerivedRelationshipData> getDerivedRelationships() {
return transaction.getDerivedRelationship(bean);
/**
* Register the derived relationships to get executed later (on JDBC batch flush or commit).
*/
public void deferredRelationship(EntityBean assocBean, ImportedId importedId, EntityBean bean) {
transaction.registerDeferred(new PersistDeferredRelationship(ebeanServer, beanDescriptor, assocBean, importedId, bean));
}
private void postInsert() {
@@ -51,6 +51,7 @@ import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.id.IdBinder;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyLists;
import com.avaje.ebeaninternal.server.el.ElComparator;
@@ -1408,6 +1409,13 @@ public class BeanDescriptor<T> implements MetaBeanInfo, BeanType<T> {
updatePlanCache.put(key, plan);
}
/**
* Return a Sql update statement to set the importedId value (deferred execution).
*/
public String getUpdateImportedIdSql(ImportedId prop) {
return "update " + baseTable + " set " + prop.importedIdClause() + " where " + idBinder.getBindIdSql(null);
}
/**
* Return true if updates should only include changed properties. Otherwise
* all loaded properties are included in the update.
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.deploy.id;
import java.sql.SQLException;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.deploy.DbSqlContext;
@@ -46,6 +47,11 @@ public interface ImportedId {
*/
Object bind(BindableRequest request, EntityBean bean) throws SQLException;
/**
* Bind the imported Id value to the SqlUpdate.
*/
int bind(int position, SqlUpdate update, EntityBean bean);
/**
* For inserting into ManyToMany intersection.
*/
@@ -55,4 +61,9 @@ public interface ImportedId {
* Used to derive a missing concatenated key from multiple imported keys.
*/
BeanProperty findMatchImport(String matchDbColumn);
/**
* Return the set importedId clause.
*/
String importedIdClause();
}
@@ -4,6 +4,7 @@ import java.sql.SQLException;
import javax.persistence.PersistenceException;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.deploy.BeanFkeyProperty;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -63,6 +64,34 @@ public class ImportedIdEmbedded implements ImportedId {
}
}
@Override
public String importedIdClause() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < imported.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(imported[i].localDbColumn).append(" = ?");
}
return sb.toString();
}
@Override
public int bind(int position, SqlUpdate update, EntityBean bean) {
int pos = position;
EntityBean embedded = (EntityBean) foreignAssocOne.getValue(bean);
for (int i = 0; i < imported.length; i++) {
if (imported[i].owner.isUpdateable()) {
Object scalarValue = imported[i].foreignProperty.getValue(embedded);
update.setParameter(pos++, scalarValue);
}
}
return pos;
}
public Object bind(BindableRequest request, EntityBean bean) throws SQLException {
Object embeddedId = null;
@@ -7,6 +7,7 @@ import java.util.List;
import javax.persistence.PersistenceException;
import com.avaje.ebean.SqlUpdate;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.server.core.InternString;
import com.avaje.ebeaninternal.server.deploy.BeanFkeyProperty;
@@ -110,6 +111,18 @@ public final class ImportedIdSimple implements ImportedId, Comparable<ImportedId
request.appendColumn(localDbColumn);
}
@Override
public String importedIdClause() {
return localDbColumn + " = ?";
}
@Override
public int bind(int position, SqlUpdate update, EntityBean bean) {
Object value = getIdValue(bean);
update.setParameter(position, value);
return ++position;
}
public Object bind(BindableRequest request, EntityBean bean) throws SQLException {
Object value = null;
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.persist.dml;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
@@ -49,10 +48,5 @@ public class DeleteHandler extends DmlHandler {
checkRowCount(rowCount);
return rowCount;
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData assocBean) {
throw new RuntimeException("Never called on delete");
}
}
@@ -1,13 +1,10 @@
package com.avaje.ebeaninternal.server.persist.dml;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiEbeanServer;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
import com.avaje.ebeaninternal.server.persist.DmlUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -18,7 +15,6 @@ import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
* Insert bean handler.
@@ -134,30 +130,9 @@ public class InsertHandler extends DmlHandler {
}
checkRowCount(rowCount);
executeDerivedRelationships();
return rowCount;
}
protected void executeDerivedRelationships() {
List<DerivedRelationshipData> derivedRelationships = persistRequest.getDerivedRelationships();
if (derivedRelationships != null) {
SpiEbeanServer ebeanServer = (SpiEbeanServer) persistRequest.getEbeanServer();
for (int i = 0; i < derivedRelationships.size(); i++) {
DerivedRelationshipData derivedRelationshipData = derivedRelationships.get(i);
BeanDescriptor<?> beanDescriptor = ebeanServer.getBeanDescriptor(derivedRelationshipData.getBean().getClass());
BeanProperty prop = beanDescriptor.getBeanProperty(derivedRelationshipData.getLogicalName());
EntityBean entityBean = (EntityBean) derivedRelationshipData.getBean();
entityBean._ebean_getIntercept().markPropertyAsChanged(prop.getPropertyIndex());
ebeanServer.update(entityBean, transaction);
}
}
}
/**
* For non batch insert with generated keys.
*/
@@ -220,9 +195,4 @@ public class InsertHandler extends DmlHandler {
}
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) {
persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship);
}
}
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.persist.dml;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.SpiUpdatePlan;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
@@ -74,9 +73,4 @@ public class UpdateHandler extends DmlHandler {
return 0;
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) {
persistRequest.getTransaction().registerDerivedRelationship(derivedRelationship);
}
}
@@ -1,15 +1,14 @@
package com.avaje.ebeaninternal.server.persist.dmlbind;
import java.sql.SQLException;
import java.util.List;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocOne;
import com.avaje.ebeaninternal.server.deploy.id.ImportedId;
import com.avaje.ebeaninternal.server.persist.dml.GenerateDmlRequest;
import java.sql.SQLException;
import java.util.List;
/**
* Bindable for an ManyToOne or OneToOne associated bean.
*/
@@ -51,10 +50,8 @@ public class BindableAssocOne implements Bindable {
// this is the scenario for a derived foreign key
// which will require an additional update
// register for post insert of assocBean
// update of bean set ... importedId.getLogicalName();
// value of assocBean.getId
DerivedRelationshipData d = new DerivedRelationshipData(assocBean, assocOne.getName(), bean);
request.registerDerivedRelationship(d);
// update of bean set importedId
request.getPersistRequest().deferredRelationship(assocBean, importedId, bean);
}
}
@@ -1,6 +1,5 @@
package com.avaje.ebeaninternal.server.persist.dmlbind;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.deploy.BeanProperty;
@@ -54,8 +53,6 @@ public interface BindableRequest {
*/
PersistRequestBean<?> getPersistRequest();
void registerDerivedRelationship(DerivedRelationshipData assocBean);
/**
* Return the system current time in millis. This is expected to the same time used
* by multiple generated properties for a single request.
@@ -8,11 +8,11 @@ import com.avaje.ebean.config.ServerConfig;
import com.avaje.ebean.config.dbplatform.DatabasePlatform.OnQueryOnly;
import com.avaje.ebean.event.changelog.BeanChange;
import com.avaje.ebean.event.changelog.ChangeSet;
import com.avaje.ebeaninternal.api.DerivedRelationshipData;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.api.TransactionEvent;
import com.avaje.ebeaninternal.server.core.PersistRequest;
import com.avaje.ebeaninternal.server.core.PersistRequestBean;
import com.avaje.ebeaninternal.server.core.PersistDeferredRelationship;
import com.avaje.ebeaninternal.server.lib.util.Str;
import com.avaje.ebeaninternal.server.persist.BatchControl;
import org.slf4j.Logger;
@@ -136,8 +136,6 @@ public class JdbcTransaction implements SpiTransaction {
protected HashMap<String, String> m2mIntersectionSave;
protected HashMap<Integer, List<DerivedRelationshipData>> derivedRelMap;
protected Map<String, Object> userObjects;
protected List<TransactionCallback> callbackList;
@@ -146,6 +144,8 @@ public class JdbcTransaction implements SpiTransaction {
protected TChangeLogHolder changeLogHolder;
protected List<PersistDeferredRelationship> deferredList;
/**
* The mode for updating doc store indexes for this transaction.
* Only set when you want to override the default behavior.
@@ -334,26 +334,11 @@ public class JdbcTransaction implements SpiTransaction {
}
@Override
public List<DerivedRelationshipData> getDerivedRelationship(Object bean) {
if (derivedRelMap == null) {
return null;
public void registerDeferred(PersistDeferredRelationship derived) {
if (deferredList == null) {
deferredList = new ArrayList<PersistDeferredRelationship>();
}
return derivedRelMap.get(System.identityHashCode(bean));
}
@Override
public void registerDerivedRelationship(DerivedRelationshipData derivedRelationship) {
if (derivedRelMap == null) {
derivedRelMap = new HashMap<Integer, List<DerivedRelationshipData>>();
}
Integer key = System.identityHashCode(derivedRelationship.getAssocBean());
List<DerivedRelationshipData> list = derivedRelMap.get(key);
if (list == null) {
list = new ArrayList<DerivedRelationshipData>();
derivedRelMap.put(key, list);
}
list.add(derivedRelationship);
deferredList.add(derived);
}
/**
@@ -715,9 +700,22 @@ public class JdbcTransaction implements SpiTransaction {
if (!isActive()) {
throw new IllegalStateException(illegalStateMessage);
}
internalBatchFlush();
}
/**
* Flush the JDBC batch and execute derived relationship statements if necessary.
*/
private void internalBatchFlush() {
if (batchControl != null) {
batchControl.flush();
}
if (deferredList != null) {
for (PersistDeferredRelationship deferred : deferredList) {
deferred.execute(this);
}
deferredList.clear();
}
}
/**
@@ -886,9 +884,7 @@ public class JdbcTransaction implements SpiTransaction {
* Batch flush, jdbc commit, trigger registered TransactionCallbacks, notify l2 cache etc.
*/
private void flushCommitAndNotify() throws SQLException {
if (batchControl != null && !batchControl.isEmpty()) {
batchControl.flush();
}
internalBatchFlush();
firePreCommit();
// only performCommit can throw an exception
performCommit();
@@ -26,10 +26,6 @@ public class Booking {
@Version
private int version;
public Booking(Long bookingUid) {
this.bookingUid = bookingUid;
}
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "agent_invoice")
private Invoice agentInvoice;
@@ -40,7 +36,11 @@ public class Booking {
@OneToMany(mappedBy = "booking")
private List<Invoice> invoices;
public Booking(Long bookingUid) {
this.bookingUid = bookingUid;
}
public Long getId() {
return id;
}
@@ -12,7 +12,6 @@ public class TestOne2OneBookingInvoice extends BaseTestCase {
public void test() {
Booking b = new Booking(3000L);
Ebean.save(b);
Invoice ai = new Invoice();
Invoice ci = new Invoice();
@@ -0,0 +1,83 @@
package com.avaje.tests.model.m2o;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Version;
@Entity
public class Addr {
@Id
Long id;
@ManyToOne
Empl employee;
String name;
String addressLine1;
String addressLine2;
String city;
@Version
long version;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Empl getEmployee() {
return employee;
}
public void setEmployee(Empl employee) {
this.employee = employee;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddressLine1() {
return addressLine1;
}
public void setAddressLine1(String addressLine1) {
this.addressLine1 = addressLine1;
}
public String getAddressLine2() {
return addressLine2;
}
public void setAddressLine2(String addressLine2) {
this.addressLine2 = addressLine2;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public long getVersion() {
return version;
}
public void setVersion(long version) {
this.version = version;
}
}
@@ -0,0 +1,65 @@
package com.avaje.tests.model.m2o;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import java.util.List;
@Entity
public class Empl {
@Id
Long id;
String name;
Integer age;
@OneToMany(mappedBy="employee" , cascade= CascadeType.ALL)
List<Addr> addresses;
@ManyToOne(cascade = CascadeType.ALL)
Addr defaultAddress;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Addr> getAddresses() {
return addresses;
}
public void setAddresses(List<Addr> addresses) {
this.addresses = addresses;
}
public Addr getDefaultAddress() {
return defaultAddress;
}
public void setDefaultAddress(Addr defaultAddress) {
this.defaultAddress = defaultAddress;
}
}
@@ -0,0 +1,49 @@
package com.avaje.tests.model.m2o;
import com.avaje.ebean.BaseTestCase;
import com.avaje.ebean.Ebean;
import com.avaje.ebean.annotation.Transactional;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class TestManyToOneAsOne extends BaseTestCase {
@Test
public void test_when_nonJdbcBatch() {
runInserts();
}
@Transactional(batchSize = 20)
@Test
public void test_when_jdbcBatch() {
runInserts();
}
private void runInserts() {
Addr junk = new Addr();
junk.setName("junk");
Ebean.save(junk);
Empl emp = new Empl();
emp.setName("My Name");
Addr address = new Addr();
address.setName("home");
emp.getAddresses().add(address);
// if I do an interim Ebean.save here it works
//Ebean.save(emp);
address.setEmployee(emp);
emp.setDefaultAddress(address);
Ebean.save(emp);
assertThat(emp.getDefaultAddress().getId()).isNotNull();
assertThat(address.getEmployee().getId()).isNotNull();
Empl foundEmpl = Ebean.find(Empl.class, emp.getId());
assertThat(foundEmpl.getDefaultAddress().getId()).isEqualTo(address.getId());
}
}