I have read several tutorial about one-to-one relation mapping forexample:
https://docs.oracle.com/javaee/6/api/javax/persistence/OneToOne.html,
http://websystique.com/hibernate/hibernate-one-to-one-unidirectional-with-foreign-key-associations-annotation-example/
https://en.wikibooks.org/wiki/Java_Persistence/OneToOne
I beleive I follow these tutorials, however my relational mapping still not works as expected. I have the following classes:
#Entity(name = "lesson")
public class Lesson {
#Id
#Type(type = "pg-uuid")
private UUID uid;
private String start_date_time;
private String end_date_time;
private String location;
#OneToOne
#JoinColumn(name="uid") //uid is the name of the Id i want to reference to in the subject class
private Subject subject_uid; // subject_uid is the name of the column in my subject table
public Lesson(UUID uid, String start_date_time, String end_date_time, String location, Subject subject_uid) {
this.uid = uid;
this.start_date_time = start_date_time;
this.end_date_time = end_date_time;
this.location = location;
this.subject_uid = subject_uid;
}
//getters setters
#Entity(name = "subject")
public class Subject {
#Id
#Type(type = "pg-uuid")
private UUID uid;
private String name;
private String start_date;
private String end_date;
private boolean is_lesson_created;
public Subject(UUID uid, String name, String start_date, String end_date, boolean is_lesson_created) {
this.uid = uid;
this.name = name;
this.start_date = start_date;
this.end_date = end_date;
this.is_lesson_created = is_lesson_created;
}
The response what the Spring Data Rest creates on /lessons endpoint looks the following:
{
"_embedded" : {
"lessons" : [ {
"start_date_time" : "2017-01-08 08:30:00",
"end_date_time" : "2017-01-08 10:15:00",
"location" : "A101 ",
"_links" : {
"self" : {
"href" : "http://localhost:3400/lessons/78038aeb-cdc9-4673-990e-36b8c1105500"
},
"lesson" : {
"href" : "http://localhost:3400/lessons/78038aeb-cdc9-4673-990e-36b8c1105500"
},
"subject_uid" : {
"href" : "http://localhost:3400/lessons/78038aeb-cdc9-4673-990e-36b8c1105500/subject_uid"
}
}
} ]
},
"_links" : {
"self" : {
"href" : "http://localhost:3400/lessons{?page,size,sort}",
"templated" : true
},
"profile" : {
"href" : "http://localhost:3400/profile/lessons"
}
},
"page" : {
"size" : 20,
"totalElements" : 1,
"totalPages" : 1,
"number" : 0
}
}
When I want access the http://localhost:3400/lessons/78038aeb-cdc9-4673-990e-36b8c1105500/subject_uidlink I get a 404.
Is the UUID type effects my mapping? What should I change to be able to access my student_uid?
Finally I found out the problem, which is something I haven't read anywhere. When a one-to-one join has to be done, JPA provides the default name of the join as the table name underscore id(subject_id). In my case, I have a tablename called "subject" in the database and the PK called simply "uid". So what you have to do is append the table name with the name of the attribute, which to join to:
#OneToOne
#JoinColumn(name="subject_uid")//the pattern is: "tablename_joined attribute"
private Subject subject_uid;
Related
I write app in Spring.
This is my json: (it is an array of json objects)
[{"id" : 643419352,
"status" : "removed_by_user",
"url" : "https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html",
"created_at" : "2020-11-27 10:46:07",
"activated_at" : "2020-12-11 12:41:12",
"valid_to" : "2020-12-17 15:38:10",
"title" : "opona 12 1/2 \" x 2 1/4 etrto 62-203 + dętka",
"description" : "opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym",
"category_id" : 1655,
"advertiser_type" : "private",
"external_id" : null,
"external_url" : null,
"contact" : {
"name" : "Damazy",
"phone" : "501474399"
},
"location" : {
"city_id" : 10609,
"district_id" : 301,
"latitude" : "51.80178",
"longitude" : "19.43928"
},
"images" : [ {
"url" : "https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700"
} ],
"price" : {
"value" : "9",
"currency" : "PLN",
"negotiable" : false,
"budget" : false,
"trade" : false
},
"salary" : null,
"attributes" : [ {
"code" : "state",
"value" : "used",
"values" : null
} ],
"courier" : null
}, {
"id" : 643435839,
"status" : "removed_by_user",
"url" : "https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html",
"created_at" : "2020-11-27 11:53:47",
"activated_at" : "2020-11-27 11:54:36",
"valid_to" : "2020-12-17 15:38:07",
"title" : "opona 4.80/4.00 - 8 do taczki nowa!!!",
"description" : "opona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8. \r\nopona do taczki, nowa, nigdy nie używana, stan idealny.\r\nrozmiar 4.80/4.00-8.",
"category_id" : 1636,
"advertiser_type" : "private",
"external_id" : null,
"external_url" : null,
"contact" : {
"name" : "Damazy",
"phone" : "501474399"
},
"location" : {
"city_id" : 10609,
"district_id" : 301,
"latitude" : "51.80178",
"longitude" : "19.43928"
},
"images" : [ {
"url" : "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700"
} ],
"price" : {
"value" : "9",
"currency" : "PLN",
"negotiable" : false,
"budget" : false,
"trade" : false
},
"salary" : null,
"attributes" : [ {
"code" : "state",
"value" : "new",
"values" : null
} ],
"courier" : null
}]
and this is my entity class Advert :
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Advert {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long ident;
private int id;
private String status;
private String url;
private String created_at;
private String activated_at;
private String valid_to;
private String title;
#Lob
private String description;
private int category_id;
private String advertiser_type;
private Long external_id;
private String external_url;
private String salary;
private String attributes;
private String courier;
#Embedded
private Location location;
#Embedded
private Contact contact;
#Embedded
private Price price;
private String images;
and my saveAdverts method :
#RequestMapping("/saveadverts")
public String saveAdverts() throws IOException {
HttpEntity<String> requestEntity = entity.requestEntityProvider();
String url = "https://www.olx.pl/api/partner/adverts";
ResponseEntity<JsonNode> responseEntity = template.exchange(url, HttpMethod.GET, requestEntity, JsonNode.class);
String adverts = responseEntity.getBody().get("data").toString();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
try {
Advert[] array = objectMapper.readValue(adverts, Advert[].class);
for(Advert a : array) {
advertRepository.save(a);
}
} catch (Exception e) {
System.out.println(e);
}
return "index";
}
what I want to do is to parse json to entity objects and save all adverts objects to sql database in one table.
Method execution stops with exception on this line :
Advert[] array = objectMapper.readValue(adverts, Advert[].class);
I get this error message :
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY)
at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])
Please notice that the field "images" realtes to nested array of json objects.
Please help, I spent one week on this and no result. Thanks
com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot deserialize value of type java.lang.String from Array value (token JsonToken.START_ARRAY) at [Source: (StringReader); line: 24, column: 14] (through reference chain: java.lang.Object[][0]->pl.vida.model.Advert["images"])
You are getting the above exception because you are trying to convert an array of objects into a String which is not possible. See in your JSON images & attributes are array of objects.
"images": [{ "url": "https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700" }],
"attributes": [{ "code": "state", "value": "new", "values": null }]
and in your Advert class you have created images & attributes as String types.
private String attributes;
private String images;
Generally, for array kind of object, we make fields either List or Set and if the field is List/Set then we need to create separate classes for them and map as OneToMany relationship. So creating separate classes means separate tables will be created but you don't want to have multiple tables. You want to store all the data in a single table. In a normal case, it is not possible but if we write some additional configuration classes then we can achieve your requirement. These tweaks have been provided by Hibernate itself.
So basically, Hibernate has provided some built-in types like String, Integer, Float, Date, Timezone, etc. Here you can check the complete list of built-in types. But according to our requirements, we can create custom types as well. So to store the array kind of data Hibernate didn't provide any built-in type. Hence we shall create a custom type.
Solution:
So we want to store an array of object data and we can easily store it in com.fasterxml.jackson.databind.JsonNode object. But Hibernate doesn't support this class as a field type. Hence to make supportable for this class we need to write 2 extra classes i.e. JsonNodeStringType.java & JsonNodeStringDescriptor.
JsonNodeStringType.java
public class JsonNodeStringType extends AbstractSingleColumnStandardBasicType<JsonNode> implements DiscriminatorType<JsonNode> {
public static final JsonNodeStringType INSTANCE = new JsonNodeStringType();
public JsonNodeStringType() {
super(VarcharTypeDescriptor.INSTANCE, JsonNodeStringDescriptor.INSTANCE);
}
#Override
public String getName() {
return "JsonNode";
}
#Override
public JsonNode stringToObject(String xml) {
return fromString(xml);
}
#Override
public String objectToSQLString(JsonNode value, Dialect dialect) {
return '\'' + toString(value) + '\'';
}
}
JsonNodeStringDescriptor.java
public class JsonNodeStringDescriptor extends AbstractTypeDescriptor<JsonNode> {
public static final ObjectMapper mapper = new ObjectMapper();
public static final JsonNodeStringDescriptor INSTANCE = new JsonNodeStringDescriptor();
public JsonNodeStringDescriptor() {
super(JsonNode.class, ImmutableMutabilityPlan.INSTANCE);
}
#Override
public String toString(JsonNode value) {
try {
return mapper.writeValueAsString(value);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
#Override
public JsonNode fromString(String string) {
try {
return mapper.readTree(string);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
return null;
}
#Override
public <X> X unwrap(JsonNode value, Class<X> type, WrapperOptions options) {
if (value == null) {
return null;
}
if (String.class.isAssignableFrom(type)) {
return (X) toString(value);
}
throw unknownUnwrap(type);
}
#Override
public <X> JsonNode wrap(X value, WrapperOptions options) {
if (value == null) {
return null;
}
if (String.class.isInstance(value)) {
return fromString(value.toString());
}
throw unknownWrap(value.getClass());
}
}
Now our Advert class will look like as
import org.hibernate.annotations.Type;
#Setter
#Getter
#ToString
#Entity
#Table(name = "advert")
#TypeDef(name = "JsonNode", typeClass = JsonNodeStringType.class)
public class Advert {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ident", unique = true, nullable = false)
private Long ident;
private int id;
private String status;
private String url;
private String created_at;
private String activated_at;
private String valid_to;
private String title;
#Lob
private String description;
private int category_id;
private String advertiser_type;
private Long external_id;
private String external_url;
private String salary;
private String courier;
#Embedded
private Location location;
#Embedded
private Contact contact;
#Embedded
private Price price;
#Type(type = "JsonNode")
private JsonNode images;
#Type(type = "JsonNode")
private JsonNode attributes;
}
Here we go. If you execute the below code it works perfectly.
String advertsString = "[ { \"id\": 643419352, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-12-1-2-x-2-1-4-etrto-62-203-detka-CID767-IDHxILu.html\", \"created_at\": \"2020-11-27 10:46:07\", \"activated_at\": \"2020-12-11 12:41:12\", \"valid_to\": \"2020-12-17 15:38:10\", \"title\": \"opona 12 1/2 \\\" x 2 1/4 etrto 62-203 + dętka\", \"description\": \"opona w bardzo dobrym stanie + dętka, rozmiar 12 1/2 x 2 1/4 , dętka z zaworem samochodowym\", \"category_id\": 1655, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/efa9any4ryrb-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"used\", \"values\": null } ], \"courier\": null }, { \"id\": 643435839, \"status\": \"removed_by_user\", \"url\": \"https://www.olx.pl/d/oferta/opona-4-80-4-00-8-do-taczki-nowa-CID628-IDHxN3p.html\", \"created_at\": \"2020-11-27 11:53:47\", \"activated_at\": \"2020-11-27 11:54:36\", \"valid_to\": \"2020-12-17 15:38:07\", \"title\": \"opona 4.80/4.00 - 8 do taczki nowa!!!\", \"description\": \"opona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8. \\r\\nopona do taczki, nowa, nigdy nie używana, stan idealny.\\r\\nrozmiar 4.80/4.00-8.\", \"category_id\": 1636, \"advertiser_type\": \"private\", \"external_id\": null, \"external_url\": null, \"contact\": { \"name\": \"Damazy\", \"phone\": \"501474399\" }, \"location\": { \"city_id\": 10609, \"district_id\": 301, \"latitude\": \"51.80178\", \"longitude\": \"19.43928\" }, \"images\": [ { \"url\": \"https://ireland.apollo.olxcdn.com:443/v1/files/qmvssagjnq1r2-PL/image;s=1000x700\" } ], \"price\": { \"value\": \"9\", \"currency\": \"PLN\", \"negotiable\": false, \"budget\": false, \"trade\": false }, \"salary\": null, \"attributes\": [ { \"code\": \"state\", \"value\": \"new\", \"values\": null } ], \"courier\": null } ]";
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Advert[] adverts = objectMapper.readValue(advertsString, Advert[].class);
for (Advert advert : adverts) {
Advert saved = advertRepository.save(advert);
System.out.println("saved " + saved.getIdent());
}
I hope your problem gets resolved which you have been stuck with for one week. If you don't want to manually create these types of descriptors you can follow this article to use as an external dependency.
we are using spring-boot 2.2.1 and query-dsl-mongoDB 4.2.1.
we are using spring-data.mongodb findAll method tp find books from book collection using
various predicates like bookUid, authorId, customerId, status, isbn and provisioningId.
I can able to construct for all the attributes except bookInfo.
Please find the sample collection for reference.
{
"_id" : ObjectId("6036323daa819c04005cff68"),
"bookUid" : "spring_boot",
"authorId" : "602bc44827e37ca2ba281f54",
"customerId" : "75e1c48e",
"status" : "ACTIVE",
"name" : "Spring boot",
"statusTimestamp" : ISODate("2021-02-24T11:07:28.000Z"),
"deleted" : false,
"bookInfo" : {
"isbn" : "240220211202",
"provisioningId" : "240220211202"
},
"customInfo" : {},
"version" : 1,
"countryCode" : "CZ",
"_class" : "book-collection"
}
And this is the Java class,
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Book {
#Id
private String id;
#Indexed
#WhiteSpaceTrim
private String bookUid;
private String authorId;
private String customerId;
private String status;
private String name;
private Date statusTimestamp;
private boolean deleted;
#WhiteSpaceTrim
private Map<String, String> provisionInfo;
private SmartObject customInfo;
private int version;
private String countryCode;
}
While looking into query DSL autogenerated class I see the types for BookInfo is QMap
public final ext.java.util.QMap provisionInfo = new
ext.java.util.QMap(forProperty("bookInfo"));
I tried to construct below predicate whether bookInfo matches the bey key and value
Map<String, String> expre = new HashMap<>();
expre.put(key, value);
predicates.add(QBook.book.provisionInfo.in(expre).isTrue());
But no luck it was not working and thrown exception, Then tried following expression
PathBuilder entityPath = new PathBuilder<>(Book.class,
"bookInfo");
predicates.add(entityPath.getMap("map", String.class, String.class).get(key).eq(value));
But it returns an empty collection always even though we have matching isbn or provisioningId.
I was looking into the documentation but I couldn't find any help either.
Any help would be really appreciable.
I want to aggregate a collection of documents that match certain conditions, group them and map the output to a different class object. The aggregation works fine and I get the expected total but the _id field is always NULL.
I'm using spring-data-mongodb 2.1.11 and MongoDB 3.6.
This is the class to be aggregated:
#Document
public class LegOrder {
public static class Key {
#Indexed
long itemId;
long transactionId;
...
}
#Id
private Key id;
#Indexed
private long brandId;
private int units;
...
}
This is the aggregation output class:
#Document
public class ItemAggregation {
public static class Key {
#Indexed
long itemId;
#Indexed
long brandId;
}
#Id
private Key id;
private long total;
...
}
My aggregation method:
public ItemAggregation aggregate(long itemId, long brandId) {
MatchOperation matchStage = Aggregation.match(new Criteria().andOperator(
Criteria.where("id.itemId").is(itemId),
Criteria.where("brandId").is(brandId)
));
GroupOperation groupStage = Aggregation.group("id.itemId", "brandId")
.sum("units").as("total")
...
;
Aggregation aggregation = Aggregation.newAggregation(matchStage, groupStage);
return mongoTemplate.aggregate(aggregation, LegOrder.class, ItemAggregation.class).getUniqueMappedResult();
}
The executed query in MongoDB:
[
{
"$match": {
"$and": [
{ "_id.itemId": 1 },
{ "brandId": 2}
]
}
},
{
"$group": {
"_id": {
"itemId": "$_id.itemId",
"brandId": "$brandId"
},
"total": { "$sum": "$units" }
}
}
]
If I run this query in the mongo shell the _id field is properly populated.
Any idea how to achieve it?
Thank you
Sorry for the late response. I faced this issue now and found this solution.
My aggregation output in console is
{
"_id" : {
"ownerId" : BinData(3,"xkB0S9Wsktm+tSKBruv6og=="),
"groupbyF" : "height"
},
"docs" : [
{
"id" : ObjectId("5fe75026e211c50ef5741b31"),
"aDate" : ISODate("2020-12-26T15:00:51.056Z"),
"value" : "rrr"
}
]
}
{
"_id" : {
"ownerId" : BinData(3,"AAAAAAAAAAAAAAAAAAAAAA=="),
"groupbyF" : "weight"
},
"docs" : [
{
"id" : ObjectId("5fe6f702e211c50ef5741b2f"),
"aDate" : ISODate("2020-12-26T08:40:28.742Z"),
"value" : "55"
},
{
"id" : ObjectId("5fe6f6ade211c50ef5741b2e"),
"aDate" : ISODate("2020-12-26T08:38:58.098Z"),
"value" : "22"
}
]
}
The mapping that worked for me
import lombok.Data;
#Data
public class AggregationLatest2Type{
private String ownerId;
private String key;
private List<Doc> docs;
#Data
public class Doc{
private String _id;
private Date aDate;
private String value;
}
}
I have a retailer class with nested dbref of Address. I would like to fetch retailers based on city which is part of Address class. But I am getting below error.
org.springframework.data.mapping.model.MappingException: Invalid path
reference address.city! Associations can only be pointed to directly
or via their id property!
Could you please let me know what's wrong and how to fix this?
Code is given below
#Document(collection="retailer")
#TypeAlias("retailer")
public class Retailer extends CommonDomainAttributes implements Serializable {
public Retailer() {
// TODO Auto-generated constructor stub
}
#DBRef
private List<Product> products;
private String shopName;
#DBRef
private Address address;
}
#Document(collection="address")
#TypeAlias("address")
public class Address extends CommonDomainAttributes implements Serializable {
/**
*
*/
private static final long serialVersionUID = 8820483439856446454L;
private String addrLine1;
private String addrLine2;
private String locality;
private String city;
private String state;
private String country;
}
I am using following query to get the data
#Override
public List<Retailer> findRetailersByProductNameAndCity(String productName,
String city) {
Query query = Query.query(Criteria.where/*("product.productName").is(productName).and*/("address.city").is(city));
//BasicQuery basicQuery = new BasicQuery("{ product.productName : { $eq : '"+productName+"' }, address.city : { $eq : '"+city+"' }}");
//System.out.println(basicQuery.toString());
//query.fields().include("user");
//query.fields().include("address");
List<Retailer> retailers = mongoTemplate.find(query, Retailer.class);
return retailers;
}
Data
db.retailer.find().pretty();
{
"_id" : ObjectId("55eb14e077c8f563fb2c11ab"),
"_class" : "retailer",
"createDate" : ISODate("2015-09-05T16:14:24.489Z"),
"lastModifiedDate" : ISODate("2015-09-05T16:14:24.489Z"),
"createdBy" : "UnAuntenticatedUser",
"lastModifiedBy" : "UnAuntenticatedUser",
"user" : DBRef("IdeaRealtyUser", ObjectId("55eb14e077c8f563fb2c11aa")),
"products" : [
DBRef("product", ObjectId("55eb14e077c8f563fb2c1193")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c1194")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c119a"))
],
"address" : DBRef("address", ObjectId("55eb14e0a1fd2e78e05053c2"))
}
{
"_id" : ObjectId("55eb14e077c8f563fb2c11ad"),
"_class" : "retailer",
"createDate" : ISODate("2015-09-05T16:14:24.561Z"),
"lastModifiedDate" : ISODate("2015-09-05T16:14:24.561Z"),
"createdBy" : "UnAuntenticatedUser",
"lastModifiedBy" : "UnAuntenticatedUser",
"user" : DBRef("IdeaRealtyUser", ObjectId("55eb14e077c8f563fb2c11ac")),
"products" : [
DBRef("product", ObjectId("55eb14e077c8f563fb2c1193")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c1194")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c119b")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c119f")),
DBRef("product", ObjectId("55eb14e077c8f563fb2c11a0"))
],
"address" : DBRef("address", ObjectId("55eb14e0a1fd2e78e05053c1"))
}
I have been playing around with Morphia as I had issues with working with referenced documents using Spring Data for Mongodb. Below are snapshots of the data I have and the code...
Here are my User and Group classes (getters and setters omitted)
User Class
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Reference;
#Entity(value="users")
public class User {
#Id
private String id;
private String username;
private String firstname;
private String lastname;
private String email;
private long cellnumber;
private Date datejoined;
private boolean active;
private Account account;
#Reference(value="usergroups", lazy=true)
private List<Group> usergroups = new ArrayList<>();
}
The group class.
#Entity(value="groups")
public class Group {
#Id
private String id;
private String name;
private String description;
private long creationdate;
#Reference
private User user;
}
I am now running the following methods, named scenario1() and scenario2() in a main method.
private void scenario1() {
User newUser = setupUser(new User());
datastore.save(newUser);
Query<User> query = datastore.createQuery(User.class)
.field("firstname").equal("Jome");
User user = query.get();
Account account = new Account();
account.setBalance(0.0);
account.setSmsvalue(0.18);
UpdateOperations<User> update = datastore
.createUpdateOperations(User.class);
update.set("account", account);
user.setAccount(account);
datastore.save(user);
List<Group> groups = query.get().getUsergroups();
Group group1 = new Group();
group1.setCreationdate(new Date().getTime());
group1.setDescription("test group for first user");
group1.setName("Group One");
group1.setUser(query.get());
datastore.save(group1);
groups.add(group1);
update.add("usergroups", group1);
datastore.update(query, update);
Group group2 = new Group();
group2.setCreationdate(new Date().getTime());
group2.setDescription("Another test group for first user");
group2.setName("Group Two");
group2.setUser(query.get());
datastore.save(group2);
groups.add(group2);
update.add("usergroups", group2);
datastore.update(query, update);
System.out.println(user);
List<Group> savedgroups = user.getUsergroups();
System.out.println(savedgroups);
}
And this is the output
User [id=52e8894ef148a7f866b9f1ac, username=jomski2013, firstname=Jome, lastname=Akpoduado, email=jomea#yookos.com, cellnumber=123456789]
[Group [id=52e8894ef148a7f866b9f1ad, name=Group One, description=test group for first user, creationdate=1390971214880, owner=User [id=52e8894ef148a7f866b9f1ac, username=jomski2013, firstname=Jome, lastname=Akpoduado, email=jomea#example.com, cellnumber=123456789]], Group [id=52e8894ef148a7f866b9f1ae, name=Group Two, description=Another test group for first user, creationdate=1390971214884, owner=User [id=52e8894ef148a7f866b9f1ac, username=jomski2013, firstname=Jome, lastname=Akpoduado, email=jomea#example.com, cellnumber=123456789]]]
and the view from the mongo console
> db.users.find().pretty()
{
"_id" : ObjectId("52e8894ef148a7f866b9f1ac"),
"account" : {
"balance" : 0,
"smsvalue" : 0.18
},
"active" : true,
"cellnumber" : NumberLong("123456789"),
"className" : "org.imanmobile.sms.core.domain.User",
"datejoined" : ISODate("2014-01-29T04:53:34.746Z"),
"email" : "jomea#example.com",
"firstname" : "Jome",
"lastname" : "Akpoduado",
"password" : "$2a$10$c0SAy7eJZv06eqmAWQNUP.YrXtB7tDOdi11lkZqlAVgzVGU9RCbCS",
"usergroups" : [
DBRef("groups", "52e8894ef148a7f866b9f1ad"),
DBRef("groups", "52e8894ef148a7f866b9f1ae")
],
"username" : "jomski2013"
}
> db.groups.find().pretty()
{
"_id" : ObjectId("52e8894ef148a7f866b9f1ad"),
"className" : "org.imanmobile.sms.core.domain.Group",
"name" : "Group One",
"description" : "test group for first user",
"creationdate" : NumberLong("1390971214880"),
"user" : DBRef("users", "52e8894ef148a7f866b9f1ac")
}
{
"_id" : ObjectId("52e8894ef148a7f866b9f1ae"),
"className" : "org.imanmobile.sms.core.domain.Group",
"name" : "Group Two",
"description" : "Another test group for first user",
"creationdate" : NumberLong("1390971214884"),
"user" : DBRef("users", "52e8894ef148a7f866b9f1ac")
}
However, when I subsequently run scenario2() below
private void scenario2() {
Query<User> query = datastore.createQuery(User.class);
User user = query.field("username").equal("jomski2013").get();
System.out.println("Number of groups: " + user.getUsergroups().size());
}
I get the error...
Caused by: org.mongodb.morphia.mapping.MappingException: The reference({ "$ref" : "groups", "$id" : "52e8894ef148a7f866b9f1ad" }) could not be fetched for org.imanmobile.sms.core.domain.User.usergroups
at org.mongodb.morphia.mapping.ReferenceMapper.resolveObject(ReferenceMapper.java:304)
at org.mongodb.morphia.mapping.ReferenceMapper$1.eval(ReferenceMapper.java:243)
at org.mongodb.morphia.utils.IterHelper.loopOrSingle(IterHelper.java:89)
at org.mongodb.morphia.mapping.ReferenceMapper.readCollection(ReferenceMapper.java:239)
at org.mongodb.morphia.mapping.ReferenceMapper.fromDBObject(ReferenceMapper.java:163)
at org.mongodb.morphia.mapping.Mapper.readMappedField(Mapper.java:602)
at org.mongodb.morphia.mapping.Mapper.fromDb(Mapper.java:584)
at org.mongodb.morphia.mapping.Mapper.fromDBObject(Mapper.java:294)
at org.mongodb.morphia.query.MorphiaIterator.convertItem(MorphiaIterator.java:71)
at org.mongodb.morphia.query.MorphiaIterator.processItem(MorphiaIterator.java:58)
at org.mongodb.morphia.query.MorphiaIterator.next(MorphiaIterator.java:53)
at org.mongodb.morphia.query.QueryImpl.get(QueryImpl.java:408)
at org.imanmobile.sms.PlayClass.scenario2(PlayClass.java:35)
at org.imanmobile.sms.PlayClass.run(PlayClass.java:29)
at org.springframework.boot.SpringApplication.runCommandLineRunners(SpringApplication.java:644)
My question is why is Morphia able to fetch the groups that were saved the first time around but not subsequently? What's different or what should i really be looking out for or missing?
There is a type mismatch between the usergroups DBRef array and the documents these are referencing:
"usergroups" : [
DBRef("groups", "52e82c05f148a7310a899a50"),
DBRef("groups", "52e82c05f148a7310a899a4f")
]
The $ref.id are of type String
Whereas the _id in the groups collection are of type ObjectId:
"_id" : ObjectId("52e82c05f148a7310a899a4f")
and
"_id" : ObjectId("52e82c05f148a7310a899a50")
This is why the references could not be fetched.
You don't have to run a secondary query, this is done automatically by Morphia.
It's hard to determine the cause for this mismatch. Could it be that initially in your Java Group Class you had
private String ObjectId; field, which had been used for insertion, and then later changed to private String id;?
In any case to fix this, you will have to wrap the $ref.id values inside usergroups array with an ObjectId to fix this:
"usergroups" : [
DBRef("groups", ObjectId("52e82c05f148a7310a899a50")),
DBRef("groups", ObjectId("52e82c05f148a7310a899a4f"))
]
You will also have to change your Group mapping accordingly (set the id property as ObjectId)
If you want to leave things as are (treat everything as Strings), just drop your database (I guess you are just experimenting), and start from fresh