How to use spring-data mongodb without objectId to upsert? - java

I am trying to use spring-data to replace our current usage of POJO to Document with mongodb, in this particular case instead of using a field as Id for the entity I use some fields.
For example, if I have this pojo:
class Thing {
public String firstName;
public String lastName;
public Int age;
}
At the moment to save it to mongodb I first do a conversion to Document:
Document doc = new Document();
doc.append("firstName", thing.firstName);
doc.append("lastName", thing.lastName);
doc.append("age", thing.age);
Then I create a query:
BasicDBObject query = new BasicDBObject(
and(
new BasicDBObject("firstName", thing.firstName),
new BasicDBObject("lastName", thing.lastName))
And then I upsert the doc using mongodb driver.
collection.updateOne(query, document, UpdateOptions().upsert(true))
This way I get the age updated only and do not need to bother about the ObjectId that there is no meaning for me.
Now I want to change it to spring-data-mongodb to be less error prone when writing the document conversion part and be more productive. But I could not find how to do something similar with spring-data-mongodb, the save in MongoRepository api is base in the ObjectId and I can not find any annotation or bean to override/create to let me do something like this.
In a perfect world I would have something like a composite key for spring-data-mongodb. So for instance I would have to just write my POJO this way:
class Thing {
#Id public String firstName;
#Id public String lastName;
public Int age;
}
Or (not the better case but maybe acceptable)
class ThingKey {
public String firstName;
public String lastName;
}
class Thing {
#Id public ThingKey thingKey;
public Int age;
}
And the save would be able to figure out that this is a upsert to a existing entity and do act with this regards.
PS: The POJOs here are simplified versions to help understand the problem and are not the final version that I am using.

There is a section on "upserting" in the official docs. It looks like you have to use either the upsert() or findAndModify() on MongoTemplate.
Alternatively, you could just do a find(), perform your changes to the object that is returned, and then do a save().

Related

MongoDB map Java POJO without org.bson.types.ObjectId

I want to map Java POJO to MongoDB and implement CRUD operations. I follow manual https://mongodb.github.io/mongo-java-driver/3.11/driver/getting-started/quick-start-pojo/ and all seems fine but one Person property is MongoDB dependent:
public final class Person {
private ObjectId id;
private String name;
private int age;
private Address address;}
This is org.bson.types.ObjectId id. This makes my domain layer dependent on MongoDB, and this actually what I would not call a POJO at all. Instead of ObjectId I would like to have String or other Java core class like Long or something like that. It could could be a kind of getter/setter too. How can I achieve this?
I tried to remove id from Person
package com.mongo_demo.domain;
public final class Person {
private String name;
private int age;
private Address address;}
and use this as my domain object, while to operate with MongoDB in DAO I will use child class:
package com.mongo_demo.mongo_domain;
public final class Person extends com.mongo_demo.domain.Person {
private ObjectId id;
}
Obviously my domain class now not have dependencies on MongoDB, but still lacks String id and no way to have getter method for it, as ObjectId id attribute is in child class.
I not sure is it fine to not have access to id value in my services code, because I could need to call delete by id operation, otherwise I will have to create my own object unique identifier, in addition to ObjectId id attribute, which will be natural key with consequent drawbacks.
PS No getter-setter methods shown, as I use Lombok #Data annotations instead.

Elasticsearch: search for two different document in a single query

I have two entities Merchant and Customer:
public class Merchant{
private UUID id;
private String name;
//... other fields and getters/setters
}
public class Customer{
private UUID id;
private String name;
//... other fields and getters/setters
}
These two entities are sightly different from each-other.
What I'am trying to to do is when I search with the term "John" I want to get both a merchant named "John Market" and a customer called "John Smith".
To achieve this I indexed these entities to a single index.
#Document(indexName = "merchant_customer_index", type = "merchantorcustomer")
public class MerchantOrCustomer {
#Id
private UUID id;
private String name;
private int type;
//...
My query can return both Merchant and Customer:
List<MerchantOrCustomer> result = elasticsearchTemplate.queryForList(nativeSearchQuery, MerchantOrCustomer.class);
I distinguish them programmatic(if(result.get(i).getType() == 0 we received Merchant else Customer)
Then use their id to extract actual object from relational db.
I searched a lot, but couldn't find anything that can help to estimate if it is a good practice. Is it a good practice?
Please, give me a hint if there is a better way.
There doesn't seem to be anything wrong with what you did unless there is some collusion as mentioned by #Ivan in comments.
Here is another possible way to do if you were using elasticTemplate- Spring Data Elasticsearch: Multiple Index with same Document or if you are using queryBuilder - https://www.elastic.co/guide/en/elasticsearch/client/java-api/current/java-search.html

Using ObjectId as String in Java (Manual reference) with spring-data mongodb

In MongoDB documentation they suggest to use ObjecId for manual references.
please see https://docs.mongodb.com/manual/reference/database-references/#document-references
original_id = ObjectId()
db.places.insert({
"_id": original_id,
"name": "Broadway Center",
"url": "bc.example.net"
})
db.people.insert({
"name": "Erin",
"places_id": original_id,
"url": "bc.example.net/Erin"
})
I'm using spring-data-mongodb and what I'm looking for is to have a People class defined like this:
#Document
public class People {
private String name;
#Reference // or any Annotation to convert an ObjectId to a String
private String placesId;
private String url;
}
How to have a "places_id" as ObjectId in mongoDB but mapped to a String in our POJO ?
I was expecting to have an annotation like #Reference but it seems to not be implemented.
I don't understand why we don't have this kind of annotation in spring-data-mongodb. I don't want to implement an explicit converter like suggested in spring documentation for all documents that use manual references.
Maybe it's not the right approach.
Did I miss something ?
UPDATE :
I like the idea to have a POJO using only String instead of ObjectId. Let's say I've got a class Place like this :
#Document
public class Place {
#Id
private String id;
private String name;
}
place.getId() will be a String but people.getPlaceId() will be an ObjectId. I want to avoid this unnecessary mapping.
The solution would be:
import org.springframework.data.mongodb.core.mapping.Field;
import org.springframework.data.mongodb.core.mapping.FieldType;
public class People {
#Field(targetType = FieldType.OBJECT_ID)
private String placesId;
}
This will map POJO string to ObjectId in MongoDB.
Why don't you leave the field as ObjectId?
#Document
public class People {
private String name;
private ObjectId placesId;
private String url;
}
If you want to query by this field you can do this:
For lists
List<String> ids // the ids as strings
List<ObjectId> objIds = ids .stream()
.map(i -> new ObjectId(i))
.collect(Collectors.toList());
For single String
String id // single id
ObjectId objId = new ObjectId(id);
If you want to make a real reference to an other object in your database, use the #DBRef annotation which is provided by Spring Data.
Your updated code could look like the following:
#Document
public class People {
private String name;
#DBRef
private Place place;
private String url;
}
Spring Data will then automatically map a Place object to your People object. Internally this is done with a reference to the unique ObjectId. Try this code and have a look at your mongo database.
For more information have a look at: MongoDb with java foreign key
I have a solution very simple:
#JsonSerialize(using= ToStringSerializer.class)
private ObjectId brandId;
...
put that on the attribute that is Object Id, and the ObjectId gets and inserts like string

How to return an entity by property if it already exists?

#Entity
public class Language {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(length = 2)
private String code; //EN, DE, US
public Language(String code) {
this.code = code;
}
}
#Entity
public class ProductText {
#OneToOne(Cascade.ALL)
private Language lang;
}
ProductText text = new ProductText();
text.setLang(new Language("en")); //what if "en" exists?
dao.save(text);
Now, when I persist the ProductText, everytime a new Language object would be generated.
Can I prevent this, and in case a language table entry with code = 'en' exists this existing entity should be linked instead.
My initial goal is to not having to repeat the countryCodeString "EN" multiple times in my product-text table, but just reference the id. But does this really make sense? Should I rather just use the plain String without an extra table? (I later want to query a list of productTexts where lang = 'de').
Is the only change executing a select like dao.findByLang("en") before?
Or is there also some hibernate feature that would support this without explicit executing a query myself?
Do you process the value "en" further or do you display it directly? If only used for displaying purposes, I would just store the string, but if you want to reduce redundancy by using foreign key IDs you have to create an Entity containing the language string en which can be persisted via entity manager and which you have to obtain out of the entity manager before persisting to reuse it.
If there is only three different possible values for the language, you can also use an enum like thisĀ :
public enum Language {
EN("EN"),
DE("DE"),
US("US");
private String code; //EN, DE, US
public Language(String code) {
this.code = code;
}
// Getter...
}
#Entity
public class ProductText {
#Enumerated(EnumType.STRING)
// Or #Enumerated(EnumType.ORDINAL)
private Language lang;
}
EnumType.STRING will store the enum in the database as a String, while EnumType.ORDINAL will store it as an int. Int is maybe a little more efficient, but the mapping could change if you insert a new value in your enum. String is more flexible since it will use the names of your enum members.
In both case, you don't have to manage a separate entity and hibernate will not create an additional table, and it's more type-safe than using a plain string.
If the only value in Language is a 2 or 3 letter string, why not just have the string as a member? This will be quicker and more efficient.

Call method in JPA

I'm using ObjectDB with JPA. I would like to call myMethod(). For example:
entityManager.createQuery("SELECT ... FROM ... WHERE MyClass.myMethod() = 100")
Is it possible? Maybe any annotation is required before method in the class?
#Entity
public class MyClass implements Serializable {
#Basic
private int x;
#Basic
private int y;
public int myMethod() {
return x*1000+y;
}
}
JPQL is not exactly an object-based query language. You can't define your own methods, and JPQL provides a very limited set of functions. So if you want to keep within the JPA spec then the answer is no; would have to be JPA-implementation specific - DataNucleus JPA certainly allows you to have your own methods in the query language (as a vendor extension), no idea about your quoted JPA provider - that said though, it would only execute such a query in the datastore if you put the code for that method in a query method implementation (as opposed to in the class)
Yes, you can! And no additional annotations are required.
ObjectDB is an implementation of an object-oriented database system (OODBS) and as a result allows you to interact with database items as objects, that includes calling methods, using inheritance and polymorphism, etc.
This is a simple working example I have. With a class like this:
#Entity
public class Person {
private static final long serialVersionUID = 1L;
#Id #GeneratedValue
private long id;
private String firstName;
private String lastName;
Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFullName() {
return firstName + " " + lastName;
}
}
This query returns correct results:
entityManager.createQuery(
"SELECT p FROM Person p WHERE p.getFullName()='John Johnson'", Person.class).getResultList();
JPQL is translated into SQL, so you cannot include a Java method call, as your database (most likely) does not support Java.
In JPA 2.1 you will be able to use the FUNCTION operator to call "database" functions. Some database do support defining functions in Java, but normally a proprietary database language is used (such as PL/SQL).
EclipseLink supports both FUNC and FUNCTION operators for calling database functions. You can also define your own operators using the OPERATOR operator which allows you to define your own custom database function call in Java.
http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#EclipseLink_special_operators
Not the way you are looking for. If you want to use custom method, you can create and register them before using them.
e.g. create a function as below:
public int myMethod(int x, int y){
return x*1000+y;
}
Then register this function using registerFunction() with the dialect of your database. Once done, you can write a query as :
from MyClass data where myMethod(data.x, data.y) =100;
Hoe this helps.
No you can't do that. Since JPA always works with prepared statement (parameterized SQL), you can only set parameters in the WHERE clause of the JPQL query like
entityManager.createQuery("SELECT .... FROM ... WHERE
someCondition=:someValue).setParameter("someValue", "parameterValue");
Make the method (myMethod()) return that value somehow and replace the second parameter which is parameterValue of the setParameter() method by the value returned by your method, myMethod().

Categories