Spring Data Rest ManytoMany POST - java

First, let me explain my usecase. It's pretty straight forward. There is a User entity and a Service entity. I have ManytoMany association between User and Service using UserService as the Joined entity (joined table) Initially,there will be some set of users and some set of services. Users can subscribe to any Service at any point of time. In that case, an entry will be added to UserService. But, I am getting null pointer exception when i tried to create a new UserService association. I could create User and Service individually.
My Entities are :
User.java
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="`user`", schema="emm")
public class User implements Serializable {
public User() {
}
#Column(name="id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_USER_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_USER_ID_GENERATOR", strategy="native")
private long id;
#ManyToOne(targetEntity=dao.models.Tenant.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="tenant_id", referencedColumnName="id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Tenant tenant;
#OneToOne(targetEntity=dao.models.Person.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="Person_id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Person person;
#Column(name="password", nullable=true, length=255)
private String password;
#Column(name="email", nullable=false, length=255)
private String email;
#Column(name="status", nullable=true, length=255)
private String status;
#ManyToMany(mappedBy="user", targetEntity=dao.models.TenantGroup.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.TenantGroup> group = new java.util.ArrayList<dao.models.TenantGroup>();
#OneToMany(mappedBy="user", targetEntity=dao.models.UserService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserService> userService = new java.util.ArrayList<dao.models.UserService>();
public void setId(long value) {
this.id = value;
}
public long getId() {
return id;
}
public void setPassword(String value) {
this.password = value;
}
public String getPassword() {
return password;
}
public void setEmail(String value) {
this.email = value;
}
public String getEmail() {
return email;
}
public void setStatus(String value) {
this.status = value;
}
public String getStatus() {
return status;
}
public void setTenant(dao.models.Tenant value) {
this.tenant = value;
}
public dao.models.Tenant getTenant() {
return tenant;
}
public void setPerson(dao.models.Person value) {
this.person = value;
}
public dao.models.Person getPerson() {
return person;
}
public void setGroup(java.util.List<dao.models.TenantGroup> value) {
this.group = value;
}
public java.util.List<dao.models.TenantGroup> getGroup() {
return group;
}
public java.util.List<dao.models.UserService> getUserService() {
return userService;
}
public void setUserService(
java.util.List<dao.models.UserService> userService) {
this.userService = userService;
}
public String toString() {
return String.valueOf(getId());
}
}
Service Entity
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="service", schema="emm")
public class Service implements Serializable {
public Service() {
}
#Column(name="service_id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_SERVICE_SERVICE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_SERVICE_SERVICE_ID_GENERATOR", strategy="native")
private long id;
#Column(name="service_name", nullable=false, length=255)
#org.hibernate.annotations.Index(name="service_service_name")
private String serviceName;
#Column(name="description", nullable=true, length=255)
private String description;
#Column(name="app_key", nullable=false, length=255)
private String appKey;
#Column(name="app_token", nullable=false, length=255)
private String appToken;
#Column(name="learnmoreurl", length=255)
private String learnMoreURL;
#Column(name="trialurl", length=255)
private String trialURL;
#ManyToMany(mappedBy="service", targetEntity=dao.models.Device.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.Device> device = new java.util.ArrayList<dao.models.Device>();
#OneToMany(mappedBy="service", targetEntity=dao.models.ServiceParam.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.ServiceParam> serviceParams = new java.util.ArrayList<dao.models.ServiceParam>();
#OneToMany(mappedBy="service", targetEntity=dao.models.TenantService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.PERSIST, org.hibernate.annotations.CascadeType.MERGE,
org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.TenantService> tenantService = new java.util.ArrayList<dao.models.TenantService>();
#OneToMany(mappedBy="service", targetEntity=dao.models.UserService.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.DELETE})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserService> userService = new java.util.ArrayList<dao.models.UserService>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getAppKey() {
return appKey;
}
public void setAppKey(String appKey) {
this.appKey = appKey;
}
public String getAppToken() {
return appToken;
}
public void setAppToken(String appToken) {
this.appToken = appToken;
}
public String getLearnMoreURL() {
return learnMoreURL;
}
public void setLearnMoreURL(String learnMoreURL) {
this.learnMoreURL = learnMoreURL;
}
public String getTrialURL() {
return trialURL;
}
public void setTrialURL(String trialURL) {
this.trialURL = trialURL;
}
public java.util.List<dao.models.Device> getDevice() {
return device;
}
public void setDevice(java.util.List<dao.models.Device> device) {
this.device = device;
}
public java.util.List<dao.models.ServiceParam> getServiceParams() {
return serviceParams;
}
public void setServiceParams(
java.util.List<dao.models.ServiceParam> serviceParams) {
this.serviceParams = serviceParams;
}
public java.util.List<dao.models.TenantService> getTenantService() {
return tenantService;
}
public void setTenantService(
java.util.List<dao.models.TenantService> tenantService) {
this.tenantService = tenantService;
}
public java.util.List<dao.models.UserService> getUserService() {
return userService;
}
public void setUserService(
java.util.List<dao.models.UserService> userService) {
this.userService = userService;
}
public String toString() {
return String.valueOf(getId());
}
}
And finally the join entity
UserService.java
package dao.models;
import java.io.Serializable;
import javax.persistence.*;
#Entity
#org.hibernate.annotations.Proxy(lazy=false)
#Table(name="user_service" ,schema="emm")
public class UserService implements Serializable {
public UserService() {
}
#Column(name="id", nullable=false, unique=true)
#Id
#GeneratedValue(generator="EMM_USER_SERVICE_ID_GENERATOR")
#org.hibernate.annotations.GenericGenerator(name="EMM_USER_SERVICE_ID_GENERATOR", strategy="native")
private long id;
#ManyToOne(targetEntity=dao.models.User.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="user_id", referencedColumnName="id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.User user;
#ManyToOne(targetEntity=dao.models.Service.class, fetch=FetchType.LAZY)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.LOCK})
#JoinColumns({ #JoinColumn(name="service_id", referencedColumnName="service_id", nullable=false) })
#org.hibernate.annotations.LazyToOne(value=org.hibernate.annotations.LazyToOneOption.NO_PROXY)
private dao.models.Service service;
#Column(name="param_name", nullable=false)
private String paramName;
#Column(name="param_value", nullable=true)
private String paramValue;
#OneToMany(mappedBy="userService", targetEntity=dao.models.UserServiceToken.class)
#org.hibernate.annotations.Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE, org.hibernate.annotations.CascadeType.LOCK})
#org.hibernate.annotations.LazyCollection(org.hibernate.annotations.LazyCollectionOption.TRUE)
private java.util.List<dao.models.UserServiceToken> userServiceToken = new java.util.ArrayList<dao.models.UserServiceToken>();
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public dao.models.User getUser() {
return user;
}
public void setUser(dao.models.User user) {
this.user = user;
}
public dao.models.Service getService() {
return service;
}
public void setService(dao.models.Service service) {
this.service = service;
}
public String getParamName() {
return paramName;
}
public void setParamName(String paramName) {
this.paramName = paramName;
}
public String getParamValue() {
return paramValue;
}
public void setParamValue(String paramValue) {
this.paramValue = paramValue;
}
public java.util.List<dao.models.UserServiceToken> getUserServiceToken() {
return userServiceToken;
}
public void setUserServiceToken(
java.util.List<dao.models.UserServiceToken> userServiceToken) {
this.userServiceToken = userServiceToken;
}
public String toString() {
return String.valueOf(getId());
}
}
Now my issue, GET requests are working properly, But, I get null pointer exception when I try to create a new UserService.
POST : http://localhost:8080/em/api/userServices/
I am trying to associate user 1 with service 2
Request :
{
"paramName": "p1",
"paramValue": "v1",
"service": {
"href": `"http://localhost:8080/em/api/userServices/1/service/2"`
},
"user": {
"href": `"http://localhost:8080/em/api/userServices/1/user/1"`
}
}
Error Messgae :
{
"cause": {
"cause": {
"cause": null,
"message": null
},
"message": "(was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"])"
},
"message": "Could not read JSON: (was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: dao.models.UserService[\"service\"])"
}
GET http://localhost:8080/em/api/userServices yields me the following output :
{
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices{?page,size,sort}",
"templated" : true
}
},
"_embedded" : {
"userServices" : [ {
"paramName" : "p1",
"paramValue" : "v1",
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices/1"
},
"userServiceToken" : {
"href" : "http://localhost:8080/em/api/userServices/1/userServiceToken"
},
"user" : {
"href" : "http://localhost:8080/em/api/userServices/1/user"
},
"service" : {
"href" : "http://localhost:8080/em/api/userServices/1/service"
}
}
}, {
"paramName" : "pone",
"paramValue" : "vone",
"_links" : {
"self" : {
"href" : "http://localhost:8080/em/api/userServices/2"
},
"userServiceToken" : {
"href" : "http://localhost:8080/em/api/userServices/2/userServiceToken"
},
"user" : {
"href" : "http://localhost:8080/em/api/userServices/2/user"
},
"service" : {
"href" : "http://localhost:8080/em/api/userServices/2/service"
}
}
} ]
},
"page" : {
"size" : 20,
"totalElements" : 2,
"totalPages" : 1,
"number" : 0
}
}
Has anyone successfully implemented ManyToMany association using Spring-data-rest. If so, kindly help me in this regard

