First of all I apologize, I am new using this technology and I really have many doubts.
I am trying to send a json object to my controller class, the problem is that with #RequestBody all the data arrives but the foreign keys arrive null. Example enter a new user with the id of a role that already exists in the BD, the user data arrives complete but the role ID arrives null
First I register the data of the role via POST and everything works perfectly with #RequestBody, but when I try to register a user with the role_id that is already saved in the database also using #RequestBody it is saved with the id_rol null
My user entity:
#Entity
public class Usuario implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String correo;
private String pass;
private boolean activo;
#OneToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="rol_id", nullable = false)
private Rol rol;
public Usuario(){}
public Usuario(String correo, String pass, boolean activo, Rol rol) {
this.correo = correo;
this.pass = pass;
this.activo = activo;
this.rol = rol;
}
/*getter y setter*/
My role entity:
#Entity
//#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Rol implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String nombre;
private boolean activo;
#OneToMany(mappedBy = "rol")
private List<Usuario> usuarios;
public Rol(){}
public Rol(String nombre, boolean activo, List<Usuario> usuarios) {
this.nombre = nombre;
this.activo = activo;
this.usuarios = usuarios;
}
/*getter y setter*/
User Repository:
#Repository
public interface UsuarioRepository extends JpaRepository<Usuario, Serializable> {
public abstract Usuario findByCorreo(String correo);
}
Role Repository:
public interface RolRepository extends JpaRepository<Rol, Serializable> {
}
User controller class:
#RestController
#RequestMapping("/v1")
public class UsuarioController {
#Autowired
UsuarioRepository usuarioRepository;
#PostMapping("/usuario/add")
public #ResponseBody ResponseEntity<String> crear(#Valid #RequestBody Usuario usuario) {
try{
usuarioRepository.save(usuario);
return new ResponseEntity<String>("Registro Exitoso..", HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<String>("Ha ocurrido un Error..", HttpStatus.BAD_REQUEST);
}
}
}
role controller class:
#RestController
#RequestMapping("/v1")
public class RolController {
#Autowired
RolRepository rolRepository;
#PostMapping("/rol/add")
public #ResponseBody ResponseEntity<String> crear(#Valid #RequestBody Rol rol) {
try{
rolRepository.save(rol);
return new ResponseEntity<String>("Registro Exitoso..", HttpStatus.OK);
}catch (Exception e){
return new ResponseEntity<String>("Ha ocurrido un Error..", HttpStatus.BAD_REQUEST);
}
}
}
This is what I send to my controller:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": "true",
"rol_id": "5"
}
And this is what I receive:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": true,
"rol_id": null
}
How should I receive this body with id_rol = 5?
I know that I am doing something wrong in #RequestBody, I appreciate any example you can provide as I have searched and I have not found that. Thank you..!
This is because you are sending a wrong JSON structure to your REST Controller.
In your Usuario class, the role_id is actually an object field with a name rol which represents Rol class.
So you need to pass Rol with id as an JSON object in your request:
{
"correo": "pepito#gmail.com",
"pass": "1234",
"activo": "true",
"rol": {
"id":"5"
}
}
Related
I have to create a very simple Spring "market" app.
No front-end needed
The Market:
The system must operate as a simplified market where users can be buyers or sellers.
Users:
user entity attributes: id:1, username:"User1", account:0
//account just gets incremented with each entry in the database.
The users can buy and sell items.
Items:
item entity attributes: id:3, name:Item1, ownerId:1.
example for interacting with items endpoints:
create: {id:1 name:"Item1", ownerId:1};
getAllItems with ownerId = 1 (use single query)
[
{
"id":3,
"name":”Item1”,
"ownerId":1,
“ownerUsername”:"User1"
}
]
Example:
"User1" owns "Item1". He wants to sell it for $100. He creates an active contract. Other users can review all active contracts and choose to participate. "User2" has enough money in her account and buys "Item1". The contract is now closed. "User1" receives $100 in his account. "User2" is the new owner of "Item1".
Contracts:
contract entity attributes: id, sellerId, buyerId, itemId, price,status. (The seller is the owner of the item and can not be the buyer)
endpoints - CRUD. Example for interacting with contracts endpoints:
create: {itemId : 3, price : 100}. Expected behavior: find the owner of item with id 3 in the DB (ownerId = 1) persist the new active contract in the DB:
{
"sellerId":1,
"itemId":3,
"price":100,
"active":true
}
update price of active contract by id: {"itemId":3, "price":200}
getAllActive contracts (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"itemId":3,
"price":200,
"active":true
}
]
closing active contract by id {"itemId":3, "buyerId":2}.
Expected behavior: update the accounts of users with id 1 and id 2.
getAllClosed contracts by optional parameters: itemId, sellerId, buyerId (use single native query):
[
{
"sellerId":1,
“sellerUsername”:"User1",
"buyerId":2,
“buyerUsername”:"User2",
"itemId":3,
"price":100,
"active":false
}
]
So far, these are my Entities:
BaseEntity:
#MappedSuperclass
public abstract class BaseEntity {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Users:
#Entity
#Table(name = "users")
public class User extends BaseEntity{
private String username;
private Long account;
private Set<Item> items;
public User() {
}
#Column(name = "username", nullable = false)
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "account", nullable = false)
public Long getAccount() {
return account;
}
public void setAccount(Long account) {
this.account = account;
}
#OneToMany(mappedBy = "id")
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
Items:
#Entity
#Table(name = "items")
public class Item extends BaseEntity{
private String name;
private String ownerUsername;
private User user;
public Item() {
}
#Column(name = "name")
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//get the id of the item's owner
#ManyToOne
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public String getOwnerUsername() {
return user.getUsername();
}
public void setOwnerUsername(String ownerUsername) {
this.ownerUsername = ownerUsername;
}
}
So, what should I do from here on?
If you've already created persistence layers (using Spring Data JPA or another mapper), You need to develop service logic and create a presentation layer.
like this (just user domain)
UserService (service layer)
#Service
#RequiredArgsConstructor
public class UserService {
private final UserJpaRepository repository;
#Transactional
public Long createUser(String username) {
User user = new User();
user.setUsername(username);
// other logic ...
repository.save(user);
return user.getId();
}
#Transactional(readonly = true)
public User getUser(Long id) {
return repository.findById(id)
.orElseThrow(() -> IllegalArgumentsException("Not Found Entity."))
}
}
UserAPIController (presentation layer)
#RestController
#RequiredArgsConstructor
public class UserAPIController {
private final UserService userService;
#PostMapping("/users")
public ResponseEntity<Long> createUser(#RequestBody CreateUserDTO dto) {
Long userId = userService.createUser(dto.getUsername());
return new ResponseEntity(userId, HttpStatus.CREATED);
}
#GetMapping("/users/{id}")
public ResponseEntity<User> getUser(#PathVariable Long id) {
User user = userService.getUser(id);
return new ResponseEntity(user, HttpStatus.OK);
}
}
I need that every time I list a portfolio it returns all the images that exist in that specific portfolio
I can list 1 by 1 via ID but when I send my endpoint to list all photos belonging to the ID of a specific portfolio it only returns me null
Photo Class
#Entity
public class Foto {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String nomeArquivo;
#ManyToOne(cascade = CascadeType.MERGE)
private Perfil autonomo;
#Column(length = 5_000_000)
private byte[] fotoByte;
private String tipoArquivo;
}//Getters and Setters
AutonomoService
#Autowired
FotosRepository fotosRepository;
#Autowired
PerfisRepository perfisRepository;
public List<byte[]> portfolio(int id){
if (perfisRepository.existsById(id)) {
return fotosRepository.findAllByAutonomoId(id).stream().map(f-> f.getFotoByte()).collect(Collectors.toList());
}
else {
return null;
}
} //Getters and Setters
Controller
#GetMapping("/portfolio/fotos/{id}")
public ResponseEntity<List<byte[]>> getPortfolioAutonomo(#PathVariable int id) throws IOException {
List<byte[]> result = autonomoService.portfolio(id);
return ResponseEntity.status(200).body(result);
}
And this is the way I can get 1 photo by its id
#GetMapping("/portfolio/{id}")
public ResponseEntity getPortfolio(#PathVariable int id){
Optional<Foto> anexoOptional = fotosRepository.findById(id);
if (anexoOptional.isPresent()) {
Foto anexo = anexoOptional.get();
return ResponseEntity.status(200)
.header("content-type", anexo.getTipoArquivo())
.header("content-disposition", "filename=" + anexo.getNomeArquivo())
.body(anexo.getFotoByte());
} else {
return ResponseEntity.status(404).build();
}
}
Instead of
return fotosRepository.findAllByAutonomoId(id).stream().map(f-> f.getFotoByte()).collect(Collectors.toList());
Can you try with
return fotosRepository.findAllById(id).stream().map(f-> f.getFotoByte()).collect(Collectors.toList());
If still this is not working, better to go with #Query implemention.
Have the same DTO object for POST and PUT methods:
class AcmeRequest {
private String id;
#NotEmpty
private String productCode;
private String description;
}
For POST request I always expect to see productCode field, that's why I specified #NotEmpty annotation but when PUT request received productCode should be optional.
Is it possible some how just to skip #NotEmpty when request is PUT?
Every Hibernate Validator annotation has a groups parameter. Through interfaces, you can control which validations are activated. See more at docs.
In controller level, specify which groups must be activated with the #Validated annotation.
Below, there is a small example from one of my demo projects. I once had the same question as you.
Entity:
#Entity
#Table(name = "tasks")
#Getter #Setter
public class Task
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(message = "You can't provide a task ID manually. ID's are automatically assigned by our internal systems.", groups = {TaskInsertValidatorGroup.class})
#NotNull(message = "You must provide an id" , groups = TaskUpdateValidatorGroup.class)
private Integer id;
#NotBlank(message = "Task description cannot be empty")
#Length(max = 255 , message = "Task description length must not exceed 255 characters")
private String description;
#JsonProperty("is_completed")
#Column(name = "is_completed")
private Boolean isCompleted = false;
#CreationTimestamp
#JsonProperty("created_on")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "created_on", updatable = false)
private Timestamp creationDate;
#UpdateTimestamp
#JsonProperty("last_modified")
#JsonFormat(pattern="dd-MM-yyyy HH:mm:ss")
#Column(name = "last_modidied")
private Timestamp lastModificationDate;
#Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Task task = (Task) o;
return id.equals(task.id);
}
#Override
public int hashCode()
{
return Objects.hash(id);
}
}
Interfaces:
public interface TaskInsertValidatorGroup {}
public interface TaskUpdateValidatorGroup{}
Controller:
RestController
#RequestMapping("/api")
public class TaskRestController
{
#Autowired
private TaskService taskService;
#GetMapping("/tasks/{id}")
public ResponseEntity<?> getTask(#PathVariable Integer id)
{
return new ResponseEntity<>(taskService.findTask(id),HttpStatus.OK);
}
#GetMapping("/tasks")
public ResponseEntity<?> getTasks()
{
return new ResponseEntity<>(taskService.findAllTasks(),HttpStatus.OK);
}
#PostMapping("/tasks")
public ResponseEntity<?> addTask(#Validated(TaskInsertValidatorGroup.class) #RequestBody Task task)
{
taskService.saveTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task added")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks" , method = RequestMethod.PATCH)
public ResponseEntity<?> updateTask(#Validated(TaskUpdateValidatorGroup.class) #RequestBody Task task)
{
taskService.updateTask(task);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Updated")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
#RequestMapping(value = "/tasks/{id}", method = RequestMethod.DELETE)
public ResponseEntity<?> removeTask(#PathVariable Integer id)
{
taskService.removeTask(id);
APISuccessResponse response = APISuccessResponse.builder()
.info("Task Deleted")
.build();
return new ResponseEntity<>(response,HttpStatus.OK);
}
}
I have the following POST request:
{
"name": "Peter",
"lastName": "Smith",
"contact": {
"phone":"12345679",
"email": "peter#smith.com"
}
}
And I would like to store that in a SQL DB as follow:
| id (int) | name (varchar) | lastName (varchar) | contact (JSON) |
I'm using spring-boot-starter-data-rest so I only have the UserRepository and User Entity, which has an Embedded property contact
User.java
#Entity
#Table(name="user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String title;
#Column(name = "lastName")
private String lastName;
#Embedded
#Column(name = "contact")
private Contact contact;
}
Contact.java
#Embeddable
public class Contact {
private String phone;
private String email;
}
UserRepository.java
public interface UserRepository extends JpaRepository<User, Integer> {
//
}
If I make a POST request I get an error, because (guess) I'm not converting Contact to JSON.
I've already tried adding a #Convert(converter = HashMapConverter.class) but I get an error.
HashMapConverter
public class HashMapConverter implements AttributeConverter<Object, String> {
private static final ObjectMapper om = new ObjectMapper();
#Override
public String convertToDatabaseColumn(Object attribute) {
try {
return om.writeValueAsString(attribute);
} catch (JsonProcessingException ex) {
//log.error("Error while transforming Object to a text datatable column as json string", ex);
return null;
}
}
#Override
public Object convertToEntityAttribute(String dbData) {
try {
return om.readValue(dbData, Object.class);
} catch (IOException ex) {
//log.error("IO exception while transforming json text column in Object property", ex);
return null;
}
}
}
i've got the same case for storing json field which is working perfectly. Please try :
Add dependency to pom.xml :
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.9.7</version>
</dependency>
Edit User class within :
#Entity
#TypeDef(name = "json", typeClass = JsonStringType.class)
public class User {
// other field here
#Type(type = "json")
#Column(columnDefinition = "json")
private Contact contact;
// getters, setters
}
Of course your database should support json type. For MariaDB for example you can refer to https://mariadb.com/kb/en/json-data-type/
You need to create an entity for Contact and then create a one to one relationship between the two. Check out this example.
In my database I have two tables airport and calendar connected by foreight key airport_id. I want to get json response with data from two tables for the determined airport_id=273
For example I want to get data for Airport with airport_id and Calendar with foreight key airport_id equels 273. Actually, I've got empty response from localhost:8080. I didn't get any error, just a blank page, like on the picture below. What I do wrong? Thank you in advance!
Airport.java
#Entity
#Table(name = "airport")
public class Airport {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer airport_id;
#Column(name = "departureAirport")
private String departureAirport;
#Column(name = "destinationAirport")
private String destinationAirport;
#OneToMany(mappedBy = "airport")
#JsonManagedReference("airport")
private List<Calendar> calendars; ....
Calendar.java
#Entity
#Table(name = "calendar")
public class Calendar {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer calendar_id;
#Column(name = "departureTime")
private Time departureTime;
#Column(name = "destinationTime")
private Time destinationTime;
#ManyToOne(targetEntity = Airport.class)
#JoinColumn(name = "airport_id")
#JsonBackReference("airport")
private Airport airport; ....
CalendarController.java
public class CalendarController {
#Autowired
CalendarService calendarService;
#Autowired
AirportService airportService;
#GetMapping(value = "/search/{airport_id}")
public List<Calendar> getCalendars(#PathVariable("airport_id") Integer airport_id) {
Airport airport = airportService.findOne(airport_id);
return calendarService.findOne(airport);
}}
CalendarRepository.java
public interface CalendarRepository extends CrudRepository<Calendar, Integer> {
Calendar getOne(int calendar_id);
List<Calendar> findByAirport(Airport airport_id);
}
CalendarService.java
public interface CalendarService {
List<Calendar> findOne(Airport airport_id);
}
CalendarServiceImpl.java
#Service
public class CalendarServiceImpl implements CalendarService {
#Autowired
CalendarRepository repository;
#Autowired
AirportRepository airportRepository;
#Override
public List<Calendar> getCalendars(Integer airport_id) {
Airport airport = airportRepository.getOne(airport_id);
return repository.findByAirport(airport);
}}
Update
AirpostService.java
public interface AirportService {
Airport findOne(int airport_id);
}
AirportRepository.java
public interface AirportRepository extends CrudRepository<Airport, Integer> {
Airport getOne(Integer airport_id);
}
The problem is not your Hibernate, but your Controller. You just need to add the #Responsebody annotation to your method. The #ResponseBody annotation tells a controller that the object returned is automatically serialized into JSON.
#GetMapping(value = "/search/{airport_id}")
#ResponseBody
public List<Calendar> getCalendars(#PathVariable("airport_id") Integer airport_id) {
Airport airport = airportService.findOne(airport_id);
return calendarService.findOne(airport);
}}