Room returns incorrect initialized object from generated query - mysql

I have three tables, one containing Cards, one containing CardDecks and third one implementing a many-to-many relation between the former two and additionally containg a symbol for every relation entry.
My task is to get three columns from the card-table and the symbol from the relation-table and save it in a data Object specifically designed for handling those inputs, the codition being, that all entries match the given deckId. Or in (hopefully correct) sql-language:
#Query("SELECT R.symbol, C.title, C.type, C.source " +
"FROM card_table C JOIN cards_to_card_deck R ON C.id = R.card_id"+
"WHERE R.card_deck_id = :cardDeckId")
LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long cardDeckId);
But the room implementation class generates:
#Override
public LiveData<List<CardWithSymbol>> getCardsWithSymbolInCardDeckById(long
cardDeckId) {
final String _sql = "SELECT R.symbol, C.title, C.typ, C.source FROM
cards_to_card_deck R INNER JOIN card_table C ON R.card_id = C.id WHERE
R.card_deck_id = ?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, cardDeckId);
return new ComputableLiveData<List<CardWithSymbol>>() {
private Observer _observer;
#Override
protected List<CardWithSymbol> compute() {
if (_observer == null) {
_observer = new Observer("cards_to_card_deck","card_table") {
#Override
public void onInvalidated(#NonNull Set<String> tables) {
invalidate();
}
};
__db.getInvalidationTracker().addWeakObserver(_observer);
}
final Cursor _cursor = __db.query(_statement);
try {
final int _cursorIndexOfSymbol = _cursor.getColumnIndexOrThrow("symbol");
final List<CardWithSymbol> _result = new ArrayList<CardWithSymbol>(_cursor.getCount());
while(_cursor.moveToNext()) {
final CardWithSymbol _item;
final int _tmpSymbol;
_tmpSymbol = _cursor.getInt(_cursorIndexOfSymbol);
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
}
}
#Override
protected void finalize() {
_statement.release();
}
}.getLiveData();
}
Where
_item = new CardWithSymbol(_tmpSymbol,null,null,null);
should return my fully initialized object.
The CardWithSymbol class is declared as follows:
public class CardWithSymbol {
public int symbol;
public String cardName;
public String cardType;
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
And the types of the columns returned by the query are:
int symbol, String title, String type, String source
I already went through some debugging and the rest of the application works just fine. I can even read the symbol from the objects return by the query, but as mentioned above for some reason room ignores the other three parameters and just defaults them to null in the query-implementation.

So after some trial and error and reading through the dao-documentation once again i found my error:
When creating a class for handling subsets of columns in room, it is important to tell room which variable coresponds to which columns via #ColumnInfo(name = "name of the column goes here")-annotation.
So changing my CardWithSymbol class as follows solved the issue for me:
import android.arch.persistence.room.ColumnInfo;
public class CardWithSymbol {
#ColumnInfo(name = "symbol")
public int symbol;
#ColumnInfo(name = "title")
public String cardName;
#ColumnInfo(name = "type")
public String cardType;
#ColumnInfo(name = "source")
public String cardSource;
public CardWithSymbol(int symbol, String cardName, String cardType, String cardSource){
this.symbol = symbol;
this.cardName = cardName;
this.cardType = cardType;
this.cardSource = cardSource;
}
}

Related

How To Insert Data with Spring Data JPA Relationships

hopefully this is an easy question, but I'm pretty new with JPA and having difficulty determining how to format a JSON POST request body that is sent to a Spring API. I have two entities, Product and Barcode with the following relationship:
One Product can have many barcodes that point to it (OneToMany)
One Barcode can point to only one Product (OneToOne)
The relationship is defined by a product_id column in the Barcode table.
Relationship definition in Barcode entity:
#OneToOne // one barcode relates to one product
private ProductEntity product;
The relationship definition in Product:
#OneToMany(cascade = CascadeType.ALL)
private List<BarcodeEntity> barcodes = new ArrayList<>();
My question is, how can I do a JSON POST request to insert a Barcode that is related to a Product already in the database? Would I need to pass the entire Product entity or is there a way to just pass the product_id alone?
How I would like to create a new Barcode entry when the Product already exists:
{
"barcode": "string",
"barcodeStatus": "string",
"codeStandard": "string",
"product": 1,
"title": "string",
"unitQuantity": 0
}
Instead of having to do the following, which I believe will result in an error because the product already exists:
{
"barcode": "string",
"barcodeStatus": "string",
"codeStandard": "string",
"product": {
productInfo: "...",
....,
},
"title": "string",
"unitQuantity": 0
}
Essentially, I'm trying to figure out how you can insert a new entity and define its relationship to another entity that is already present in the database. I'm sure I'm over complicating it.
Endpoint in Product controller:
#PostMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public ProductRest createProduct(#Valid #RequestBody ProductRequestModel productDetails) throws Exception {
ProductRest returnValue = new ProductRest();
ModelMapper modelMapper = new ModelMapper();
ProductDto productDto = modelMapper.map(productDetails, ProductDto.class);
ProductDto createdProduct = productService.createProduct(productDto);
returnValue = modelMapper.map(createdProduct, ProductRest.class);
return returnValue;
}
createProduct method in Product service file:
public ProductDto createProduct(ProductDto product) {
if (productRepo.findByTitle(product.getTitle()) != null)
throw new ServiceException("Record with matching title already exists");
// Set product id for each barcode
for (int i = 0; i < product.getBarcodes().size(); i++) {
ProductBarcodeDto barcode = product.getBarcodes().get(i);
barcode.setProduct(product);
product.getBarcodes().set(i, barcode);
}
ModelMapper modelMapper = new ModelMapper();
ProductEntity productEntity = modelMapper.map(product, ProductEntity.class);
ProductEntity storedProductDetails = productRepo.save(productEntity);
ProductDto returnValue = modelMapper.map(storedProductDetails, ProductDto.class);
return returnValue;
}
Fields in ProductDto (defines getters/setters and empty constructor; just not shown):
public class ProductDto {
private long id;
private List<BarcodeDto> barcodes;
private String title;
private String description;
private String SKU;
private ProductVariationDto variation;
private double cost;
private double retailPrice;
private LocalDate launchDate;
private LocalDate discontinueDate;
private String discontinueReason;
private String salesChannel;
private LabelDto label;
private int secondaryStockLevel;
private int primaryStockLevel;
private LocalDateTime modifiedDate;
private LocalDateTime createdDate;
private String productStatus;
private List<SupplierDto> suppliers;
}
I had it set up so that ProductRequestModel expected a BarcodeEntity. Is this the correct or should I change it to expect just an integer value for the Barcode ID?
createBarcode endpoint in Barcode Controller:
#PostMapping(produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE })
public BarcodeRest createBarcode(#Valid #RequestBody BarcodeRequestModel barcodeDetails) throws Exception {
BarcodeRest returnValue = new BarcodeRest();
ModelMapper modelMapper = new ModelMapper();
BarcodeDto barcodeDto = modelMapper.map(barcodeDetails, BarcodeDto.class);
BarcodeDto createdBarcode = barcodeService.createBarcode(barcodeDto);
returnValue = modelMapper.map(createdBarcode, BarcodeRest.class);
return returnValue;
}
createBarcode method implementation:
public BarcodeDto createBarcode(BarcodeDto barcode) {
ModelMapper modelMapper = new ModelMapper();
if (barcodeRepo.findByBarcode(barcode.getBarcode()) != null)
throw new ServiceException("Barcode value already exists.");
BarcodeEntity barcodeEntity = modelMapper.map(barcode, BarcodeEntity.class);
BarcodeEntity storedBarcode = barcodeRepo.save(barcodeEntity);
BarcodeDto returnValue = modelMapper.map(storedBarcode, BarcodeDto.class);
return returnValue;
}
Firstly, there seems to be an issue with the Database Schema Mapping:
One Product : Many Barcodes (One to Many) [NO ISSUES]
Many Barcodes : One Product (Many to One, instead of One to One) [HAS ISSUES]
Secondly, yes you can use just the 'product_id' to update the barcode. It'd be better if you post the controller code once. Would be easier to find any issues with that.

