I have two models:
Class One:
import javax.persistence.*;
import java.util.Set;
#Entity
public class One {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#OneToMany(mappedBy = "one")
private Set<Many> manySet;
//Constructor, Getter and Setter
}
Class Many:
import javax.persistence.*;
#Entity
public class Many {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "one_id")
private One one;
//Constructor, Getter and Setter
}
Repository:
import com.hotel.model.Many;
import org.springframework.data.jpa.repository.JpaRepository;
public interface ManyRepository extends JpaRepository<Many, Long> {
}
Controller Class:
#RestController
#RequestMapping(value = "many")
public class ManyController {
#Autowired
private ManyRepository manyRepository;
#GetMapping
#ResponseBody
public List<Many> getAllMany() {
return manyRepository.findAll();
}
#PostMapping
#ResponseBody
public ResponseEntity createMany(#RequestBody Many many) {
return new ResponseEntity(manyRepository.save(many), HttpStatus.CREATED);
}
}
I created One record with id=1.
But when I create a Many record with JSON data:
{
"name": "Foo",
"one_id": 1
}
I received Many record with one_id is null
Can I using only one request to create new Many record and assign to One record has id = 1?
Do I have to use 2 request: create Many and assign to One?
You have to update your method like so
#PostMapping
#ResponseBody
public ResponseEntity createMany(#RequestBody ManyDTO many) {
One one = oneRepository(many.getOne_id()); //Get the parent Object
Many newMany = new Many(); //Create a new Many object
newMany.setName(many.getName());
newMany.setOne(one); // Set the parent relationship
...
}
Note: The above answer only explains the way to set the relationships of the entity. Proper service layer should be invoked actually.
You can persist 1 record in One and multiple record in Many tables only using a single request while your are using the below repository
import com.hotel.model.One;
import org.springframework.data.jpa.repository.JpaRepository;
public interface OneRepository extends JpaRepository<One, Long> {
}
On the other hand you can persist 1 record in One and 1 record in Many tables using the JpaRepository given in your example.
Your question was not clear, whether you want to persist One/Many or retrieve One/Many.
Related
I have two tables created in a H2 in-memory DB inside a spring boot app as shown below:
Restaurant Table
CREATE TABLE RESTAURANT (
ID BIGINT AUTO_INCREMENT PRIMARY KEY,
NAME VARCHAR(50) NOT NULL,
BOOKING_DATE DATE NOT NULL,
TABLES JSON
);
BookTable Table
CREATE TABLE BOOKTABLE (
TABLE_ID BIGINT AUTO_INCREMENT PRIMARY KEY,
SEATING_CAPACITY INT NOT NULL,
IS_AVAILABLE BOOL
);
My immediate first question here is, how do I insert data in the Restaurant Table using SQL Commands (specially the JSON field "TABLES" which actually stores the data from the other BOOKTABLE table in a JSON format?
Is this query correct?
INSERT INTO RESTAURANT VALUES (2, 'Saraya', '2021-05-25', '[{"tableId": 1, "seatingCapacity": 12, "isAvailable": true}]');
NOTE: Im expecting Restaurant table to store multiple tables, hence the array in the JSON string.
Next, I have these two #Entity classes defined in my Spring Boot project:
Restaurant.java
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "restaurant")
public class Restaurant {
#Id
#Column(name="ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name="NAME")
private String name;
#Column(name="BOOKING_DATE")
private Date bookingDate;
#Column(name = "TABLES", columnDefinition = "json")
#JsonRawValue
private String tables;
}
BookTable.java
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "booktable")
public class BookTable {
#Id
#Column(name="TABLE_ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long tableId;
#Column(name="SEATING_CAPACITY")
private int seatingCapacity;
#Column(name="IS_AVAILABLE")
private boolean isAvailable;
}
I am trying to do basic CRUD Operations on these entities. I have below controller created for my Restaurant Entity:
RestaurantController.java
#RestController
#RequestMapping("/v1/restaurants")
public class RestaurantController {
private final RestaurantService restaurantService;
public RestaurantController(RestaurantService restaurantService) {
this.restaurantService = restaurantService;
}
#GetMapping(produces = "application/json")
public List<Restaurant> getAllExistingRestaurants() {
return restaurantService.getAllRestaurants();
}
#GetMapping(value = "/{id}", produces = "application/json")
public Restaurant getRestaurantById(#PathVariable("id") Long reservationId) {
return restaurantService.getRestaurant(reservationId);
}
#PostMapping(consumes = "application/json")
private long addRestaurant(#RequestBody Restaurant restaurant) throws IOException, ClassNotFoundException {
restaurantService.AddNewRestaurant(restaurant);
return restaurant.getId();
}
}
As you can see above, for now i am just trying 3 REST APIs: GetAll(), GetByID() and Post(). Below is the service class code:
RestaurantService.java
package com.assignment.restaurantmanagement.service;
import com.assignment.restaurantmanagement.model.BookTable;
import com.assignment.restaurantmanagement.model.Restaurant;
import com.assignment.restaurantmanagement.repository.RestaurantRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
#Service
public class RestaurantService {
private final RestaurantRepository restaurantRepository;
private final TableService tableService;
public RestaurantService(RestaurantRepository restaurantRepository, TableService tableService) {
this.restaurantRepository = restaurantRepository;
this.tableService = tableService;
}
// Retrieve all restaurants
public List<Restaurant> getAllRestaurants() {
List<Restaurant> restaurants = new ArrayList<>();
restaurantRepository.findAll().forEach(restaurants::add);
return restaurants;
}
// Retrieves a restaurant based on restaurant id
public Restaurant getRestaurant(Long id) {
Optional<Restaurant> restaurant = restaurantRepository.findById(id);
return restaurant.get();
}
// Adds a new restaurant
public void AddNewRestaurant(Restaurant restaurant) throws IOException, ClassNotFoundException
{
//*Some code here*
}
}
And below is the Repository classes defined for each of the Entities:
RestaurantRepository.java
public interface RestaurantRepository extends CrudRepository<Restaurant, Long> {
}
TableRepository.java
public interface TableRepository extends CrudRepository<BookTable, Long> {
}
I have below further questions:
How do I add/store multiple BookTable objects (List) in the column TABLES which has data type JSON in the RESTAURANT table, through the POST API call?
How do i read the same JSON column TABLES from the RESTAURANT table and convert it into BookTable object or List<BookTable object for the GetByID() and GetAll() APIs?
Basically, i want to store and retrieve the entire Restaurant JAVA object which internally has BookTable object as part of its data members through REST APIs with H2 in-memory database. I am open to any new changes in this existing code and table structures.
Thanks!!
Good Day developers , i'm hardly striving with this problem on my App which use SpringBoot framework.Basically can't put two and two together about how deleting one of the items in the relation ship once its parent is delete. Here my explanation:
First both entities with its respective relation to each other:
Product(Children)
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "native")
#GenericGenerator(name="native",strategy="native")
private Long id;
#OneToMany(mappedBy = "products",fetch= FetchType.EAGER,cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Category> categorySet= new HashSet<>();
CONSTRUCTOR FOR PRODUCTS ENTITY
-------------------------------------GETTERS AND SETTERS---------------------------------
Being this the Product entity under the premise of one product being able to clasify to several categories hence its relation OnetoMany.Then:
Categories(Parent)
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO,generator = "native")
#GenericGenerator(name="native",strategy="native")
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="product_id")
private Product products;
CONSTRUCTOR FOR CATEGORY ENTITY
---------------------------GETTERS AND SETTERS-----------------------------
Following the former concept but withan inverse logic applied Category reltion toward products, and works perect on my database.
on repositories lets say i set this
Category Repository
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import java.util.Collection;
#RepositoryRestResource
public interface CategoryRepository extends JpaRepository <Category,Long> {
}
Product Repository
package com.miniAmazon;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.*;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource
public interface ProductRepository extends CrudRepository<Product,Long> {
Product findByProductName (String productName);
}
Then trying to set the command to delete products or categories from my Jpa and Crud Reps, using Junit Test on the Category Entity, like this:
Category Entity
#Test
public static void whenDeletingCategories_thenProductsShouldAlsoBeDeleted() {
ProductRepository.deleteAll();
assert(CategoryRepository.count()).isEqualTo(0);
assert(ProductRepository.count()).isEqualTo(0);
}
#Test
public static void whenDeletingProducts_thenCategoriesShouldAlsoBeDeleted() {
CategoryRepository.deleteAll();
assert(CategoryRepository.count()).isEqualTo(0);
assert(ProductRepository.count()).isEqualTo(2);
}
Throws me an error saying that "Non-static method 'deleteAll()/count()' cannot be referenced from a static context".
Any idea about why this is happening .Any advice ?.Thanks in advance!!!!.Have a good day!!!
Try using instantiated beans CategoryRepository and ProductRepository instead of the interfaces.
You are try to use Non-static method of interface but deleteAll() or count() are not static method. Try to create a repository object then autowired it to call deleteAll() / count() method.
#Autowired
private CategoryRepository categoryRepository;
And use categoryRepository to call call deleteAll() / count() method
categoryRepository.deleteAll();
assert(categoryRepository.count()).isEqualTo(0);
I'm writing a sample app using the HR schema on an Oracle db 18c.
I'm using Spring boot 2, Spring Data Jpa and Spring Rest.
I'm working on Regions' table (that contains two fields: region_id and region_name) and countries table (that contains three fields: country_id, country_name and region_id).
I can manage all CRUD operation on Regions' table if its entity doesn't contain the relationship #OneToMany with Country's entity when I add it the application return me a 415 error (non supported method) that have no sense!
Here it is my code:
Region Entity:
package it.aesys.springhr.entities;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
import java.util.List;
/**
* The persistent class for the REGIONS database table.
*
*/
#Entity
#Table(name="REGIONS")
#NamedQuery(name="Region.findAll", query="SELECT r FROM Region r")
public class Region {
#Id
#GeneratedValue(generator="REGIONS_SEQ", strategy=GenerationType.SEQUENCE)
#SequenceGenerator(name="REGIONS_SEQ", sequenceName="REGIONS_SEQ", allocationSize=0)
#Column(name="REGION_ID", unique=true, nullable=false)
private int regionId;
#Column(name="REGION_NAME", nullable=false, length=50)
private String regionName;
//bi-directional many-to-one association to Country
#OneToMany(mappedBy="region", cascade = CascadeType.ALL)
// this annotation help me to not retrieve all countries when I find all Regions but I tried also without it and anything change
#JsonBackReference
private List<Country> countries;
// constructor, getters and setters as usual
}
Country Entity:
package it.aesys.springhr.entities;
import javax.persistence.*;
import com.fasterxml.jackson.annotation.JsonBackReference;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import java.util.List;
/**
* The persistent class for the COUNTRIES database table.
*
*/
#Entity
#Table(name="COUNTRIES")
#NamedQuery(name="Country.findAll", query="SELECT c FROM Country c")
public class Country {
#Id
#Column(name="COUNTRY_ID", unique=true, nullable=false, length=2)
private String countryId;
#Column(name="COUNTRY_NAME", nullable=false, length=40)
private String countryName;
//bi-directional many-to-one association to Region
#ManyToOne
#JoinColumn(name="REGION_ID")
#JsonManagedReference
private Region region;
// constructor, getters and setters as usual
}
The RegionRepository Interface is simply:
package it.aesys.springhr.dao;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import it.aesys.springhr.entities.Region;
public interface RegionRepository extends JpaRepository<Region, Integer> {
}
and the RegionService contains this method:
public void save(Region theRegion) {
regionRepository.save(theRegion);
}
and finally the RegionRestController contains this method:
#PostMapping( value= "/regions", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public Region addRegion(#RequestBody Region theRegion) {
// also just in case they pass an id in JSON ... set id to 0
// this is to force a save of new item ... instead of update
theRegion.setRegionId(0);
regionService.save(theRegion);
return theRegion;
}
I'm using CURL to test this app and I tried to pass countries in different ways but no one works!
Can I resolve without using some external framework like Map Struct? Or, in this case I MUST create a new object that mapping what I receive with what I must persist?
[Edit]: I modify the last method with this code:
#PostMapping( value= "/regions", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public Region addRegion(#RequestBody HashMap) {
Region theRegion= new Region();
theRegion.setRegionId(0);
theRegion.setRegionName(map.get("regionName"));
regionService.save(theRegion);
return theRegion;
}
and now it works but I'm not sure that this solution is secure because it seems so simply and so generic ...
As I edited, I modify the last method with this code:
#PostMapping( value= "/regions", consumes = "application/json;charset=UTF-8", produces = "application/json;charset=UTF-8")
public Region addRegion(#RequestBody HashMap<String, String>) {
Region theRegion= new Region();
theRegion.setRegionId(0);
theRegion.setRegionName(map.get("regionName"));
regionService.save(theRegion);
return theRegion;
}
and now it works but I'm not sure that this solution is secure because it seems so simply and so generic so if you think that it is not sure please answer and tell me another better solution!
You can use #Component in service class and controller class and #ComponentScan(basePackages = "<full package name>" in the main class. Problem may be solved.
I'm seeing some videos about API Rest with Spring Boot and so far I've done some basics and when I tried to increase the complexity I'm getting caught.
My idea is in the Post / class, create a new class with students getting the following json:
{
"nome": "Primeira Serie - A".
"alunos": [
"João",
"José",
"Maria"
]
}
And return:
{
"id_classe": 101
}
It happens that it saves the class, but it does not save the students and I have no idea how to show only the id of the class.
I have created the following classes in Java:
Model
Classe.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "classe")
public class Classe {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#OneToMany(mappedBy = "classe")
private Set<Aluno> alunos = new HashSet<Aluno>();
//Get's e Set's suppressed
}
Aluno.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "aluno")
public class Aluno {
private static int tempID = 0;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#ManyToOne
#JoinColumn(name = "id_classe")
#JsonBackReference
private Classe classe;
public Aluno(String nome) {
tempID++;
this.id = tempID;
this.nome = nome;
}
public Aluno() {
}
//Get's e Set's suppressed
}
Repository
ClasseRepository.java
package com.example.classe.repository;
//Import's suppressed
#Repository
public interface ClasseRepository extends JpaRepository<Classe, Integer> {
public List<Classe> findAll();
}
Controller
ClasseController.java
package com.example.classe.controller;
//Import's suppressed
#RestController
#RequestMapping("/classe")
public class ClasseController {
#Autowired
private ClasseRepository classeRepo;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Classe>> getClasse() {
return new ResponseEntity<>(classeRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> salvarClasse(#RequestBody Classe classe) {
return new ResponseEntity<>(classeRepo.saveAndFlush(classe), HttpStatus.CREATED);
}
}
Am I doing everything wrong or did I not understand the concept? But I wanted to understand how to do it that way.
Thanks in advance.
Cesar Sturion
What you want to achieve is totally doable, but requires several changes.
I split my answer into 2 parts:
Save the students
There are several problems with saving:
On POST your incoming json deserialized into objects in which Classe has a reference to Anuli, but Anuli doesn't have a reference toClasse. To check it you can add a break point at the line: return new ResponseEntity<>(... , run in debug mode and check fields of Anuli in Classe. To fix it you can add #JsonManagedReference on aluni field in Classe. Related question
Hibernate can't save referenced objects by default. You have to save them one by one after saving your Classe object or just turn on Cascade persisting. Related question
So, to fix 1 and 2 Classe should have:
#OneToMany(mappedBy = "classe", cascade = CascadeType.PERSIST)
#JsonManagedReference
private Set<Aluno> alunos = new HashSet<Aluno>();
You have to remove custom id generation in Alumi (I am talking about static int tempID). Annotation #GeneratedValue will perfectly generate id for you as soon as you persist an object. This custom generation breaks Hibernate support. I even not talking about that it also breaks the app after restart, not threadsafe etc.
Return id only
On POST returned json represent what was returned in classeRepo.saveAndFlush(classe) so it's an object of Classe.
If you want to return exactly this:
{
"id_classe": 101
}
Then create new class like this:
public class ClasseIdVO {
#JsonProperty("id_casse")
private Integer id;
// Constructors, getter, setter
VO - means View Object, so this object only for representation, not for persisting, etc.
You can use field name id_casse, but it's against Java code convention, so better add #JsonProperty.
Also change your saving code to new ClasseIdVO(classeRepo.saveAndFlush(classe).getId())
Or you can just return id as a number: classeRepo.saveAndFlush(classe).getId()
I'm stuck with trying to display data for a One-to-One relationship in Twirl templates (using Play Framework Java - 2.5.10). Basically I have a User model:
package models;
import java.sql.Date;
import javax.persistence.*;
import com.avaje.ebean.Model;
#Entity
#Table(name = "users")
public class User extends Model {
#Id
#Column(name = "id")
public Long id;
#Column(name = "first_name")
public String firstName;
#Column(name = "middle_name")
public String middleName;
#Column(name = "last_name")
public String lastName;
#Column(name = "date_of_birth")
public Date dateOfBirth;
#Column(name = "sex")
public String sex;
#OneToOne
#JoinColumn(name = "time_zone_id")
public TimeZone timeZone;
public static Finder<Long, User> find = new Finder<>(User.class);
}
and the Farmer model:
package models;
import com.avaje.ebean.Model;
import javax.persistence.*;
import java.util.List;
#Entity
#Table(name="farmers")
public class Farmer extends Model {
public enum Status {INACTIVE, ACTIVE}
#Id
#Column(name="id")
public Long id;
#OneToOne(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinColumn(name="user_id")
public User user;
#Column(name="profile_pic_url")
public String profilePicUrl;
#Column(name="access_url")
public String accessUrl;
#Column(name="status")
public String status = Status.INACTIVE.name();
#OneToMany(mappedBy = "farmer", targetEntity = Farm.class, fetch = FetchType.LAZY)
public List<Farm> farms;
public static Finder<Long, Farmer> find = new Finder<>(Farmer.class);
public static List<Farmer> getAllActive() {
return Farmer.find.where().eq("status", Status.ACTIVE.name()).findList();
}
}
Notice there's a one-to-one with User model with fetch type set to eager. Now, I want to display data of farmers in my template, where a farmer's name is actually the name in the associated User model.
So I did this in my controller:
public class FarmerController extends Controller {
public Result all() {
return ok(farmers.render(Farmer.getAllActive()));
}
public Result farmer(Long id, String url) {
return ok(farmer.render());
}
}
Now this gets me the right farmer data, but when I try to display the name via the User model, I get null. More specifically, writing this results in nulls (I get nullnull, actually):
<div><h4>#(farmer.user.firstName + farmer.user.lastName)</h4></div>
What am I missing?
As discussed at the comments, this is because play-enhancer does not works for views or any Scala code at all. Since Twirl compiles scala.html code to scala code, this compiled code is not touched by the enhancer.
The solution is then to manually create the get for the relationship:
public class Farmer extends Model {
public User getUser() {
return this.user;
}
}
This is Java code and then will be handled as expected. Of course, you have to change your views to use farmer.getUser instead of farm.user.
Also, as stated at the docs, byte code enhancement involves some magic. But you can avoid it at all and just use regular POJOs (with explicitly declared gets and sets).