I'm using Spring Data MongoDB to generate an aggregated query. At one point I do this:
// 5. Rejoin the array with group.
group("email", "name", "surname", "birthday", "creationTime", "updateTime", "technology")
.push(SCORES_FIELD).as(SCORES_FIELD));
The generated step (in the log) is this:
"$group" : {
"_id" : {
"email" : "$_id",
"name" : "$name" ,
"surname" : "$surname" ,
"birthday" : "$birthday" ,
"creationTime" : "$creationTime" ,
"updateTime" : "$updateTime" ,
"technology" : "$technology"
} ,
"scores" : { "$push" : "$scores"}
}
Which is perfectly fine, I've tested it on the Mongo shell and gives back exactly what I want.
The problem is that when I do the same with Spring Data, the email field (which is the _id field in Mongo) is mapped as null. There's probably something wrong in my mapping but I haven't been able to figure out what is it exactly. Here's the model:
#Document(collection = "user")
public class User implements UserDetails {
private static final long serialVersionUID = 1L;
private String name;
private String surname;
private LocalDate birthday;
#Id
#Field("_id")
private String email;
private Collection<? extends GrantedAuthority> authorities;
private String password;
private Set<Score> scores;
private LocalDateTime creationTime;
private LocalDateTime updateTime;
private String technology;
// Getters and setters, hashmap, equals and toString
}
I've done other queries and everything works out perfectly. I'm having this problem only on this one which is the only aggregation I do.
Promoting my comment to answer.
The _id can't be mapped into email because group stage returns multiple keys in _id document. previousOperation() is just a convenience method to return the _id from previous group operation. You can try changing to group("email").first("name").as("name").... and see if it helps.
I would expect spring to read the Field annotation from the model and map the _id field back to email now.
Related
I am trying to map an array of Objects to a field. All the fields in that object are being mapped to columns with different name but similar structure. The response structure should be:
"customers": [
{
"firstName": "string",
"lastName": "string",
"products": [
{
"description":"string",
"amount": "string"
},
{
"description":"string",
"amount": "string"
}
]
}
]
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
. I need to map these two fields to the product(object). How should I approach to solve the problem using JPA annotations if possible?
For the reference:
Customers.class
#Entity
public class Customers implements Serializable {
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#ElementCollection
List<Products> products;
}
Product.class
#Embeddable
public class Product implements Serializable {
#Column(?)
private String description;
#Column(?)
private String amount;
}
Inside the products field, I have a list of product(description and amount). In DB, columns are stored like
product_des1,product_amt1,product_des2,product_amt2.....product_des30,product_amt30
So your Products JPA entity should simply look like this:
#Embeddable
public class Products implements Serializable {
#Column(name = "product_des1")
private String description1;
#Column(name = "product_amt1")
private String amount1;
#Column(name = "product_des2")
private String description2;
#Column(name = "product_amt2")
private String amount2;
// ... repeat
}
if you don't want to do additional mapping between the DB and JPA entities (which I don't recommend - I try to keep JPA entities as exact representation of a DB row and map, if necessary, in Java and not between different technologies).
I am in middle of developing a REST API using spring data JPA and need some assistance.
My REST API will process a request to create a Group and assign members to the group based on request data. The URL for the request will be #PostMapping("/createGroup/{user-id}/groups")
I have below classes,
Member class:
public class Member {
private int memberId;
private String memberName;
private String memberCity;
// getters / setters
My Request Body class:
public class AppRequest {
private String name;
private String description;
private List<Member> members;
// getters / setters
My GroupEntity:
#Entity
#Table(name="Groups")
public class GroupEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int id;
#OneToOne
#JoinColumn(name="groupOwnerId")
private MemberEntity groupOwnerId;
private String groupName;
private String description;
#ManyToMany(cascade=CascadeType.ALL)
#JoinTable(
name="Group_Members",
joinColumns= #JoinColumn(name="id"),
inverseJoinColumns= #JoinColumn(name="memberId")
)
Set<MemberEntity> members = new HashSet<>();
// getters / setters
My MemberEntity:
#Entity
#Table(name="Members")
public class MemberEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private int memberId;
private String memberName;
private String memberCity;
#ManyToMany(cascade=CascadeType.ALL, mappedBy="members")
Set<GroupEntity> groups = new HashSet<>();
// getters / setters
And finally My controller,
#RestController
public class AppController {
#Autowired
MemberRepository memRepo;
#Autowired
GroupRepository groupRepo;
#PostMapping("/createGroup/{user-id}/groups")
#Transactional
public ResponseEntity<AppResponse> createGroup(#RequestBody AppRequest request,
#PathVariable(name="user-id") String userId) {
AppResponse response = new AppResponse();
GroupEntity group = new GroupEntity();
group.setGroupName(request.getName());
group.setDescription(request.getDescription());
// Code that causes error when trying to save owner ID
MemberEntity mem = new MemberEntity();
mem.setMemberId(Integer.parseInt(userId));
group.setGroupOwnerId(mem);
List<Member> members = request.getMembers();
Set<MemberEntity> storedMembers = new HashSet<>();
Set<GroupEntity> storedGroups = new HashSet<>();
storedGroups.add(group);
for(Member member : members) {
if(member.getMemberId() != 0) { // existing member
MemberEntity memberentity = new MemberEntity();
memberentity.setMemberName(member.getMemberName());
memberentity.setMemberCity(member.getMemberCity());
storedMembers.add(memberentity);
memberentity.setGroups(storedGroups);
memRepo.save(memberentity);
}
else { // new member
//some logic
}
group.setMembers(storedMembers);
groupRepo.save(group);
}
return null;
}
}
Request Body would be something like this,
{
"description": "Funny Group",
"members": [
{
"memberCity": "Mumbai",
"memberName": "Amit"
},
{
"memberId": 123
}
],
"name": "My Group"
}
What I want to achieve is when a group is created, I want to add the user-Id in the REST URL [/createGroup/{user-id}/groups] as a owner ID for that group. To achieve this I am manually creating a member entity and setting that as a groupOwnerId as shown below,
MemberEntity mem = new MemberEntity();
mem.setMemberId(Integer.parseInt(userId));
group.setGroupOwnerId(mem);
If I comment above piece of code application boots up but the groupOwnerId value is set as null which is obvious as I am not setting it anywhere. So if I write above code, application boots up properly. But when I hit the endpoint, I get below error,
org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKB0L8UXRYL2TEVI7BTCMI4BYOD: PUBLIC.""GROUPS"" FOREIGN KEY(GROUP_OWNER_ID) REFERENCES PUBLIC.MEMBERS(MEMBER_ID) (12345)"; SQL statement:
insert into groups (id, description, group_name, group_owner_id) values (null, ?, ?, ?)
I am trying to figure out HOW I can do the mapping correctly, so that I can map the groupOwnerId from the URL. And also the new members gets saved into the database with auto generated IDs.
I am using H2 database at the moment but will eventually move to MySQL or Oracle.
Please let me know if you guys can help deciding the approach for this issue.
First of all, I would use the following URL patterns for the group and user resources:
/groups/{groupId}
/users/{userId}
This will allow you to create users and groups separately, since a group can exist without any users and a user can exist without being member of a group.
As usual a POST operation to /groups/ to create a group and POST to /users/ to create a user.
After having created a group and a user, I would retrieve both the entities (or use the entities returned by the POST operations) to set a group owner of the group and create a JSON (or XML) representation of the group.
The JSON representation is then PUT to the URL /groups/{groupId} (with the actual id of the group inserted in the URL) in order to persist the group with the new group owner.
Hope this helps!
This is one sample document (row) from a collection (table) called product which is in MongoDB:
{
"_id" : ObjectId("5cb39171528a28f831f"),
"seller" : "Product Seller 1",
"title" : "Product Title 1",
"price" : "159.75",
"brand" : "Product Brand 1",
"productId" : NumberInt(247)
}
The Java Model for this collection looks like this:
#Entity(value = "product", noClassnameStored = true)
#Data
public class Product {
#Id
#Property("_id")
private ObjectId objectId;
private String seller;
private String title;
private String price;
private String brand;
private Long productId;
}
I am using Morphia as a MongoDB Java Driver here. And I am using the Lombok #Data annotation which provides the constructor, getters and setters.
The model works perfectly and is able to fetch data from the DB. My new requirement is to add an additional field in the model. The name of the new field would be stringObjectId, which will store the value of the objectId in String format.
Please note that there will not be any changes in the DB document. That is, no new field is getting added to the DB document.
I want that when I am fetching data from the collection in the form of list or a single row
query.asList();
the value in the stringObjectId should also get populated. Something like this:
stringObjectId = objectId.toString()
No far I have tried to override the setters and the constructor of this model to set the value in the stringObjectId variable but nothing seems to work. Is there any way this can be achieved?
Check out Morphia LifeCycle events: https://www.playframework.com/modules/morphia-1.2.4/lifecycle-def
The #PostLoad one will be helpful here. You probably will need #Transient as well.
this is more like a "best-practice" question.
I am playing around with spring-boot and hibernate to build a small CRUD Service.
Let's assume, I have an entity called Customer (which is annotated as hibernate entity...) which has all kinds of properties like name, middlename, lastname, ...
#Entity
#Table(name = "OCUS")
public CustomerEntity {
// Props
#Column(name = "Name")
#SerializedName("name")
#Expose
private String name;
#Column(name = "MName")
#SerializedName("mname")
#Expose
private String middlename;
...
#Column(name = "Status")
#SerializedName("status")
#Expose
private CustomerEntityType status = CustomerEntityType.NEW;
#Column(name = "Valid")
#SerializedName("valid")
#Expose
private Boolean valid = false;
// getters and setters
}
I also created a CustomerEntityRepository which extends the JPARepository like this:
#Repository
public interface CustomerEntityRepository extends JpaRepository {
}
After that, I created a RestController to have GET/POST/DELETE/... Methods for my customers.
#RestController
#RequestMapping("/v1/customers")
public class CustomerRestController {
#Autowired
CutomerEntityRepository customerRepo;
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#RequestBody CustomerEntity customer) {
customerRepo.save(customer);
// ... Build ResponseEntity...
}
...
}
Let's assume, the RequestBody does not include all properties from my CustomerEntity, maybe I have Properties like "status" or "valid".
e.g.:
{
"name" : "Jon",
"lastName" : "Doe",
"field1" : "value1",
...
}
My question now is:
What has to be done, so that the customer I am going to save into the CustomerEntityRepository gets all the standard values set (see property: status and valid) without having to check all properties if it was included in the request body.
My Goal is only to save customers, which represent my CustomerEntity with all the default values when getting customers via my Rest Controller.
Thank you for any feedback
Robert
[EDIT]:
Because my question seems to be unclear, I'll try to elaborate it further:
Assume my REST Endpoint receives the following Json which represents a CustomerEntity:
{
"name" : "Jon",
"lastName" : "Doe"
}
The Deserializer from the spring-boot packages seems to look up for a suitable constructor of my customerentity.
So if I implement a constructor like this:
public CustomerEntity(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
The deserializer tries to construct the customerEntity nicely via this constructor. All my standard values like valid will be set to false and status will be set to CustomerEntityType.NEW;
Let's assume the next incoming Json is:
{
"name" : "Jon",
"lastName" : "Doe",
"middleName" : "Harry"
}
But this time, I have no suitable constructor to create the customer via a constructor. So creating a vast number of constructors to represent all possible variants is not the right way, I think.
I don't know how exactly the deserializer now creates my CustomerEntity object out of the JSON, but the object I receive in my method:
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#RequestBody CustomerEntity customer) {
customerRepo.save(customer);
// ... Build ResponseEntity...
}
Has only set the 3 properties: name, lastName, middleName and all other properties of CustomerEntity are null.
And I need to prevent such CustomerEntities to be saved to the database.
Hope this case is now more clear.
You can use hibernate validator which is an implementation of JSR 380.
and btw, if you are using any of the annotation provided by hibernate-validator, you'll have to use #valid annotation in your controller from package javax.validation.
so your controller will be something like,
#PostMapping
public ResponseEntity<CustomerEntity> createCustomer(#Valid #RequestBody CustomerEntity customer) { }
you can read more about that in detail here : https://spring.io/guides/gs/validating-form-input/
http://hibernate.org/validator/
Not eally sure what you are asking , if you want to set the default values of fields/attribute
you can use :
#Column(name="status", columnDefinition="Decimal(10,2) default '100.00'")
ref: Setting default values for columns in JPA
I am having trouble writing code that would allow get a user and claim details in a straightforward way. This is my MongoDB structure,
db.user.find();
user:
{
"name" : "KSK",
"claim" : [objectId("52ffc4a5d85242602e000000"),objectId("52ffc4a5d85242602e000001")]
}
claim:
[
{
"_id" : "52ffc4a5d85242602e000001",
"claimName" :"XXXX"
},
{
"_id" : "52ffc4a5d85242602e000000",
"claimName" :"YYY"
}
]
My Entity class is:
#Document(collection="user")
public class User{
#Id
private String id;
private String name;
#DBRef
private List<Claim> claim;
// setter and getter
}
Claim Class:
#Document(collection="Claim")
public class Claim{
#Id
private String id;
private String claimName;
}
I have a method to get the users by name like below,
public User findByName(String name);
If I try to hit this method am getting an error that,
No converter found capable of converting from type org.bson.types.ObjectId to type java.lang.String
So I changed my User entity class as like below,
Instead of private List<Claim> claim;
Changed as Private List<ObjectId> claim;
Now if I execute a method(findByName), I get a user object that has both claimed object ids ("52ffc4a5d85242602e000001","52ffc4a5d85242602e000000"), then iterate the claim list and get the claim details corresponding to the claim object Id.
Instead of doing this, when I execute findByName method I want to get a user and claim details. How can I achieve this functionality?
If you reference your Claims in the User class with #DBRef, your JSON should not only contain the ID but the reference to the collection where to find the ID as well, like this:
{
"name" : "KSK",
"claim" : [
{
"$ref" : "claim", // the target collection
"$id" : ObjectId("52ffc4a5d85242602e000000")
}
]
}
That is how Spring-Data maps your Java objects to MongoDB. If you start with a blank database and let Spring create and save the relations, you should have no problems using
#DBRef List<Claim> claims;
My suggestion is not to set that Claim class into separate #Document or just switch back to Relational Databases, because it's not a Mongo approach.
Also, if you insist on current architecture you can try using #DBRef above that List in User.class into smth like this:
public class ParentModel {
#Id
private String id;
private String name;
private ParentType parentType;
private SubType subType;
#DBRef
private List<Model> models;
....
}
as an alternative to #DBRef, take a look at RelMongo (link)
which provides a powerfull way to manage relations, in your case it will be like this :
#OneToMany(fetch = FetchType.LAZY)
private list<Claim> claims;