The most efficient way to store photo reference in a database

I'm currently looking to store approximately 3.5 million photo's from approximately 100/200k users. I'm only using a mysql database on aws. My question is in regards to the most efficient way to store the photo reference. I'm only aware of two ways and I'm looking for an expert opinion.
Choice A
A user table with a photo_url column, in that column I would build a comma separated list of photo's that both maintain the name and sort order. The business logic would handle extracting the path from the photo name and append photo size. The downside is the processing expense.
Database example
"0ea102, e435b9, etc"
Business logic would build the following urls from photo name
/0e/a1/02.jpg
/0e/a1/02_thumb.jpg
/e4/35/b9.jpg
/e4/35/b9_thumb.jpg
Choice B - Relational Table joined on user table with the following fields. I'm just concerned I may have potential database performance issues.
pk
user_id
photo_url_800
photo_url_150
photo_url_45
order
Does anybody have any suggestions on the better solution?
The best and most common answer would be: choice B - Relational Table joined on user table with the following fields.
id
order
user_id
desc
photo_url_800
photo_url_150
photo_url_45
date_uploaded
Or a hybrid, wherein, you store the file names individually and add the photo directory with your business logic layer.
My analysis, your first option is a bad practice. Comma separated fields are not advisable for database. It would be difficult for you to update these fields and add description on it.
Regarding the table optimization, you might want to see these articles:
Optimizing MyISAM Queries
Optimizing InnoDB Queries
Here is an example of my final solution using the hibernate ORM, Christian Mark, and my hybrid solution.
#Entity
public class Photo extends StatefulEntity {
private static final String FILE_EXTENSION_JPEG = ".jpg";
private static final String ROOT_PHOTO_URL = "/photo/";
private static final String PHOTO_SIZE_800 = "_800";
private static final String PHOTO_SIZE_150 = "_150";
private static final String PHOTO_SIZE_100 = "_100";
private static final String PHOTO_SIZE_50 = "_50";
#ManyToOne
#JoinColumn(name = "profile_id", nullable = false)
private Profile profile;
//Example "a1d2b0" which will later get parsed into "/photo/a1/d2/b0_size.jpg"
//using the generatePhotoUrl business logic below.
#Column(nullable = false, length = 6)
private String fileName;
private boolean temp;
#Column(nullable = false)
private int orderBy;
#Temporal(TemporalType.TIMESTAMP)
private Date dateUploaded;
public Profile getProfile() {
return profile;
}
public void setProfile(Profile profile) {
this.profile = profile;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public Date getDateUploaded() {
return dateUploaded;
}
public void setDateUploaded(Date dateUploaded) {
this.dateUploaded = dateUploaded;
}
public boolean isTemp() {
return temp;
}
public void setTemp(boolean temp) {
this.temp = temp;
}
public int getOrderBy() {
return orderBy;
}
public void setOrderBy(int orderBy) {
this.orderBy = orderBy;
}
public String getPhotoSize800() {
return generatePhotoURL(PHOTO_SIZE_800);
}
public String getPhotoSize150() {
return generatePhotoURL(PHOTO_SIZE_150);
}
public String getPhotoSize100() {
return generatePhotoURL(PHOTO_SIZE_100);
}
public String getPhotoSize50() {
return generatePhotoURL(PHOTO_SIZE_50);
}
private String generatePhotoURL(String photoSize) {
String firstDir = getFileName().substring(0, 2);
String secondDir = getFileName().substring(2, 4);
String photoName = getFileName().substring(4, 6);
StringBuilder sb = new StringBuilder();
sb.append(ROOT_PHOTO_URL);
sb.append("/");
sb.append(firstDir);
sb.append("/");
sb.append(secondDir);
sb.append("/");
sb.append(photoName);
sb.append(photoSize);
sb.append(FILE_EXTENSION_JPEG);
return sb.toString();
}
}

'Cannot bind to the new value member. Parameter name: newDisplayMember'.

I'm using C# in Visual Studio 2010. I have 2 comboboxes that pull data from the database. The code looks something like this:
cbo1.DisplayMember = "Name";
cbo1.ValueMember = "HROfficeLocationID";
cbo1.DataSource = offices;
cbo2.DisplayMember = "Name";
cbo2.ValueMember = "HROfficeLocationID";
cbo2.DataSource = offices;
I kept getting this exception: 'Cannot bind to the new value member. Parameter name: newDisplayMember'. I searched around and then reorganized the lines of code so that cbo.DataSource came before .DisplayMember and .ValueMember .It ended up looking something like this:
cbo1.DataSource = offices;
cbo1.DisplayMember = "Name";
cbo1.ValueMember = "HROfficeLocationID";
cbo2.DataSource = offices;
cbo2.DisplayMember = "Name";
cbo2.ValueMember = "HROfficeLocationID";
The exception went away. Just thought I'd share.
I had this occur when the internal class I was using had the varialbles as "internal". Changed them to "public" and it worked fine.
Specify as a Property, not as a variable in a class for example,
public class projectData
{
public string ProjName { get; set; }
public string ProjId { get; set; }
}
List<projectData> projects = getProjects();
lBoxFDTProjects.DataSource = projects;
lBoxFDTProjects.ValueMember = "ProjId";
lBoxFDTProjects.DisplayMember = "ProjName";
Some property attributes also cause this error like the [Browsable(false)]
public class CmbStringItem
{
public CmbStringItem(string text, string val)
{
Text = text;
Value = val;
}
private string text;
public string Text
{
get {return text;}
set {text = value;}
}
private string val;
[System.ComponentModel.BrowsableAttribute(true)] // must use
public string Value
{
get {return val;}
set {val = value;}
}
public override string ToString()
{
return Text;
}
}
List<CmbStringItem> items = new List<CmbStringItem>();
items.Add(new CmbStringItem("Onula", "0"));
items.Add(new CmbStringItem("Jedna", "1"));
items.Add(new CmbStringItem("Dva", "2"));
items.Add(new CmbStringItem("Tri", "3"));
this.cmbSklad.DataSource = items;
this.cmbSklad.ValueMember = "Value";
this.cmbSklad.DisplayMember = "Text";
this.cmbSklad.SelectedIndex = 0;
// set Chombobox - Display vlaue
cmbSklad.SelectedValue = "1";

How to handle unidirectional many-to-many relations with Ebean

I have a problem with Ebean. I have the usual Objects PsecUser, PsecRoles and PsecPermission.
A user can have many Permissions or Roles and a Role can have many Permission.
Here the code (extract):
#Entity
public class PsecPermission {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Column(nullable=false)
private String type = PsecBasicPermission.class.getName();
#Column(nullable=false)
private String target;
#Column(nullable=false)
private String actions;
}
#Entity
public class PsecRole {
#Id
#GeneratedValue
private Long id;
#Column(unique=true, nullable=false)
private String name;
#Temporal(TemporalType.TIMESTAMP)
private Date lastUpdate;
#ManyToMany(fetch=FetchType.EAGER)
private List<PsecPermission> psecPermissions;
private boolean defaultRole = false;
}
I wrote the following helper-method:
public PsecRole createOrUpdateRole(String name, boolean defaultRole, String... permissions) {
PsecRole result = server.find(PsecRole.class).
where().eq("name", name).findUnique();
if (result == null) {
result = new PsecRole();
result.setName(name);
}
final List<PsecPermission> permissionObjects = server.find(PsecPermission.class).
where().in("name", (Object[])permissions).findList();
result.setPsecPermissions(permissionObjects);
result.setDefaultRole(defaultRole);
final Set <ConstraintViolation <PsecRole>> errors =
Validation.getValidator().validate(result);
if (errors.isEmpty()) {
server.save(result);
server.saveManyToManyAssociations(result, "psecPermissions");
} else {
log.error("Can't save role: " + name +"!");
for (ConstraintViolation <PsecRole> constraintViolation : errors) {
log.error(" " + constraintViolation);
}
}
return result;
}
and try the following test:
#Test
public void testCreateOrUpdateRole() {
String[] permNames = {"Test1", "Test2", "Test3"};
List <PsecPermission> permissions = new ArrayList <PsecPermission>();
for (int i = 0; i < permNames.length; i++) {
helper.createOrUpdatePermission(permNames[i], "target"+ i, "actions" +i);
PsecPermission perm = server.find(PsecPermission.class).where().eq("name", permNames[i]).findUnique();
assertThat(perm.getTarget()).isEqualTo("target" + i);
assertThat(perm.getActions()).isEqualTo("actions" + i);
permissions.add(perm);
}
PsecRole orgRole = helper.createOrUpdateRole(ROLE, false, permNames);
testRole(permNames, orgRole);
PsecRole role = server.find(PsecRole.class).where().eq("name", ROLE).findUnique();
testRole(permNames, role);
}
private void testRole(String[] permNames, PsecRole role) {
assertThat(role).isNotNull();
assertThat(role.getName()).isEqualTo(ROLE);
assertThat(role.isDefaultRole()).isEqualTo(false);
assertThat(role.getPermissions()).hasSize(permNames.length);
}
Which fails if it checks the number of permissions at the readed role. It's always 0.
I looked into the database and found that psec_role_psec_permission is alway empty.
Any idea what's wrong with the code?
You can get a pure Ebean-example from https://github.com/opensource21/ebean-samples/downloads it uses the eclipse-plugin from ebean.
There are two solutions for this problem:
Simply add cascade option at PsceRole
#ManyToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL)
private List<PsecPermission> psecPermissions;
and remove server.saveManyToManyAssociations(result, "psecPermissions"); you find it in the cascade-solution-branch.
The cleaner solution, because you don't need to define cascase- perhaps you don't want it:
Just don't replace the list, just add your entries to the list. Better is to add new and remove old one. This mean in createOrUpdateRole:
result.getPsecPermissions().addAll(permissionObjects);
instead of
result.setPsecPermissions(permissionObjects);

Jackson JSON processor overwrites properties of object during deserialization

I have interesting problem. Jackson overwrites values of properties on the 'parent' object with values of properties of 'child' object that have same name. So, to be more precise, this is Java structure I have
public class Contact {
...
String name;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
...
}
public class Account {
...
String accountName;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
...
}
So, when I form Contact JSON object and send it to server, everything goes fine until BeanDeserializer comes into account property of Contact class. Then, it starts reading proeprties of account part of JSON, which is ok, but does not create Account instance to set it on contact - it writes values of account's properties into properties with same names of Contact instance.
I am confused and not sure where to start looking how to fix this.
I'm not able to reproduce any problem similar to what's described in the original question.
The following example, created based on the descriptions in the original question, works as expected, without errors or improper deserialization.
import java.util.LinkedList;
import java.util.List;
import org.codehaus.jackson.annotate.JsonAutoDetect.Visibility;
import org.codehaus.jackson.annotate.JsonCreator;
import org.codehaus.jackson.annotate.JsonProperty;
import org.codehaus.jackson.map.ObjectMapper;
public class JacksonFoo
{
public static void main(String[] args) throws Exception
{
Account account1 = new Account();
account1.accountName = "account 1";
account1.emails = new LinkedList<Email>();
account1.emails.add(new Email("email_11#google.com"));
account1.emails.add(new Email("email_12#google.com"));
account1.phoneNumbers = new LinkedList<PhoneNumbers>();
account1.phoneNumbers.add(new PhoneNumbers(1111, 1112));
account1.phoneNumbers.add(new PhoneNumbers(1113, 1114));
Account account2 = new Account();
account2.accountName = "account 2";
account2.emails = new LinkedList<Email>();
account2.emails.add(new Email("email_21#google.com"));
account2.emails.add(new Email("email_22#google.com"));
account2.phoneNumbers = new LinkedList<PhoneNumbers>();
account2.phoneNumbers.add(new PhoneNumbers(2221, 2222));
account2.phoneNumbers.add(new PhoneNumbers(2223, 2224));
account2.account = account1;
Contact contact = new Contact();
contact.name = "contact";
contact.emails = new LinkedList<Email>();
contact.emails.add(new Email("email_31#google.com"));
contact.emails.add(new Email("email_32#google.com"));
contact.phoneNumbers = new LinkedList<PhoneNumbers>();
contact.phoneNumbers.add(new PhoneNumbers(3331, 3332));
contact.phoneNumbers.add(new PhoneNumbers(3333, 3334));
contact.account = account2;
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibilityChecker(
mapper.getVisibilityChecker()
.withFieldVisibility(Visibility.ANY));
String account1Json = mapper.writeValueAsString(account1);
String account2Json = mapper.writeValueAsString(account2);
String contactJson = mapper.writeValueAsString(contact);
System.out.println(account1Json); // {"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}
System.out.println(account2Json); // {"accountName":"account 2","emails":[{"email":"email_21#google.com"},{"email":"email_22#google.com"}],"phoneNumbers":[{"phone1":2221,"phone2":2222},{"phone1":2223,"phone2":2224}],"account":{"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}}
System.out.println(contactJson); // {"name":"contact","emails":[{"email":"email_31#google.com"},{"email":"email_32#google.com"}],"phoneNumbers":[{"phone1":3331,"phone2":3332},{"phone1":3333,"phone2":3334}],"account":{"accountName":"account 2","emails":[{"email":"email_21#google.com"},{"email":"email_22#google.com"}],"phoneNumbers":[{"phone1":2221,"phone2":2222},{"phone1":2223,"phone2":2224}],"account":{"accountName":"account 1","emails":[{"email":"email_11#google.com"},{"email":"email_12#google.com"}],"phoneNumbers":[{"phone1":1111,"phone2":1112},{"phone1":1113,"phone2":1114}],"account":null}}}
Account account1Copy = mapper.readValue(account1Json, Account.class);
Account account2Copy = mapper.readValue(account2Json, Account.class);
Contact contactCopy = mapper.readValue(contactJson, Contact.class);
System.out.println(account1.equals(account1Copy)); // true
System.out.println(account2.equals(account2Copy)); // true
System.out.println(contact.equals(contactCopy)); // true
}
}
class Contact
{
String name;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
#Override
public boolean equals(Object o)
{
Contact c = (Contact) o;
if (name.equals(c.name))
if (emails.containsAll(c.emails))
if (c.emails.containsAll(emails))
if (phoneNumbers.containsAll(c.phoneNumbers))
if (c.phoneNumbers.containsAll(phoneNumbers))
return account.equals(c.account);
return false;
}
}
class Account
{
String accountName;
List<Email> emails;
List<PhoneNumbers> phoneNumbers;
Account account;
#Override
public boolean equals(Object o)
{
Account a = (Account) o;
if (accountName.equals(a.accountName))
if (emails.containsAll(a.emails))
if (a.emails.containsAll(emails))
if (phoneNumbers.containsAll(a.phoneNumbers))
if (a.phoneNumbers.containsAll(phoneNumbers))
if (account != null && a.account != null)
return account.equals(a.account);
else if (account == null && a.account == null)
return true;
return false;
}
}
class Email
{
String email;
#JsonCreator
Email(#JsonProperty("email") String e) {email = e;}
#Override
public boolean equals(Object o)
{
Email e = (Email) o;
return email.equals(e.email);
}
}
class PhoneNumbers
{
long phone1;
long phone2;
#JsonCreator
PhoneNumbers(#JsonProperty("phone1") long p1, #JsonProperty("phone2")long p2) {phone1 = p1; phone2 = p2;}
#Override
public boolean equals(Object o)
{
PhoneNumbers p = (PhoneNumbers) o;
return phone1 == p.phone1 && phone2 == p.phone2;
}
}