I figured out the issue and got it working.
Previously, my request body was:
{
"paramName": "p1",
"paramValue": "v1",
"service": {
"href": "http://localhost:8080/em/api/userServices/1/service/2"
},
"user": {
"href": "http://localhost:8080/em/api/userServices/1/user/1"
}
}
I figured out that it should be the following:
{
"paramName": "p1",
"paramValue": "v1",
"service": "http://localhost:8080/em/api/services/2",
"user": "http://localhost:8080/em/api/users/1"
}
I feel there is still an issue with spring-data-rest. Kindly clarify, if anyone feels otherwise. Even with the fixed request, i was getting null constraint for ServiceId. I figured out in db, the primary key column for service was service_id. Even though, I have the entity mapping properly (My Id property in Service Entity maps properly to service_id in db), it was not working, I had to change the column name to id to get this working.
Spring-Data-Rest should depending upon the Entity mappings for Id right ? If so, then Still there is a bug.
Thanks,
Vivek

Related

Dynamodb Mapper Save functions not creating the item in table

I tried using the dynamodbmapper for crud operations over the table. When I am using the save functionality from the mapper code getting executed with out any error or exceptions but when I scan the table records were not reflecting what could be the possible error I am doing in below way
try{
User user = new User();
/* added some dummy data to user object*/
static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
static DynamoDBMapper dynamoDB = new DynamoDBMapper(client, new DynamoDBMapperConfig(DynamoDBMapperConfig.SaveBehavior.CLOBBER));
dynamoDB.save(user);
}catch(Exception e){
}
My class object
#DynamoDBTable(tableName = "users")
public class User {
private String id;
private String user_id;
private String email;
private String name;
private String mobile_no;
private Integer createdDate;
private Integer modifiedDate;
#DynamoDBHashKey(attributeName = "id")
public String getId(){
return id;
}
public void setId(String id) {
this.id = id;
}
#DynamoDBAttribute(attributeName = "user_id")
public String getUser_id() {
return user_id;
}
public void setUser_id_ref(String user_id) {
this.user_id = user_id;
}
#DynamoDBAttribute(attributeName = "email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#DynamoDBAttribute(attributeName = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#DynamoDBAttribute(attributeName = "mobile_no")
public String getMobile_no() {
return mobile_no;
}
public void setMobile_no(String mobile_no) {
this.mobile_no = mobile_no;
}
#DynamoDBAttribute(attributeName = "created_date")
public Integer getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Integer createdDate) {
this.createdDate = createdDate;
}
#DynamoDBAttribute(attributeName = "modified_date")
public Integer getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(Integer modifiedDate) {
this.modifiedDate = modifiedDate;
}
For scanning i have used the command
aws dynamodb scan --table-name users
Result for the above command is coming as below
{
"Items": [],
"Count": 0,
"ScannedCount": 0,
"ConsumedCapacity": null
}
Table Description
aws dynamodb describe-table --table-name users
{
"Table": {
"AttributeDefinitions": [
{
"AttributeName": "id",
"AttributeType": "S"
}
],
"TableName": "users",
"KeySchema": [
{
"AttributeName": "id",
"KeyType": "HASH"
}
],
"TableStatus": "ACTIVE",
"CreationDateTime": "2022-09-29T19:33:02.692000+05:30",
"ProvisionedThroughput": {
"LastDecreaseDateTime": "2022-09-29T19:45:29.763000+05:30",
"NumberOfDecreasesToday": 0,
"ReadCapacityUnits": 1,
"WriteCapacityUnits": 1
}
}
How ever I tried creating a test class where I just have hashkey attribute and tried saving the data it got persisted
package com.moneyview.model.dynamo;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBAutoGeneratedKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBHashKey;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBTable;
import com.amazonaws.services.dynamodbv2.model.ScalarAttributeType;
#DynamoDBTable(tableName = "users")
public class UserTest {
private String id;
#DynamoDBHashKey(attributeName = "id")
public String getId(){
return id;
}
public void setId(String id) {
this.id = id;
}
}
test code for inserting
public void testinsertItem(){
try{
UserTest test = new UserTest();
test.setId("8a8180967e8b5112017e8b99268702df");
dynamoDB.save(test);
}catch (Exception e){
System.out.println("Something went wrong while inserting the records to DynamoDb");
}
}
When I tried scanning the table
aws dynamodb scan --table-name users
Result:
{
"Items": [
{
"id": {
"S": "8a8180967e8b5112017e8b99268702df"
}
}
],
"Count": 1,
"ScannedCount": 1,
"ConsumedCapacity": null
}
not sure where I am doing wrong or if there is any issue in the config please help me
The code which you shared is correct, and the item should be persisted.
However, you would need to show your full class, and confirm how you are checking the item exists?
You mention it doesn't exist when your Scan? Are you scanning from your code directly after writing? Check that you have made your Scan Strongly Consistent to ensure you get the latest value.
https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html#DDB-Scan-request-ConsistentRead
If this does not resolve the issue, I will be able to help you further when you share the rest of your code.
Reviewing your updated code it seems pretty fine, I did notice some issues with the setters you defined, but i'm not certain that is your issue. The below code snippet works for me:
import com.amazonaws.services.dynamodbv2.AmazonDynamoDB;
import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder;
import com.amazonaws.services.dynamodbv2.datamodeling.*;
import java.util.List;
public class UserOverflow {
public static void main(String[] args){
AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build();
DynamoDBMapper dynamoDB = new DynamoDBMapper(client, new DynamoDBMapperConfig(DynamoDBMapperConfig.SaveBehavior.CLOBBER));
User myUser = new User();
myUser.setId("1");
try{
dynamoDB.save(myUser);
}catch(Exception e){
System.out.println(e.getMessage());
}
DynamoDBScanExpression scanExpression = new DynamoDBScanExpression()
.withConsistentRead(Boolean.TRUE);
try{
List<User> scanResult = dynamoDB.scan(User.class, scanExpression);
for (User user : scanResult) {
System.out.println(user.getId());
}
}catch (Exception e){
System.out.println(e.getMessage());
}
}
#DynamoDBTable(tableName = "users")
public static class User {
private String id;
private String user_id;
private String email;
private String name;
private String mobile_no;
private Integer createdDate;
private Integer modifiedDate;
#DynamoDBHashKey(attributeName = "id")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
#DynamoDBAttribute(attributeName = "user_id")
public String getUserId() {
return user_id;
}
public void setUserId(String user_id) {
this.user_id = user_id;
}
#DynamoDBAttribute(attributeName = "email")
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#DynamoDBAttribute(attributeName = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#DynamoDBAttribute(attributeName = "mobile_no")
public String getMobileNo() {
return mobile_no;
}
public void setMobileNo(String mobile_no) {
this.mobile_no = mobile_no;
}
#DynamoDBAttribute(attributeName = "created_date")
public Integer getCreatedDate() {
return createdDate;
}
public void setCreatedDate(Integer createdDate) {
this.createdDate = createdDate;
}
#DynamoDBAttribute(attributeName = "modified_date")
public Integer getModifiedDate() {
return modifiedDate;
}
public void setModifiedDate(Integer modifiedDate) {
this.modifiedDate = modifiedDate;
}
}
}
I'm not sure if your did not have log statements inside your catch block but that is one thing you should definitely add. Should that fail, I would take a look at the following:
IAM policy, do you have the correct permissions
Internet access, does where you are executing the code from have a path to the DynamoDB endpoint?

Pull Request Doesn't update repository

I am trying to change a Perfil object in PerfilRepository by sending the following request with the following code:
package br.com.bandtec.projetocaputeam.controller;
import br.com.bandtec.projetocaputeam.dominio.*;
import br.com.bandtec.projetocaputeam.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
#RestController
#RequestMapping("/caputeam")
public class CaputeamController {
//USER CLASS
public abstract class Perfil {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "nome")
#NotNull
#Size(min = 5,max = 30)
#Pattern(regexp = "^[a-zA-Z\s]*$", message = "Nome inválido! Digite apenas letras e espaçamento") //Permite apenas letras e espaço
private String nome;
#NotNull
#CPF
private String cpf;
#Column(name = "email")
#NotNull
#Email
private String email;
#NotNull
#Size(min = 5,max = 12)
private String senha;
private Integer telefone;
#DecimalMin("0")
#DecimalMax("5")
private Double avaliacao;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_endereco")
private Endereco endereco;
#NotNull
private String tipoPerfil;
#OneToOne(cascade = CascadeType.ALL)
private Categoria categoriaAutonomo;
//Getters
public Integer getId() {
return id;
}
public String getNome() {
return nome;
}
public String getCpf() {
return cpf;
}
public String getEmail() {
return email;
}
public String getSenha() {
return senha;
}
public Integer getTelefone() {
return telefone;
}
public Double getAvaliacao() {
return avaliacao;
}
public Endereco getEndereco() {
return endereco;
}
public String getTipoPerfil() {
return tipoPerfil;
}
public Categoria getCategoriaAutonomo() {
return categoriaAutonomo;
}
//Setters
public void setTipoPerfil(String tipoPerfil) {
this.tipoPerfil = tipoPerfil;
}
public void setNome(String nome) {
this.nome = nome;
}
public void setCpf(String cpf) {
this.cpf = cpf;
}
public void setEmail(String email) {
this.email = email;
}
public void setSenha(String senha) {
this.senha = senha;
}
public void setTelefone(Integer telefone) {
this.telefone = telefone;
}
public void setAvaliacao(Double avaliacao) {
this.avaliacao = avaliacao;
}
public void setEndereco(Endereco endereco) {
this.endereco = endereco;
}
public void setCategoriaAutonomo(Categoria categoriaAutonomo) {
this.categoriaAutonomo = categoriaAutonomo;
}
}
//--- REPOSITORIES
#Autowired
private PerfilService perfilService = new PerfilService();
//--- USUARIOS
#GetMapping("/usuarios")
public ResponseEntity getUsuario(){
return perfilService.getPerfisRepository();
}
#PostMapping("/cadastrar-usuario")
public ResponseEntity cadastrarUsuario(#RequestBody #Valid Perfil novoPerfil){
return perfilService.cadastrarUsuario(novoPerfil);
}
#PutMapping("/usuarios/{id}")
public ResponseEntity alterarUsuario(#RequestBody #Valid Perfil usuarioAlterado, #PathVariable int id){
return perfilService.alterarPerfil(usuarioAlterado,id);
}
And thats my method for changing my perfil object
public ResponseEntity alterarPerfil(Perfil perfilAlterado, int id){
perfisRepository.findById(id).map(perfil -> {
perfil.setNome(perfilAlterado.getNome());
perfil.setEmail(perfilAlterado.getEmail());
perfil.setTelefone(perfilAlterado.getTelefone());
return ResponseEntity.ok().build();
});
return ResponseEntity.badRequest().body("Alteração inválida!");
}
Im sending my put request in postman like that:
{
"id": 1,
"nome": "Beatriz Barros",
"cpf": "103.725.810-05",
"email": "bia#hotmail.com",
"senha": "121520",
"telefone": 1134577777,
"avaliacao": 4.9,
"endereco": {
"id": 1,
"cep": "03311111",
"bairro": "Vila Poeira",
"logradouro": "RuaFlor",
"numeroLogradouro": 7,
"complemento": null,
"uf": "sp",
"cidade": "SaoPaulo"
},
"tipoPerfil": "autonomo",
"categoriaAutonomo": {
"id": 1,
"nome": "Jardineiro",
"descricao": null
},
"precoAutonomo": 0.0
}
But it always returns status 400 bad request!
I already tried to send only the fields I want to change (name, email and phone) but it also didn't work
I also tried to send with and without the ID and nothing to work
How can I send a correct request?
Well, that's actually what you're doing.
perfisRepository.findById(id).map(perfil -> {
perfil.setNome(perfilAlterado.getNome());
perfil.setEmail(perfilAlterado.getEmail());
perfil.setTelefone(perfilAlterado.getTelefone());
return ResponseEntity.ok().build();
});
perfil -> { /* */ } is actually a lambda expression or in other words: an anonymous method. So instead of returning in the alterarPerfil method, you're returning in the anonymous method.
I've never worked with Spring, but from reading the docs, I assume that perfisRepository is implementing the CrudRepository interface.
The right code should be something like:
Perfil oldObject = perfisRepository.findById(id).get();
if(oldObject == null) return ResponseEntity.badRequest().body("Alteração inválida!");
oldObject.setNome(perfilAlterado.getNome());
oldObject.setEmail(perfilAlterado.getEmail());
/* and so on - you probably want to use reflection or a helping class to save you some time here */
// For a given entity, save will act as update too.
perfisRepository.save(oldObject);
return ResponseEntity.ok().build();
Since in your code, you aren't persisting any change.

why don't you see the "keys" in the json?

when I enter postman, I get the json, but without his "keys" why? Maybe I'm making a mistake and I haven't noticed. Some help please.
I am using a stored procedure to be able to do a crud.
this is the json that shows me postman. Shows me without his "key"
{
"data": [
[
1,
"aaa",
"aaa#gmail.com"
],
[
2,
"bbb",
"bbb#gmail.com"
],
[
3,
"ccc",
"ccc#gmail.com"
]
]
}
I would like to get something like this.
{
"data": [
{
userCod: 1,
userName: "aaa",
userEmail: "aaa#gmail.com"
},
{
userCod: 2,
userName: "bbb",
userEmail: "bbb#gmail.com"
},
{
userCod: 3,
userName: "ccc",
userEmail: "ccc#gmail.com"
}
]
}
I leave the code
public class ApiResponse {
private List<UserTest> data;
public List<UserTest> getData() {
return data;
}
public void setData(List<UserTest> data) {
this.data = data;
}
}
#Entity
#Table(name = "tbUsers")
public class UserTest implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "userCod")
private Long id;
#Column(name = "userName")
private String name;
#Column(name = "userEmail")
private String email;
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 String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
#Repository
public class ClienteDaoImpl implements IClienteDao{
#Autowired
private EntityManager em;
#SuppressWarnings("unchecked")
#Override
public ApiResponse mntUsers(int op) {
ApiResponse api = new ApiResponse();
Session session = em.unwrap(Session.class);
ProcedureCall call = session.createStoredProcedureCall("sp_MntUser");
call.registerParameter(1, Integer.class, ParameterMode.IN);
call.setParameter(1, op);
call.execute();
api.setData(call.getResultList());
return api;
}
}
#RestController
#RequestMapping(value = "/mntUsers")
public class ClienteController {
#Autowired
private ClienteServiceImpl serviceImpl;
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> CrudUsers(#RequestParam(value = "option", required = true) Integer op) {
return new ResponseEntity<>(serviceImpl.mntUsers(op),HttpStatus.OK);
}
}
Create a method called getCollectionType
public static <T, C extends Collection<T>> C getCollectionType(Iterable<?> from, C to, Class<T> listClass) {
for (Object item: from) {
to.add(listClass.cast(item));
}
return to;
}
Then use it on the following line:
api.setData(getCollectionType(call.getResultList(),
new ArrayList<UserTest>(),
UserTest.class));

