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);
Related
I have this JobPosting class and a repository interface for this entity. I wanna be able to search for all combinations of company, skills, title and city fields of JobPosting class. The only way I know is creating a different method in repository interface . For instance for searching by city and title i need to create a method named findByCityIdAndTitleContaining(Long id,String title). For searching by skills and title i need to create a method named LfindBySkillsIdAndTitleContaining(Long id,String title). The problem is if i create different method for each possibility there will be too many methods. Is there any better way ?
Entity (I didn't include get set methods here):
#Entity
public class JobPosting
{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Company company;
private Date publishedAt;
private String title,description;
private boolean isActive;
#ManyToOne
private City city;
#ManyToMany
#JoinTable(name="job_posting_skill",joinColumns=#JoinColumn(name="job_posting_id"),inverseJoinColumns=#JoinColumn(name="skill_id"))
private Set<Skill> skills;
}
Repository:
package app.repository;
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import app.entity.JobPosting;
public interface JobPostingRepository extends JpaRepository<JobPosting,Long>
{
Page<JobPosting> findAll(Pageable pageable);
List<JobPosting> findByCompanyId(long companyId);
List<JobPosting> findByTitleContaining(String title);
List<JobPosting> findBySkillsId(Long id);
List<JobPosting> findByCityId(Long id);
}
Spring Data provides JpaSpecificationExecutor which can fit your requirements.
https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/JpaSpecificationExecutor.html
List<T> findAll(Specification<T> spec)
Returns all entities matching the given Specification.
A Specification has a predicate method:
interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}
It has a CriteriaBuilder, so in theory you will still need to define what exactly you need to match for, however you wont have to create multiple findByXYZ.
To be able to use findAll(Specification), your repository needs to extend org.springframework.data.jpa.repository.JpaSpecificationExecutor<T>:
Example of usage:
https://www.baeldung.com/spring-data-criteria-queries
I have a list of persons in DB everyone having a CV field which is a MultiPart File in Spring. I'm trying to get all persons from db, but to ignore the CV field because I don't want to be downloaded when I use getAllPersons function. I saw a possibility with JsonIgnore but I want to ignore that field just in getAllPersons, not in other functions like getPersonById etc. How can I do it?
For that purpose you can use HQL.
i.e
interface Repo extends C... {
#Query(select h.name, h.phone, ... from Person h)
List<Person> getAllPerson();
}
Suppose that you use Sping and Spring-data you can use a projection in order to avoid maintaining custom queries. Consider the following example:
Entity class Book
#Data
#Entity
#Table(name = "book")
public class Book {
#Id
#GeneratedValue(generator = "book_sequence", strategy = GenerationType.SEQUENCE)
#SequenceGenerator(name = "book_sequence", sequenceName = "book_sequence", allocationSize = 1)
private Long id;
#NaturalId
private String name;
private String author;
private String publisher;
private String plot;
#ManyToMany(mappedBy = "books")
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<BookFilter> filters = new HashSet<>();
}
Projection interface:
public interface BookNameAuthorOnly {
String getName();
String getAuthor();
}
Repository method:
#Repository
public interface BookRepository extends JpaRepository<Book, Long> {
List<BookNameAuthorOnly> findBy();
}
When the latter is invoked, the dynamic query generated by Spring, will select only the fields that you have specified in the related interface object. For more on this you can check the following documentation:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Don't know if this fits your use case scenario but this is also another way of achieving what you need to achieve.
You can also use DTO as shown in below example:
Person entity:
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Lob;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter
#Setter
#NoArgsConstructor
#Entity
public class Person {
#Id
private long id;
private String name;
private String address;
#Lob
private Object cvFields;
}
PersonDTO:
package com.example.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
#Getter
#Setter
#NoArgsConstructor
public class PersonDTO {
private long id;
private String name;
private String address;
public PersonDTO(Person person) {
this.id = person.getId();
this.name = person.getName();
this.address = person.getAddress();
}
}
PersonRepository:
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
public interface PersonRepository extends JpaRepository<Person, Long> {
#Query("SELECT new com.example.dto.PersonDTO(p) FROM Person p")
List<PersonDTO> getAll();
}
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 am creating a spring boot application and use it's build in JpaRepository interface to store my entities. I have the following two entities (removed getters and setters for readability):
Profile entity
#Entity
public class Profile {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy = "profileOne", orphanRemoval = true)
private List<Match> matchOnes;
#OneToMany(mappedBy = "profileTwo", orphanRemoval = true)
private List<Match> matchTwos;
}
Match entity
#Entity
#Table(uniqueConstraints={
#UniqueConstraint(columnNames = { "profileOne_id", "profileTwo_id" })
})
public class Match {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne
#JoinColumn(name = "profileOne_id")
private Profile profileOne;
#ManyToOne
#JoinColumn(name = "profileTwo_id")
private Profile profileTwo;
}
To understand the JpaRepository behavior I wrote the following unit test.
#RunWith(SpringRunner.class)
#DataJpaTest
public class IProfileDaoTest {
#Autowired
private IProfileDao profileDao; //This class extends JpaRepository<Profile, long>
#Autowired
private IMatchDao matchDao; //This class extends JpaRepository<Match, long>
#Test
public void saveProfileTest() throws Exception {
#Test
public void profileMatchRelationTest() throws Exception {
//Test if matches stored by the IMatchDao are retrievable from the IProfileDao
Profile profileOne = new Profile("Bob"),
profileTwo = new Profile("Alex");
profileDao.saveAndFlush(profileOne);
profileDao.saveAndFlush(profileTwo);
matchDao.saveAndFlush(new Match(profileOne, profileTwo));
profileOne = profileDao.getOne(profileOne.getId());
Assert.assertEquals("Match not retrievable by profile.", 1, profileOne.getMatchOnes().size());
}
}
Now I expected the matches to have appeared in the profile entity but they do not. I also tried adding CascadeType.ALL to the #ManyToOne annotation in the match entity and adding FetchType.EAGER to the #OneToMany annotation in the profile entity.
Is it possible to get the matches saved with the matchDao by requesting a profile in the profileDao? Or should I find the matches with a profile with a separate function?
Spring Data repositories don't write to the database immediately for performance (and probably other) reasons. In tests, if you need to test query methods you need to use the TestEntityManager provided by #DataJpaTest (it's just the entity manager that the repositories use anyway in the background, but with a few convenience methods for testing).
Update 1:
The matches aren't added to the profile. To make sure the relationship is bidirectional the matches should have the profiles but the profiles should also have the matches.
Try this:
#RunWith(SpringRunner.class)
#DataJpaTest
public class IProfileDaoTest {
#Autowired
private IProfileDao profileDao; //This class extends JpaRepository<Profile, long>
#Autowired
private IMatchDao matchDao; //This class extends JpaRepository<Match, long>
#Autowired
private TestEntityManager testEntityManager;
#Test
public void saveProfileTest() throws Exception {
#Test
public void profileMatchRelationTest() throws Exception {
//Test if matches stored by the IMatchDao are retrievable from the IProfileDao
Profile profileOne = new Profile("Bob"),
profileTwo = new Profile("Alex");
//Persist the profiles so they exist when they are added to the match
entityManager.persistAndFlush(profileOne);
entityManager.persistAndFlush(profileTwo);
//Create and persist the match with two profiles
Match yourMatch = entityManager.persistFlushFind(new Match(profileOne, profileTwo));
//Add the match to both profiles and persist them again.
profileOne.matchOnes.add(yourMatch);
entityManager.persistAndFlush(profileOne);
profileTwo.matchTwos.add(yourMatch);
entityManager.persistAndFlush(profileTwo);
profileOne = profileDao.getOne(profileOne.getId());
Assert.assertEquals("Match not retrievable by profile.", 1, profileOne.getMatchOnes().size());
}
}
Everything in your test happens in the same JPA session. Such a session guarantees that every entity is included only once. So when you execute
profileOne = profileDao.getOne(profileOne.getId());
you are getting the exact instance back you created 5 lines above.
Hibernate nor any other JPA implementation will change anything in the entity for loading.
If you want to actually reload an entity you'll have to either evict it first from the entity manager or use a fresh Session/EntityManager.
For more details see chapter 3 of the JPA specification.
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.