I want to create Organization Structure using JPA. If I insert data using my controller, it will throw a PSQLException because the first node's parent is null. But if I input the first node manually into DB, it works for the next node. I read about this and other postings but no luck for me.
This is my entity:
#Entity
#Table(name = "tree")
public class Node implements Serializable {
#Id
private int id;
private String name;
#OneToMany(
cascade = CascadeType.ALL
)
#JoinColumn(name = "parent_id", nullable = true)
#OrderColumn
#JsonManagedReference
private Set<Node> children = new HashSet<>();
#Basic(optional = true)
#ManyToOne(
fetch = FetchType.LAZY,
optional = true
)
#JoinColumn(name = "parent_id", nullable = true)
#JsonBackReference
private Node parent;
public void addNode(Node node){
this.getParent().getChildren().add(node);
node.setParent(this.getParent());
}
// Getter setter was removed for brevity
}
This is my controller:
#RestController
#RequestMapping("/node")
public class NodeController {
#Autowired
private NodeRepository nodeRepository;
#GetMapping
public #ResponseBody Iterable<Node> index(){
return nodeRepository.findByParentIsNull();
}
#PostMapping
public String add(
#RequestBody List<Node> nodes){
nodes.stream().forEach(
node -> {
node.addNode(node);
}
);
nodeRepository.saveAll(nodes);
return "nice";
}
}
And, this is my data sample:
[
{
"id":8,
"name":"Top Parent",
"parent":{}
},{
"id":9,
"name":"Node 1",
"parent":{
"id":8
}
},{
"id":10,
"name":"Node 2",
"parent":{
"id":8
}
},{
"id":11,
"name":"Node 3",
"parent":{
"id":9
}
}
]
The Expected Result after inserting via controller is:
id |name |parent_id
===========================
8 |Top Parent |null
9 |Node 1 |8
10 |Node 2 |8
11 |Node 3 |9
Let me know if something unclear. Thank in advance!
I find a workaround for my problem. So, I put here if someone faces a similar problem with me.
I added a NativeQuery in my repository like this:
#Query(value = "insert into tree values(:id,:name)", nativeQuery = true)
void saveTop(#Param("id")int id,#Param("name")String name);
And then I added some lines in my controller like this:
#PostMapping("/top")
public void addTop(#RequestBody Node node){
nodeRepository.saveTop(node.getId(),node.getName());
}
Basically, I create an endpoint("/node/top") to insert top node for my structure organization tree manually.
Related
Here below is a simple model for a pet shop...
Pet Class
#Entity
#Table(name = "pet")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public abstract class Pet {
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "name", nullable = false)
private String name;
#Column(name = "birth_date", nullable = false)
private LocalDate birthDate;
#Column(name = "death_date")
private LocalDate deathDate;
#ManyToOne
#JoinColumn(name = "pet_shop_id", nullable = false, referencedColumnName = "id")
#Setter(AccessLevel.NONE)
private PetShop petShop;
public void setPetShop(PetShop petShop) {
setPetShop(petShop, true);
}
public void setPetShop(PetShop petShop, boolean add) {
this.petShop= petShop;
if (petShop!= null && add) {
petShop.addPet(this, false);
}
}
PetShop Class
#Entity
#Table(name = "pet_shop")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#EqualsAndHashCode
public class PetShop {
#Column(name = "id", nullable = false)
private Long id;
...
#OneToMany(
mappedBy = "petShop",
fetch = FetchType.LAZY,
cascade = {CascadeType.ALL})
private List<Pet> pets= new ArrayList<>();
public void addPet(final Pet pet) {
addPet(pet, true);
}
public void addPet(final Pet pet, boolean set) {
if (pet!= null) {
if (pets.contains(pet)) {
pets.set(pets.indexOf(pet), pet);
} else {
pets.add(pet);
}
if (set) {
pet.setPetShop(this, false);
}
}
}
}
PetShopRepository Interface
public interface PetShopRepository
extends JpaRepository<PetShop, Long> {
#Query(
"SELECT DISTINCT ps FROM PetShop ps"
+ " JOIN ps.pets p"
+ " WHERE ps.id = :id AND p.deathDate IS NULL")
#Override
Optional<PetShop> findById(#NonNull Long id);
}
... and here is how to create a PetShop with 2 Pet instances (one alive and another one dead):
final Pet alive = new Pet();
alive.setName("cat");
alive.setCall("meow");
alive.setBirthDate(LocalDate.now());
final Pet dead = new Pet();
dead.setName("cat");
dead.setCall("meow");
dead.setBirthDate(LocalDate.now().minusYears(15L));
dead.setDeathDate(LocalDate.now());
final PetShop petShop = new PetShop();
petShop.getPets().add(alive);
petShop.getPets().add(dead);
petShopRepositiry.save(petShop);
Now I want to retrieve the PetShop and I'd assume it contains only pets that are alive:
final PetShop petShop = petShopRepository.findById(shopId)
.orElseThrow(() -> new ShopNotFoundException(shopId));
final int petCount = petShop.getPets().size(); // expected 1, but is 2
According to my custom query in PetShopRepository I'd expect petShop.getPets() returns a list with 1 element, but it actually returns a list with 2 elements (it includes also the dead pet).
Am I missing something? Any hint would be really appreciated :-)
This is because Jpa maintains the coherence of the relations despite your query.
I.e. : your query returns the shops having at least one pet alive. But, Jpa will return the shop with the complete set of pets. And you can probably see extra sql queries sent by Jpa (if you set show_sql=true) to refill pets collection on the returned shop.
Fundamently, it's not because you wanted to get the shops with living pets that these shops loose their dead pets.
To get it right you would have to design the pets collection so that it would filter the dead pets. Hibernate provides such annotations (#Filter and #FilterDef), but apparently JPA does not.
I don't think that filtering at #Postload would be a good idea, because you would have to put back the filtered dead pets in the collection before any flush in the database. That looks risky to me.
On my MySql project I got this particular model with 3 entities: Prodotto with many childs QuotaIngrediente, that in turn is Many-to-One child of Ingrediente too. All my relationships are bi-directional.
All of them got an autogenerated integer Id and other fields removed to focus on the interesting ones.
#Entity
public class Prodotto {
private List<QuotaIngrediente> listaQuoteIng = new ArrayList<QuotaIngrediente>();
#OneToMany(mappedBy = "prodotto", cascade = CascadeType.ALL, orphanRemoval = true)
public List<QuotaIngrediente> getListaQuoteIng() {
return listaQuoteIng;
}
#Entity
public class QuotaIngrediente{
private Prodotto prodotto;
private Ingrediente ing;
private Double perc_ing;
#ManyToOne
#JoinColumn(name = "prodotto")
public Prodotto getProdotto() {
return prodotto;
}
#ManyToOne
#JoinColumn(name = "ing")
public Ingrediente getIng() {
return ing;
}
#Entity
public class Ingrediente {
private Set<QuotaIngrediente> quoteIng = new HashSet<QuotaIngrediente>();
#OneToMany(mappedBy = "ing", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<QuotaIngrediente> getQuoteIng() {
return quoteIng;
}
I'm using SpringData Specification and I can build a query to get Prodotto based on Ingrediente criteria, this way:
public static Specification<Prodotto> getProdottoByIngSpec (String ing) {
if (ing != null) {
return (root, query, criteriaBuilder) -> {
query.groupBy(root.get(Prodotto_.id));
return criteriaBuilder.like(((root.join(Prodotto_.listaQuoteIng))
.join(QuotaIngrediente_.ing))
.get(Ingrediente_.nome), "%"+ing+"%");
};
It works as expected, but now I want to sort it by the QuotaIngrediente perc_ing field OF THAT SPECIFIC INGREDIENTE.
Obviously I'm asking how to do it on DB, not in business logic.
I was struggling with a false problem due to a wrong assumption of mine. Solution was the simplest. Just sort by orderBy CriteriaQuery method. The query I used to search already filtered the QuotaIngrediente returning just the lines that match my search criteria. Then this is the only line I had to add to my Specification:
query.orderBy(builder.desc((root.join(Prodotto_.listaQuoteIng))
.get(QuotaIngrediente_.perc_ing)));
I am tryng to do a workaround for this issue, that I have entered:
https://github.com/jhipster/generator-jhipster/issues/9639
I did some work (I added findAllWithEagerRelationships in the repository), and the GET method works fine: I get all the master and children.
What is not working, and I need your help, is the POST method:
when I post a parent with some children (pets), the children are not posted with the parent, so children are lost.
so summarizing this is the get result, correct:
[
{
"id": 1002,
"name": "Piera",
"pets": [
{
"id": 1051,
"name": "fido",
"species": "barboncino",
"owner": {
"id": 1002,
"name": "Piera"
}
}
]
}
]
but the post does not work correctly:
{
"name": "newName",
"pets": [
{
"id": 1051
}
]
}
newName is created, but pet 1051 is not attached to it
I am working on a app generated with Jhipster:
entity Owner {
name String required
}
entity Pet {
name String required,
species String required
}
relationship OneToMany {
Owner{pet} to Pet{owner}
}
for the get, I added those two methods that I copied form a manytomany relationship, and they worked:
#Query(value = "select distinct owner from Owner owner left join fetch owner.pets")
List<Owner> findAllWithEagerRelationships();
#Query("select owner from Owner owner left join fetch owner.pets where owner.id =:id")
Optional<Owner> findOneWithEagerRelationships(#Param("id") Long id);
Public class Owner implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#OneToMany(mappedBy = "owner")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Pet> pets = new HashSet<>();
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public class Pet implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#NotNull
#Column(name = "name", nullable = false)
private String name;
#NotNull
#Column(name = "species", nullable = false)
private String species;
#ManyToOne
#JsonIgnoreProperties("pets")
private Owner owner;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
I expect that when I do a post on a parent with some children, the children (pets) are also posted.
In your Owner service interface and implementation layer (it should be under com.yourpackage.service and com.yourpackage.service.impl) create a new method like
public Optional<Owner> findOneWithChildren(long id) {
Optional<Owner> owner = ownerRepository.findById(id);
owner.ifPresent(o -> {
o.getPets().addAll(petRepository.getByParentId(id);
}
return owner.map(ownerMapper::toDto);
}
And in your Pet repository, create a new method, like
List<Pet> getByParentId(long id);
in the bug report, I could find a workaround without touching the JPA. I still don't know how we can put a new owner associating to it some pets with JPA as I did receive a resolving answer, anyway my workaround works. thanks :)
I am getting a foreign key violation when I try to delete a record.
I have this record:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long parentId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "childId")
private Child child;
and
#Entity
#Table(name = "Child")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long childId;
public Long getOperatoryId() {
return id;
}
When I try to delete the child, I get a key violation because there are some parent records that point to the children. I thought I could delete the parent first, then go delete the children as:
parentRepository.delete(parent)
but I get an error that the property id doesn't exist on child. Is this because the child id is named childId and not id?
Here I worked on similar example what did you asked for. Customize it based on your need.
SQL Server table create query
CREATE TABLE "DBO"."Parent"(
Parent_Id INT PRIMARY KEY NOT NULL,
Parent_Name VARCHAR(255) NOT NULL)
CREATE TABLE "DBO"."Child"(
Child_Id INT PRIMARY KEY NOT NULL,
Child_Name VARCHAR(255) NOT NULL,
Parent_Ref_Id int not null,
CONSTRAINT FK_Parent_Ref_Id FOREIGN KEY (Parent_Ref_Id) REFERENCES Parent(Parent_Id)
)
Spring data JPA code
Parent Entity
#Entity(name = "Parent")
#Table(name = "Parent")
public class Parent {
#Id
#Column(name = "Parent_Id")
private long parentId;
#Column(name = "Parent_Name")
private String parentName;
//cascade = {CascadeType.REMOVE} OR orphanRemoval = true
#OneToOne(cascade = {CascadeType.ALL},orphanRemoval = true,fetch = FetchType.LAZY,mappedBy="parentInfo")
private Child childInfo;
public long getParentId() {
return parentId;
}
public void setParentId(long parentId) {
this.parentId = parentId;
}
public String getParentName() {
return parentName;
}
public void setParentName(String parentName) {
this.parentName = parentName;
}
public Child getChildInfo() {
return childInfo;
}
public void setChildInfo(Child childInfo) {
this.childInfo = childInfo;
}
}
Child Entity
#Entity(name = "Child")
#Table(name = "Child")
public class Child {
#Id
#Column(name = "Child_Id")
private int childId;
#Column(name = "Child_Name")
private String childName;
#OneToOne
#JoinColumn(name = "Parent_Ref_Id", referencedColumnName = "Parent_Id")
private Parent parentInfo;
public int getChildId() {
return childId;
}
public void setChildId(int childId) {
this.childId = childId;
}
public String getChildName() {
return childName;
}
public void setChildName(String childName) {
this.childName = childName;
}
public Parent getParentInfo() {
return parentInfo;
}
public void setParentInfo(Parent parentInfo) {
this.parentInfo = parentInfo;
}
}
Child Repo Code
public interface ChildRepo extends CrudRepository<Child,Long> {
}
Parent Repo Code
public interface ParentRepo extends CrudRepository<Parent,Long> {
Parent findByParentId(long id);
}
Controller Code
#Autowired
private final ParentRepo parentRepo;
....
private void save() {
//Save the parent
Parent p = new Parent();
p.setParentId(1);
p.setParentName("Parent1");
Child c = new Child();
c.setChildId(1);
c.setChildName("Child1");
c.setParentInfo(p);
p.setChildInfo(c);
parentRepo.save(p);
//delete the parent and child as well
Parent p1 = parentRepo.findByParentId(1);
parentRepo.delete(p1);
}
I need 3 entities: User, Contract (which are a many to many relation) and a middle entity: UserContract (this is needed to store some fields).
What I want to know is the correct way to define the relationships between these entities in JPA/EJB 3.0 so that the operations (persist, delete, etc) are OK.
For example, I want to create a User and its contracts and persist them in a easy way.
Currently what I have is this:
In User.java:
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<UserContract> userContract;
In Contract.java:
#OneToMany(mappedBy = "contract", fetch = FetchType.LAZY)
private Collection<UserContract> userContract;
And my UserContract.java:
#Entity
public class UserContract {
#EmbeddedId
private UserContractPK userContractPK;
#ManyToOne(optional = false)
private User user;
#ManyToOne(optional = false)
private Contract contract;
And my UserContractPK:
#Embeddable
public class UserContractPK implements Serializable {
#Column(nullable = false)
private long idContract;
#Column(nullable = false)
private String email;
Is this the best way to achieve my goals?
Everything looks right. My advice is to use #MappedSuperclass on top of #EmbeddedId:
#MappedSuperclass
public abstract class ModelBaseRelationship implements Serializable {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
}
protected Id id = new Id();
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
I omitted obvious constructors/setters for readability. Then you can define UserContract as
#Entity
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "user_id")),
#AttributeOverride(name = "entityId2", column = #Column(name = "contract_id"))
})
public class UserContract extends ModelBaseRelationship {
That way you can share primary key implementation for other many-to-many join entities like UserContract.