Spring boot HATEOAS not automatically adding links to related resources

Links are not automatically being provided for resources when using HATEOAS to fetch resource collections.
When fetching a collection of ThreadResource with '/forum/threads', the response is:
{
"_embedded": {
"threadList": [
{
"posts": [
{
"postText": "This text represents a major breakthrough in textual technology.",
"thread": null,
"comments": [],
"thisId": 1
},
{
"postText": "This text represents a major breakthrough in textual technology.",
"thread": null,
"comments": [],
"thisId": 2
}
],
"createdBy": "admin",
"updatedBy": null,
"thisId": 1
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/forum/threads?page=0&size=10"
}
},
"page": {
"size": 10,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
I was expecting a JSON array of posts (instead of links to associated posts collection), like below:
{
"_embedded": {
"threadList": [
{
"createdBy": "admin",
"updatedBy": null,
"thisId": 1,
"_links": {
"posts": {
"href": "http://localhost:8080/forum/threads/1/posts"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/forum/threads?page=0&size=10"
}
},
"page": {
"size": 10,
"totalElements": 1,
"totalPages": 1,
"number": 0
}
}
I could manually build and add links in ResourceProcessor implementation classes and exclude the collection from being rendered using #JsonIgnore, but I have never had to do this before. What I am doing wrong?
The relevant classes are provided below. Thanks in advance!
#Entity
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany
private List<Post> posts;
#Column(name = "created_by")
private String createdBy;
#Column(name = "updated_by")
private String updatedBy;
public Thread() { }
#PrePersist
public void prePersist() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
posts = new ArrayList<>();
createdBy = auth.getName();
}
#PreUpdate
public void preUpdate() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
updatedBy = auth.getName();
}
public void submitPost(Post newPost) {
posts.add(newPost);
}
public Long getThisId() {
return id;
}
public List<Post> getPosts() {
return posts;
}
public void setPosts(List<Post> posts) {
this.posts = posts;
}
public String getCreatedBy() {
return createdBy;
}
public void setCreatedBy(String createdBy) {
this.createdBy = createdBy;
}
public String getUpdatedBy() {
return updatedBy;
}
public void setUpdatedBy(String updatedBy) {
this.updatedBy = updatedBy;
}
#Entity
public class Post {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String postText;
#ManyToOne(fetch = FetchType.LAZY)
private Thread thread;
#OneToMany
private List<Comment> comments;
public Post() { }
}
public class ThreadResource extends ResourceSupport {
private List<PostResource> postResources;
private String createdBy;
private String updatedBy;
public ThreadResource() {
}
}
public class PostResource extends ResourceSupport {
private String postText;
private ThreadResource threadResource;
private List<CommentResource> commentResources;
public PostResource() { }
#Component
public class PostResourceAssembler extends ResourceAssemblerSupport<Post, PostResource> {
public PostResourceAssembler() {
super(PostController.class, PostResource.class);
}
#Override
public PostResource toResource(Post entity) {
PostResource resource = super.createResourceWithId(entity.getThisId(), entity);
resource.setPostText(entity.getPostText());
return resource;
}
}
#Component
public class ThreadResourceAssembler extends ResourceAssemblerSupport<Thread, ThreadResource> {
private PostResourceAssembler postResourceAssembler;
public ThreadResourceAssembler(PostResourceAssembler postResourceAssembler) {
super(ThreadController.class, ThreadResource.class);
this.postResourceAssembler = postResourceAssembler;
}
#Override
public ThreadResource toResource(Thread entity) {
ThreadResource resource = super.createResourceWithId(entity.getThisId(), entity);
List<Post> posts = entity.getPosts();
List<PostResource> postResources = new ArrayList<>();
posts.forEach((post) -> postResources.add(postResourceAssembler.toResource(post)));
resource.setPostResources(postResources);
return resource;
}
}
#RestController
public class PostController {
private PostService postService;
#Autowired
public PostController(PostService postService) {
this.postService = postService;
}
#GetMapping("/forum/threads/{threadId}/posts/{postId}")
public ResponseEntity<Resource<Post>> getPost(#PathVariable long threadId, #PathVariable long postId) {
Post post = postService.fetchPost(postId)
.orElseThrow(() -> new EntityNotFoundException("not found thread " + postId));
Link selfLink = linkTo(PostController.class).slash(postId).withSelfRel();
post.add(selfLink);
return ResponseEntity.ok(new Resource<>(post));
}
#GetMapping
public ResponseEntity<PagedResources<Resource<Post>>> getPosts(PagedResourcesAssembler<Post> pagedResourcesAssembler) {
Pageable pageable = new PageRequest(0, 10);
Page<Post> posts = postService.fetchAllPosts(pageable);
PagedResources<Resource<Post>> resources = pagedResourcesAssembler.toResource(posts);
return ResponseEntity.ok(resources);
}
#PostMapping("/forum/threads/{threadId}/posts")
public HttpEntity<?> submitPost(#PathVariable long threadId) throws URISyntaxException {
Post post = postService.submitPost(threadId, new Post());
if (post != null) {
Link selfLink = linkTo(methodOn(PostController.class).submitPost(threadId)).slash(post.getThisId()).withSelfRel();
post.add(selfLink);
return ResponseEntity.created(new URI(selfLink.getHref())).build();
}
return ResponseEntity.status(500).build();
}
}
#RestController
public class ThreadController {
private ThreadService threadService;
private ThreadResourceAssembler threadResourceAssembler;
#Autowired
public ThreadController(ThreadService threadService,
ThreadResourceAssembler threadResourceAssembler) {
this.threadService = threadService;
this.threadResourceAssembler = threadResourceAssembler;
}
#GetMapping("/forum/threads/{threadId}")
public ResponseEntity<ThreadResource> getThread(#PathVariable long threadId) {
Thread thread = threadService.fetchThread(threadId)
.orElseThrow(() -> new EntityNotFoundException("not found thread " + threadId));
ThreadResource threadResource = threadResourceAssembler.toResource(thread);
return ResponseEntity.ok(threadResource);
}
#GetMapping("/forum/threads")
public ResponseEntity<PagedResources<Resource<ThreadResource>>> getThreads(PagedResourcesAssembler pagedResourcesAssembler) {
Pageable pageable = new PageRequest(0, 10);
Page<Thread> threads = threadService.fetchAllThreads(pageable);
PagedResources pagedResources = pagedResourcesAssembler.toResource(threads);
return ResponseEntity.ok(pagedResources);
}
#PostMapping("/forum/threads")
public HttpEntity<?> createThread() {
Thread thread = threadService.createThread();
return ResponseEntity.ok(thread);
}
#DeleteMapping("/forum/threads/{threadId}")
public HttpEntity<?> deleteThread(#PathVariable long threadId) {
Thread thread = threadService.fetchThread(threadId)
.orElseThrow(() -> new EntityNotFoundException("not found thread" + threadId));
threadService.closeThread(thread);
return ResponseEntity.ok().build();
}
}

Spring REST Jpa HATEOAS links not being created

I know that very similar questions have been asked here before, but I'm struggling to apply it to my problem.
I've recently started using jpa repositories for my data persistence needs and until now i had been content building HAL links that i wanted. Then i found that if i started using the #JoinTable and #JoinColumn annotations then i could have my links generated for me.
My problem is that when i hit the endpoint for my /posts, i don't get a HAL link for comments in the response.
#Entity
#Table(name="post")
public class Post {
#Id
#Column(name="id")
private #JsonIgnore Long id;
#Column(name="text")
private String text;
#Column(name="sender_id")
private Long senderId;
#Column(name="event_id")
private Long eventId;
protected Post () {};
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public Long getEventId() {
return eventId;
}
public void setEventId(Long eventId) {
this.eventId = eventId;
}
}
#Entity
#Table(name="comment")
public class Comment {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
#Column(name="id")
private #JsonIgnore Long id;
#Column(name="text")
private String comment;
#JsonIgnore
#Column(name="sender_id")
private Long senderId;
#JsonIgnore
#JoinColumn(name="post_id")
private Long post_id;
#JsonIgnore
#Column(name="deleted")
private Boolean deleted;
#ManyToOne
#JoinTable(name="post")
private Post post;
protected Comment() {};
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public Long getSenderId() {
return senderId;
}
public void setSenderId(Long senderId) {
this.senderId = senderId;
}
public Long getPostId() {
return post_id;
}
public void setPostId(Long postId) {
this.post_id = postId;
}
public Post getPost() {
return post;
}
public void setPost(Post post) {
this.post
}
}
#RestController
public class CommentController {
private JdbcOperations jdbc;
private final CommentRepository commentDao;
#Autowired
public CommentController(CommentRepository postDao) {
this.commentDao = postDao;
}
#RequestMapping(value = "/comments", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Comment>>> getPagedList(#RequestParam(value = "user-id", required = true) long userId,
#RequestParam(value = "page-size", required = false, defaultValue = "20") Long pageSize,
#RequestParam(value = "page", required = false, defaultValue = "1") Long pageNum) {
List<Comment> commentsList = (ArrayList) commentDao.findAll();
List<Resource<Comment>> resourceList = new ArrayList<>();
for (Comment comment : commentsList) {
Resource<Comment> postResource = new Resource<>(comment);
postResource.add(linkTo(methodOn(CommentController.class)
.getPagedList(userId, pageSize,pageNum)).withSelfRel());
resourceList.add(postResource);
}
Resources<Resource<Comment>> resources = new Resources<>(resourceList,
linkTo(methodOn(PostController.class)
.getPagedList(userId, pageSize, pageNum)).withSelfRel());
return ResponseEntity.ok(resources);
}
#RestController
public class PostController {
private final PostRepository postDao;
#Autowired
public PostController(PostRepository postDao) {
this.postDao = postDao;
}
#RequestMapping(value = "/posts", method = RequestMethod.GET)
public ResponseEntity<Resources<Resource<Post>>> getPagedList(#RequestParam(value="user-id", required = true) long userId,
#RequestParam(value = "page-size", required = false, defaultValue = "20") Long pageSize,
#RequestParam(value = "page", required = false, defaultValue = "1") Long pageNum) {
List<Post> modelsList = (ArrayList) postDao.readBydeletedIsFalseOrderByCreated();
List<Resource<Post>> resourceList = new ArrayList<>();
for (Post post : modelsList) {
Resource<Post> resource = new Resource<>(post);
resource.add(linkTo(methodOn(PostController.class)
.getSpecificModel(post.getId())).withSelfRel());
resource.add(linkTo(methodOn(UserController.class).getSpecificModel(post.getSenderId()))
.withSelfRel().withRel("sender"));
if (post.getEventId() != null) {
resource.add(linkTo(methodOn(EventController.class)
.getSpecificModel(post.getEventId())).withSelfRel().withRel("event"));
}
resourceList.add(resource);
}
Resources<Resource<Post>> resources = new Resources<>(resourceList,
linkTo(methodOn(PostController.class)
.getPagedList(userId, pageSize, pageNum)).withSelfRel());
return ResponseEntity.ok(resources);
}
The response i get when i hit the /posts endpoint is this, with no link to comments:
http://localhost:8090/posts?user-id=1
{
"_embedded": {
"postList": [
{
"created": -84330000000,
"text": "second post",
"senderId": 2,
"eventId": null,
"_links": {
"self": {
"href": "http://localhost:8090/posts/2"
},
"sender": {
"href": "http://localhost:8090/users/2"
}
}
},
{
"created": 1286665200000,
"text": "dfgtfy",
"senderId": 1,
"eventId": null,
"_links": {
"self": {
"href": "http://localhost:8090/posts/1"
},
"sender": {
"href": "http://localhost:8090/users/1"
}
}
},
{
"created": 1464735600000,
"text": "third",
"senderId": 1,
"eventId": null,
"_links": {

Categories