guys i've tried different methods proposed here or in an other websites to fix this error, i've fixed it before but in another case, but this one seems a complicated one. , an entity named department which have a oneToMany relation with it self , so the departement can have one or many subDepartments , and a department should have one and only one parent department.
Department Entity :
#Entity
#Table(name = "DEPARTMENTS")
public class Department {
#Id
#Column(name = "dep_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private long depId;
private String depName;
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#OnDelete(action = OnDeleteAction.CASCADE)
#JoinColumn(name = "supDep", referencedColumnName = "dep_Id")
#JsonIgnoreProperties(value ={"departments","users"} , allowSetters = true)
private Department supDep;
#OneToMany(mappedBy = "supDep", orphanRemoval = true, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnoreProperties(value ={"supDep","users"} , allowSetters = true)
private List<Department> departments = new ArrayList<>() ;
Constructors & getters &setters...
}
DepartmentRepository:
#Repository
public interface DepartmentRepository extends JpaRepository<Department,Long> {
Department findByDepName(String name);
}
DepartmentService Interface :
public interface DepartmentService {
Department add(Department department);
Department update(Department department, Long id);
void delete(long id);
List<Department> findAll();
Department findByName(String name);
Department findById(Long id);
User getChefDep(Long idDep);
}
DepartmentServiceImplement :
#Service(value = "departmentService")
public class DepartmentServiceImpl implements DepartmentService {
....
#Override
public Department add(Department department) {
Department newDep = new Department();
if(department.getDepId() != 0)
newDep.setDepId(department.getDepId());
newDep.setDepName(department.getDepName());
newDep.setChefDep(department.getChefDep());
newDep.setSupDep(department.getSupDep());
newDep.setDepartments(department.getDepartments());
newDep.setUsers(department.getUsers());
return departmentRepository.save(department);
}
...
}
DepartmentController ADD method :
#RestController
#CrossOrigin("*")
#RequestMapping("/department/")
public class DepartmentController {
...
#PostMapping("add")
public Department add(#RequestBody Department department) {
return departmentService.add(department);
}
...
}
anyway, when i add a new department with postman it works and the department is saved in DATABASE :
{
"depName": "marketing",
"supDep": null,
"departments": []
}
and when i add a new department with a supDep that doesn't exist in DATABASE it works too and the both entities are saved in DATABASE :
{
"depName": "Security",
"supDep":
{
"depName": "IT",
"supDep": null,
"departments": [],
"chefDep": 0,
}
}
but when i add a new department passing supDep a department that does exist :
{
"depName": "sub-marketing",
"supDep":
{
"depId": 1,
"depName": "marketing"
}
}
it throws this annoying error :
{
"timestamp": "2020-03-17T14:49:40.071+0000",
"status": 500,
"error": "Internal Server Error",
"message": "detached entity passed to persist: com.ats.remotetimemanager.Model.Department; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.ats.remotetimemanager.Model.Department",
"trace": "org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.ats.remotetimemanager.Model.Department; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.ats.remotetimemanager.Model.Department\r\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:319)\r\n\tat org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)\r\n\tat org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:528)\r\n\tat org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61)\r\n\tat org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:242)\r\n\tat org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:153)\r\n\tat org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)\r\n\tat org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
The correct way to implement the method
#Override
public Department add(Department department) {
Department newDep;
if(department.getDepId() != 0) {
Optional<Department> optional = departmentRepository.findById(id);
if(optional.isPresent()) {
newDep = optional.get();
} else {
newDep = new Department();
}
} else {
newDep = new Department();
}
newDep.setDepName(department.getDepName());
newDep.setChefDep(department.getChefDep());
newDep.setSupDep(department.getSupDep());
newDep.setDepartments(department.getDepartments());
newDep.setUsers(department.getUsers());
return departmentRepository.save(department);
}
the getters & setters :
public long getDepId() {
return depId;
}
public void setDepId(long depId) {
this.depId = depId;
}
public String getDepName() {
return depName;
}
public void setDepName(String depName) {
this.depName = depName;
}
public Department getSupDep() {
return supDep;
}
public void setSupDep(Department supDep) {
this.supDep = supDep;
}
public List<Department> getDepartments() {
return departments;
}
public void setDepartments(List<Department> departments) {
this.departments = departments;
}
public long getChefDep() {
return chefDep;
}
public void setChefDep(long chefDep) {
this.chefDep = chefDep;
}
public List<User> getUsers() {
return users;
}
public void setUsers(List<User> users) {
this.users = users;
}
#Override
public String toString() {
return "Department{" +
"depId=" + depId +
", depName='" + depName + '\'' +
", supDep=" + supDep +
", departments=" + departments +
", chefDep=" + chefDep +
'}';
}
}
Related
I am making a REST api in Java with Spring boot JPA Hibernate and with H2 database, I am trying to do a POST to "api/facturas" which is the RequestMapping of FacturaController, in which I want to add data to the FacturaDetalles List, but It returns an empty array, as if the data had never been sent, what am I doing wrong here?
I am trying to make a POST to "/api/facturas" like this:
{
"cliente": {
"idCliente": 1
},
"facturaDetalles": [
{
"producto": {
"idProducto": 3
}
}
]
}
This is what it should return:
{
"cliente": {
"idCliente": 1,
"nombreCliente": "Juan",
"apellidoCliente": "Rufiol",
"dni": 35884121,
"direccionCliente": "Av. Siempre Viva 300",
"telefonoCliente": 353654128,
"emailCliente": "juancito#gmail.com",
"cuit": 5412456985510,
"ciudad": "Capital Federal",
"provincia": "Buenos Aires",
"pais": "Argentina",
"codigoPostal": 1426,
"fechaNacimiento": "10-10-1980"
},
"facturaDetalles": [
{
"idFacturaDetalles": 1,
"producto": {
"idProducto": 3,
"nombreProducto": "Pollo frito",
"precioProducto": 40,
"descripcion": "Una delicia king chicken",
"stock": 3
}
}
]
}
This is what i get:
{
"cliente": {
"idCliente": 1,
"nombreCliente": "Juan",
"apellidoCliente": "Rufiol",
"dni": 35884121,
"direccionCliente": "Av. Siempre Viva 300",
"telefonoCliente": 353654128,
"emailCliente": "juancito#gmail.com",
"cuit": 5412456985510,
"ciudad": "Capital Federal",
"provincia": "Buenos Aires",
"pais": "Argentina",
"codigoPostal": 1426,
"fechaNacimiento": "10-10-1980"
},
"facturaDetalles": []
}
[data.sql]
INSERT INTO clientes (nombre,apellido,dni,direccion,telefono,email,cuit,ciudad,provincia,pais,codigo_postal, fecha_nacimiento ) VALUES ('Juan','Rufiol','35884121','Av. Siempre Viva 300','353654128','juancito#gmail.com','5412456985510','Capital Federal','Buenos Aires', 'Argentina',1426,'10-10-1980' );
INSERT INTO clientes (nombre,apellido,dni,direccion,telefono,email,cuit,ciudad,provincia,pais,codigo_postal, fecha_nacimiento ) VALUES ('Rolo','Garcia','41882121','Mariano Moreno 44','353614128','rolaso#rol.com','51134569854113','Capital Federal','Buenos Aires', 'Argentina',1426,'01-03-1989' );
INSERT INTO facturas (nro_factura,tipo_factura,precio_total_factura,id_cliente) VALUES (4444,'A',2000,1);
INSERT INTO facturas (nro_factura,tipo_factura,precio_total_factura,id_cliente ) VALUES (4444,'A',2000,1);
INSERT INTO facturas_detalles (cantidad,subtotal,id_facturas ,id_producto) VALUES (1,1,1,3);
INSERT INTO facturas_detalles (cantidad,subtotal,id_facturas ,id_producto) VALUES (1,1,1,2);
[Factura]
#Entity
#Table(name = "facturas")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Factura {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long idFactura;
#Column(name = "nro_factura")
private Integer nroFactura;
#Column(name = "tipo_factura")
private String tipoFactura;
#Column(name = "fecha", columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private String fechaFactura;
#Column(name = "precio_total_factura")
private Integer precioTotalFactura;
#OneToOne
#JoinColumn(name = "id_cliente")
private Cliente cliente;
#JsonManagedReference
#OneToMany(mappedBy = "factura", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<FacturaDetalles> facturaDetalles = new ArrayList<>();
#OneToOne
#JoinColumn(name = "id_empresa")
private Empresa empresa;
}
[FacturaDTO]
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDTO {
private ClienteDTO cliente;
private List<FacturaDetallesDTO> facturaDetalles;
}
[FacturaServiceImp]
#Service
public class FacturaServiceImp implements FacturaService {
#Autowired
private FacturaRepository facturaRepository;
public List<Factura> getAllFacturas() {
return facturaRepository.findAll();
}
public Factura getOneFactura(Long id) {
return facturaRepository.findById(id).orElseThrow(() -> new RuntimeException("Factura no encontrado"));
}
public Factura createFactura(Factura factura) {
return facturaRepository.save(factura);
}
public void deleteFactura(Long id) {
facturaRepository.deleteById(id);
}
}
[FacturaService]
public interface FacturaService {
List<Factura> getAllFacturas();
Factura getOneFactura(Long id);
Factura createFactura(Factura factura);
void deleteFactura(Long id);
}
[FacturaController]
#RestController
#RequestMapping("/api/facturas")
public class FacturaController {
#Autowired
ModelMapper modelMapper;
#Autowired
FacturaService facturaService;
#GetMapping()
public List<FacturaDTO> getAllFacturas() {
return facturaService.getAllFacturas().stream().map(factura -> modelMapper.map(factura, FacturaDTO.class)).collect(Collectors.toList());
}
#GetMapping("/{id}")
public ResponseEntity<FacturaDTO> getOneFactura(#PathVariable Long id) {
Factura factura = facturaService.getOneFactura(id);
FacturaDTO facturaDTO = modelMapper.map(factura, FacturaDTO.class);
return ResponseEntity.ok().body(facturaDTO);
}
#PostMapping()
public ResponseEntity<FacturaDTO> createFactura(#RequestBody FacturaDTO facturaDTO) {
Factura factura = modelMapper.map(facturaDTO, Factura.class);
Factura facturaCreacion = facturaService.createFactura(factura);
FacturaDTO conversion = modelMapper.map(facturaCreacion, FacturaDTO.class);
return new ResponseEntity<FacturaDTO>(conversion, HttpStatus.CREATED);
}
#DeleteMapping("/{id}")
public ResponseEntity<FacturaDTO> deleteFactura(#PathVariable Long id) {
Factura factura = facturaService.getOneFactura(id);
facturaService.deleteFactura(id);
FacturaDTO facturaDTO = modelMapper.map(factura, FacturaDTO.class);
return ResponseEntity.ok().body(facturaDTO);
}
}
[FacturaDetalles]
#Entity(name = "facturas_detalles")
#Table(name = "facturas_detalles")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDetalles {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long idFacturaDetalles;
#Column(name = "cantidad")
private Integer cantidadProductos;
#Column(name = "subtotal")
private Integer totalParcial;
#JsonBackReference
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_facturas")
private Factura factura;
#OneToOne
#JoinColumn(name = "id_producto")
private Producto producto;
}
[FacturaDetallesDTO]
#Data
#AllArgsConstructor
#NoArgsConstructor
public class FacturaDetallesDTO {
private Long idFacturaDetalles;
private ProductoDTO producto;
}
[FacturaDetallesServiceImp]
#Service
public class FacturaDetallesServiceImp implements FacturaDetallesService {
#Autowired
private FacturaDetallesRepository facturaDetallesRepository;
public List<FacturaDetalles> getAllFacturaDetalles() {
return facturaDetallesRepository.findAll();
}
public FacturaDetalles getOneFacturaDetalles(Long id) {
return facturaDetallesRepository.findById(id).orElseThrow(() -> new RuntimeException("Factura no encontrado"));
}
public FacturaDetalles createFacturaDetalles(FacturaDetalles facturaDetalles) {
return facturaDetallesRepository.save(facturaDetalles);
}
public void deleteFacturaDetalles(Long id) {
facturaDetallesRepository.deleteById(id);
}
}
[FacturaDetallesService]
public interface FacturaDetallesService {
List<FacturaDetalles> getAllFacturaDetalles();
FacturaDetalles getOneFacturaDetalles(Long id);
void deleteFacturaDetalles(Long id);
FacturaDetalles createFacturaDetalles(FacturaDetalles facturaDetalles);
}
[FacturaDetallesController]
#RestController
#RequestMapping("/api/detalles")
public class FacturaDetallesController {
#Autowired
ModelMapper modelMapper;
#Autowired
FacturaDetallesService facturaDetallesService;
#GetMapping()
public List<FacturaDetallesDTO> getAllFacturaDetalles() {
return facturaDetallesService.getAllFacturaDetalles().stream().map(facturaDetalles -> modelMapper.map(facturaDetalles, FacturaDetallesDTO.class)).collect(Collectors.toList());
}
#GetMapping("/{id}")
public ResponseEntity<FacturaDetallesDTO> getOneFacturaDetalles(#PathVariable Long id) {
FacturaDetalles facturaDetalles = facturaDetallesService.getOneFacturaDetalles(id);
FacturaDetallesDTO detallesDTO = modelMapper.map(facturaDetalles, FacturaDetallesDTO.class);
return ResponseEntity.ok().body(detallesDTO);
}
#PostMapping()
public ResponseEntity<FacturaDetallesDTO> createFacturaDetalles(#RequestBody FacturaDetallesDTO facturaDetallesDTO) {
FacturaDetalles facturaDetalles = modelMapper.map(facturaDetallesDTO, FacturaDetalles.class);
FacturaDetalles facturaDetallesCreacion = facturaDetallesService.createFacturaDetalles(facturaDetalles);
FacturaDetallesDTO conversion = modelMapper.map(facturaDetallesCreacion, FacturaDetallesDTO.class);
return new ResponseEntity<FacturaDetallesDTO>(conversion, HttpStatus.CREATED);
}
#DeleteMapping("/{id}")
public ResponseEntity<FacturaDetallesDTO> deleteFacturaDetalles(#PathVariable Long id){
FacturaDetalles facturaDetalles = facturaDetallesService.getOneFacturaDetalles(id);
facturaDetallesService.deleteFacturaDetalles(id);
FacturaDetallesDTO detallesDTO = modelMapper.map(facturaDetalles, FacturaDetallesDTO.class);
return ResponseEntity.ok().body(detallesDTO);
}
}
try like this (lines omitted due to readability, e.g. checks)
#Autowired
private FacturaRepository facturaRepository;
#Autowired
private FacturaDetallesRepository facturaDetallesRepository;
public Factura createFactura(...) {
// get factura from db (by id from dto)
Factura factura = facturaRepository.findByIdFactura(facura.getIdFactura())
// get facturaDetalles from db (by id from dto)
FacturaDetalles facturaDetalles = facturaDetallesRepository.findByIdFacturaDetalles(...)
// link entites, for each detalles object!
facturaDetalles.setFactura(factura)
// add to list, but check if already in list first
factura.getFacturaDetalles().add(facturaDetalles)
facturaRepository.save(factura);
}
In many cases in my application, I want to return a tree of data using #OneToMany and #ManyToOne relationships. I am implementing soft delete using #SQLDelete
#Where annotations. I cannot figure out how to keep the tree from returning soft deleted grandchild objects.
For example, my parent entity ...
#Entity
#Table(name = "gson_test_parent")
#SQLDelete(sql = "UPDATE gson_test_parent SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestParent extends SoftDeletableEntity {
public static final String STATUS_NEW = "new";
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private String name;
#Expose
#OneToMany(fetch = FetchType.EAGER, mappedBy="gsonTestParentId")
private List<GsonTestParentToGsonTestChild> gsonTestParentToGsonTestChild = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public List<GsonTestParentToGsonTestChild> getGsonTestParentToGsonTestChild() {
return gsonTestParentToGsonTestChild;
}
}
... my join entity ...
#Entity
#Table(name = "gson_test_parent_to_gson_test_child")
#SQLDelete(sql = "UPDATE gson_test_parent_to_gson_test_child SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestParentToGsonTestChild extends SoftDeletableEntity {
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private Long gsonTestParentId;
#Expose
#Transient
#GsonExcludeBackReference
private GsonTestParent gsonTestParent;
#Expose
#ManyToOne(fetch = FetchType.EAGER)
#Where(clause = "deleted=false")
private GsonTestChild gsonTestChild;
public Long getGsonTestParentId() {
return gsonTestParentId;
}
public GsonTestParent getGsonTestParent() {
return gsonTestParent;
}
public void setGsonTestParent(GsonTestParent gsonTestParent) {
this.gsonTestParent = gsonTestParent;
}
public GsonTestChild getGsonTestChild() {
return gsonTestChild;
}
}
... and the child entity ...
#Entity
#Table(name = "gson_test_child")
#SQLDelete(sql = "UPDATE gson_test_child SET deleted = true, deleted_at = now() WHERE id=?")
#Where(clause = "deleted=false")
public class GsonTestChild extends SoftDeletableEntity {
public static final String STATUS_NEW = "new";
public static final String STATUS_ACTIVE = "active";
public static final String STATUS_DISABLED = "disabled";
#Expose
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
FYI, these all extend SoftDeletableEntity, which looks like ...
#MappedSuperclass
public class SoftDeletableEntity extends BaseEntity {
public SoftDeletableEntity() {
super();
}
#Expose
protected Timestamp deletedAt;
protected Boolean deleted = Boolean.FALSE;
public Timestamp getDeletedAt() {
return deletedAt;
}
public void setDeletedAt(Timestamp deletedAt) {
this.deletedAt = deletedAt;
}
public Boolean getDeleted() {
return deleted;
}
public void setDeleted(Boolean deleted) {
this.deleted = deleted;
}
}
When I do a find on the parent entity ...
#GetMapping(path="/{id}")
public ResponseEntity<String> get(#PathVariable Long id) throws BaseException {
Map<String, Object> responseMap = new HashMap<>();
GsonTestParent gsonTestParent = gsonTestParentService.find(id);
responseMap.put("action", "Get");
responseMap.put("message", "Entity retrieved");
responseMap.put("entityType", "GsonTestParent");
responseMap.put("entity", gsonTestParent);
return responseService.success(responseMap);
}
I get the child entity (grandchild) even though it is marked as deleted in the database ...
{
"payload": {
"entityType": "GsonTestParent",
"action": "Get",
"message": "Entity retrieved",
"entity": {
"name": "test_parent_1",
"gsonTestParentToGsonTestChild": [
{
"gsonTestParentId": 1,
"gsonTestChild": {
"name": "test_child_1",
"deletedAt": "2022-07-26T04:31:30.000",
"id": 1,
"createdAt": "2022-07-22T07:24:15.000",
"updatedAt": "2022-07-22T07:24:15.000",
"status": "active"
},
"deletedAt": null,
"id": 1,
"createdAt": "2022-07-22T07:57:46.000",
"updatedAt": "2022-07-22T07:57:46.000",
"status": "active"
}
],
"deletedAt": null,
"id": 1,
"createdAt": "2022-07-22T07:23:15.000",
"updatedAt": "2022-07-22T07:23:15.000",
"status": "active"
}
},
"status": "success"
}
The gson_test_child record in the DB
mysql> select * from gson_test_child where id = 1;
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
| id | created_at | updated_at | deleted_at | deleted | status | name |
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
| 1 | 2022-07-22 14:24:15 | 2022-07-22 14:24:15 | 2022-07-26 11:31:30 | 1 | active | test_child_1 |
+----+---------------------+---------------------+---------------------+---------+--------+--------------+
A few comments:
I am referencing the join table explicitly, as opposed to using the #JoinTable functionality because many of the "join" tables in my app have other meaningful fields I want to expose.
I thought the #Where annotation on the GsonTestParentToGsonTestChild.gsonTestChild field would eliminate soft deleted children, but apparently not (or I'm doing something wrong).
I know I can create explicit JOIN FETCH native queries in the repositories that will filter the deleted grandchildren, but that kind of subverts the reasons for using annotations.
Please let me know if I can provide further information.
Mike
Try adding #Where(clause = "deleted=false") also to the collections.
#Christian Beikov,
it looks like you're correct. The #Where(clause = "deleted=false") works on an #OneToMany relationship, but not on an #ManyToOne.
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);
}
}
When adding multiple relationships between nodes simultaneously, only some of them are created. In the example below, the calls to makeUser(...) are only populating some of the relationships.
Main
#Transactional
void clearDatabase() {
session.execute("MATCH (n) OPTIONAL MATCH (n)-[r]-() DELETE n,r");
}
void createPeople() {
Person mark = new Person("Mark");
mark.password = "mark123";
people.save(mark);
Organisation noxRentals = new Organisation("Nox Rentals");
organisations.save(noxRentals);
makeUser(noxRentals, mark, "Administrator", Right.ADMINISTRATE);
makeUser(noxRentals, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, mark, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, richard, "Administrator", Right.ADMINISTRATE);
makeUser(classicVillas, charlotte, "Reservations", Right.LOGIN, Right.SEND_QUOTES);
}
#Transactional
void makeUser (Organisation organisation, Person person, String role, Right...rights) {
IsUser account = organisation.addUser(person, role);
account.addRights(rights);
organisations.save(organisation);
}
void run() {
clearDatabase();
createPeople();
}
Resulting in (notice Nox has no relationships):
Organisation.java
#NodeEntity
public class Organisation extends NitroBaseEntity {
#Relationship(type = "IsUser", direction = Relationship.INCOMING)
Set<IsUser> users = new HashSet<>();
public IsUser addUser(Person person, String role) {
IsUser user = new IsUser(person, this, role);
this.users.add(user);
return user;
}
}
Person.java
#NodeEntity
public class Person extends NitroBaseEntity {
#Property
String password;
#Relationship(type = "IsUser", direction = Relationship.OUTGOING)
Set<IsUser> users = new HashSet<>();
public Set<IsUser> getUserAccounts() {
return this.users;
}
}
IsUser.java
#RelationshipEntity
public class IsUser {
#GraphId
Long id;
#StartNode
public Person person;
#EndNode
public Organisation organisation;
#Property
public String role;
#Property
public Set<Right> rights = new HashSet<>();
public IsUser (Person person, Organisation organisation, String role) {
this.person = person;
this.organisation = organisation;
this.role = role;
}
}
Complete source code: https://bitbucket.org/sparkyspider/neo4j-sandbox-4/src/22eb3aba82e33dfe473ee15e26f9b4701c62fd8e/src/main/java/com/noxgroup/nitro/config/DatabaseInitializer.java?at=master
There are two things missing-
The type has to be specified on the #RelationshipEntity as well, like this #RelationshipEntity(type = "IsUser")
In Organisation.addUser(), add the IsUser to the Person too, something like person.users.add(user);. The entities have to be navigable from both ends.
I'm getting this errors when trying to create relation between 2 entities, this time i'm doing this in different way - passing JSON with 2 object into helper class and then getting those object and persisting them, one by one and setting the relation. When i remove setters of relation : 1. newPerson.setKoordynator(koordynatorzyPraktykEntity);
2.koordynatorzyPraktykEntity.setKoordynatorByIdOsoby(newPerson);
then it is persisting both entities without a problem, with setters only first one (KoordynatorzyPraktykEntity) is persisted (idKoordynatora = 1, idOsoby =0, test = test )
Here is the important part of error from POSTMAN ( full log http://pastebin.com/SRmnPMBH )
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity
org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
KoordynatorzyEntity:
#Entity
#Table(name = "koordynatorzy_praktyk", schema = "public", catalog = "praktykidb")
public class KoordynatorzyPraktykEntity {
private int idKoordynatoraPraktyk;
private int idOsoby;
private String doTestow;
private OsobyEntity koordynatorByIdOsoby;
private Collection<KoordynatorzyKierunkowiEntity> koordynatorzyByIdKierunku;
#Id
#GeneratedValue
#Column(name = "id_koordynatora_praktyk")
public int getIdKoordynatoraPraktyk() {
return idKoordynatoraPraktyk;
}
public void setIdKoordynatoraPraktyk(int idKoordynatoraPraktyk) {
this.idKoordynatoraPraktyk = idKoordynatoraPraktyk;
}
#Basic
#Column(name = "id_osoby")
public int getIdOsoby() {
return idOsoby;
}
public void setIdOsoby(int idOsoby) {
this.idOsoby = idOsoby;
}
/*
STUFF
*/
#JsonIgnore
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_osoby", referencedColumnName = "id_osoby", insertable = false , updatable = false)
public OsobyEntity getKoordynatorByIdOsoby() {
return koordynatorByIdOsoby;
}
public void setKoordynatorByIdOsoby(OsobyEntity koordynatorByIdOsoby) {
this.koordynatorByIdOsoby = koordynatorByIdOsoby;
}
#JsonIgnore
#OneToMany(mappedBy = "koordynatorzyByIdKierunku", cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
public Collection<KoordynatorzyKierunkowiEntity> getKoordynatorzyByIdKierunku() {
return koordynatorzyByIdKierunku;
}
public void setKoordynatorzyByIdKierunku(Collection<KoordynatorzyKierunkowiEntity> koordynatorzyByIdKierunku) {
this.koordynatorzyByIdKierunku = koordynatorzyByIdKierunku;
}
OsobyEntity:
#Entity
#Table(name = "osoby", schema = "public", catalog = "praktykidb")
public class OsobyEntity {
private int idOsoby;
private String tytulZawodowy;
private String imie;
private String nazwisko;
private String email;
private String telefonKomorkowy;
private String telefonStacjonarny;
private KoordynatorzyPraktykEntity koordynator;
#Id
#GeneratedValue
#Column(name = "id_osoby")
public int getIdOsoby() {
return idOsoby;
}
public void setIdOsoby(int idOsoby) {
this.idOsoby = idOsoby;
}
/*
STUFF
*/
#OneToOne(mappedBy = "koordynatorByIdOsoby", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public KoordynatorzyPraktykEntity getKoordynator() {
return koordynator;
}
public void setKoordynator(KoordynatorzyPraktykEntity koordynator) {
this.koordynator = koordynator;
}
KoordynatorzyPraktykService :
public class KoordynatorzyPraktykService implements iKoordynatorzyPraktykService {
#Autowired
private iKoordynatorzyPraktykDAO ikoordynatorzyPraktykDAO;
#Autowired
private iOsobyDAO iosobyDAO;
#Override
public KoordynatorzyPraktykEntity addCoordinator(KoordynatorzyPraktykEntity koordynatorzyPraktykEntity) {
return ikoordynatorzyPraktykDAO.addCoordinator(koordynatorzyPraktykEntity);
}
/*
STUFF
*/
#Override
public OsobyEntity addPerson(OsobyEntity osobyEntity, KoordynatorzyPraktykEntity koordynatorzyPraktykEntity) {
OsobyEntity newPerson = iosobyDAO.addPerson(osobyEntity);
newPerson.setKoordynator(koordynatorzyPraktykEntity);
System.out.println(koordynatorzyPraktykEntity.toString()); //shows idKoordynatora: 1 idOsoby: 0 test: test
System.out.println(newPerson.toString()); //shows idOsoby: 32768 imie: Tomasz nazwisko: Potempa
int idOsoby = newPerson.getIdOsoby();
koordynatorzyPraktykEntity.setIdOsoby(idOsoby);
System.out.println(koordynatorzyPraktykEntity.toString()); //shows idKoordynatora: 1 idOsoby: 32768 test: test
koordynatorzyPraktykEntity.setKoordynatorByIdOsoby(newPerson);
return newPerson;
}
Both DAOs have em.persist(entity)
and POST of KoordynatorzyPraktykController:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<KoordynatorzyPraktykEntity> addCoordinator(#RequestBody Koordynator newCoordinator) {
KoordynatorzyPraktykEntity addCoordinator = ikoordynatorzyPraktykService.addCoordinator(newCoordinator.getKoordynator());
OsobyEntity addPerson = ikoordynatorzyPraktykService.addPerson(newCoordinator.getOsoba(), addCoordinator);
if (addCoordinator !=null && addPerson !=null) {
return new ResponseEntity<KoordynatorzyPraktykEntity>(addCoordinator, HttpStatus.OK);
}
else {
return new ResponseEntity<KoordynatorzyPraktykEntity>(HttpStatus.NOT_FOUND);
}
}
Helper Class Koordynator:
public class Koordynator {
private KoordynatorzyPraktykEntity koordynator;
private OsobyEntity osoba;
public KoordynatorzyPraktykEntity getKoordynator() {
return koordynator;
}
public void setKoordynator(KoordynatorzyPraktykEntity koordynator) {
this.koordynator = koordynator;
}
public OsobyEntity getOsoba() {
return osoba;
}
public void setOsoba(OsobyEntity osoba) {
this.osoba = osoba;
}
}
and this is parsed JSON into controller through POSTMAN
{
"koordynator":
{
"doTestow" : "test"
},
"osoba":
{
"tytulZawodowy" : "inzynier",
"imie" : "Tomasz",
"nazwisko" : "Potempa",
"email" : "tp#tp.pl",
"telefonKomorkowy" : "124675484",
"telefonStacjonarny" : "654786484"
}
}
Only way I got it work
Class A:
#OneToMany(cascade = CascadeType.MERGE)
private List<B> b;
Class B:
#ManyToOne
#JoinColumn(name = "aId", referencedColumnName = "id")
private A a;
private String test;
Service:
A a = new A();
//Create without children
aFacade.create(a);
//items
List<B> list = new ArrayList<>();
B b = new B();
b.setTest("Hello");
b.setA(a);
list.add(b);
//merge
a.setB(list);
aFacade.edit(a);
you hit the exception below simply because the entity isn't in the Entity Manager's session at the moment you are trying to persist it. That's due to laziness of your association.
"detached entity passed to persist: praktyki.core.entities.KoordynatorzyPraktykEntity;"
Try calling em.merge() to attach it to the session.