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.
Related
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 +
'}';
}
}
I want to search in spring data jpa by bedType. But, bedType is not string. Not String bedType but BedType bedType(Object). Here is my repository
#Query("select a,b.bedType,b.roomCategory from RoomDetail a left outer join RoomMaster b on a.roomId = b.id where lower(b.bedType) like %:bedType%")
<Page>RoomDetail findByBedType(
#Param("bedType") BedType bedType,
Pageable paging);
FindRoomStatus.Java
public class FindRoomStatus {
private BedType bedType;
public BedType getBedType() {
return bedType;
}
public void setBedType(BedType bedType) {
this.bedType = bedType;
}
In my controller, I have got error
String cannot be a converted to BedType
data = roomDetailRepository.findByBedType(findRoomStatus.getBedType().toString(), paging);
Here is BedType.Java
public class BedType implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY, generator = "bed_type_seq")
#SequenceGenerator(name = "bed_type_seq", sequenceName = "bed_type_id_bed_type_seq", allocationSize = 1, initialValue = 1)
private int id;
#Column(name = "bed_type_name", length = 20)
private String bedTypeName;
#JsonIgnore
#Column(name = "status", length = 10)
private String status;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getBedTypeName() {
return bedTypeName;
}
public void setBedTypeName(String bedTypeName) {
this.bedTypeName = bedTypeName;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
List Room Status, in this list, i want to find by BedType
{
"id": 105,
"roomId": 43,
"floor": "1",
"roomNumber": "001",
"description": "Normal",
"status": "Vacant Clean"
},
{
"id": 11,
"bedTypeName": "King size"
},
{
"id": 39,
"categoryName": "President Suite"
}
You are using LIKE keyword in the query but you are accepting BedType object as a parameter in your query. You are sending String as an argument from your controller. This is the problem. toString method will give the string representation of an object.
What you can do is, change the parameter to String which accepts bedTypeName like:
#Query("select a,b.bedType,b.roomCategory from RoomDetail a left outer
join RoomMaster b on a.roomId = b.id where lower(b.bedType.bedTypeName) like
%:bedTypeName%")
<Page>RoomDetail findByBedType(
#Param("bedTypeName") String bedTypeName,
Pageable paging);
And from the controller,
data = roomDetailRepository.findByBedType(findRoomStatus.getBedType().getBedTypeName(),
paging);
I develop an application that allows to manage the candidates, the application contains two tables (candidate and techno) joined with a #ManyToMany join table, I'm looking for how to fill both tables with the same #PostMapping, as my code indicates. I'm using an Angular application witch send a candidat with all the informations and a table of techno (that the candidat have to select, he can not add a new techno). I would like to join the new candidat with some techno. This is what the controller will receive:
{prenom: "Pname", nom: "Name", pseudo: "Pnamename", ecole: "school", mail: "email#email.com", …}
ecole: "school"
mail: "email#email.com"
nom: "Name"
numTel: "0123456789"
prenom: "Pname"
pseudo: "Pnamename"
roleCible: "poste"
secteurActivites: "sector"
techno: Array(3)
0: "android"
1: "drupal"
2: "html"
length: 3
__proto__: Array(0)
typeContrat: "CDI"
villeRecherchee: "Paris"
__proto__: Object
1- Candidat.java
#Entity
#Table(name = "Candidats")
public class Candidat {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nom;
private String prenom;
private String ecole;
private String numTel;
private String mail;
private String pseudo;
private String roleCible;
private String typeContrat;
private String villeRecherchee;
#Temporal(TemporalType.DATE)
private Date dateCurrent = new Date();
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "candidat_techno", joinColumns = { #JoinColumn(name = "candidat_id") },
inverseJoinColumns = {
#JoinColumn(name = "techno_id") })
private Set<Techno> techno = new HashSet<>();
public Candidat() {
}
#SuppressWarnings("unchecked")
public Candidat(String nom, String prenom, String ecole, String numTel, String mail, String pseudo,
String roleCible, String typeContrat, String villeRecherchee, List<Techno> techno, Date dateCurrent,) {
super();
this.nom = nom;
this.prenom = prenom;
this.ecole = ecole;
this.numTel = numTel;
this.mail = mail;
this.pseudo = pseudo;
this.roleCible = roleCible;
this.typeContrat = typeContrat;
this.villeRecherchee = villeRecherchee;
this.techno = (Set<Techno>) techno;
this.dateCurrent = new Date();
//getters ans setters
2- CandidatController
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/avatar")
public class CandidatController {
#Autowired
CandidatDao candidatdao;
#Autowired
TechnoDao technoDao;
#PostMapping(value = "/add-candidat")
public Candidat addCandidate(#RequestBody Candidat Candidat) {
Candidat candidatAdded = candidatdao.save(Candidat);
return candidatAdded;
technodao.save(Candidat.getTechno());
}
}
3- CandidatDAO
#Repository
public interface CandidatDao extends JpaRepository<Candidat, String> {
}
4-Techno.java
#Entity
#Table(name = "techno")
public class Techno {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String nomTechno;
#ManyToMany(fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "techno")
private Set<Candidat> candidat = new HashSet<Candidat>();
public Techno() {
}
#SuppressWarnings("unchecked")
public Techno(String nomTechno, Candidat candidat) {
super();
this.nomTechno = nomTechno;
this.candidat = (Set<Candidat>) candidat;
}
public String getNomTechno() {
return nomTechno;
}
public void setNomTechno(String nomTechno) {
this.nomTechno = nomTechno;
}
#Override
public String toString() {
return "Techno [nomTechno=" + nomTechno + ", candidat=" + candidat + "]";
}
//getters ans setters
5- TechnoController
#CrossOrigin(origins = "http://localhost:4200")
#RestController
#RequestMapping("/avatar")
public class TechnoController {
#Autowired
TechnoDao technodao;
#PostMapping(value = "/add-techno")
public Techno addCandidate(#RequestBody Techno Techno) {
Techno technoAdded = technodao.save(Techno);
return technoAdded;
}
}
6- TechnoDao
#Repository
public interface TechnoDao extends JpaRepository<Techno, String> {
Techno save(Set<Techno> techno);
}
for now I can fill both tables, but with two different post mapping.
how to fill both tables (techno and candidate) at the same time with a single #post mapping ?? like this:
{
id: 1,
nom: "smith",
prenom: "john",
ecole: "usa",
numTel: "11111",
mail: "j#smith",
pseudo: "JS",
roleCible: "usa",
typeContrat: "usa",
villeRecherchee: "paris",
dateCurrent: "2019-10-02",
techno: [
{
id: 1,
nomTechno: "springBoot"
},
{
id: 2,
nomTechno: "java"
}
]
}
In your CandidateController, Add this:
#Autowired
TechnoDao technoDao;
Inside post mapping use this:
technoDao.save(candidat.getTechno());
This has to help you.
I'm working with Spring, hibernate and MySql but I have some problem with seralization of query result.
First in my entity I added #JsonManagedReference on Set structure (#OneToMany side) and #JsonBackReference on single object reference (#ManyToOne side) and it works but I wasn't be able to retrieve all needed information (for example #ManyToOne reference).
So i swapping #JsonBackReference on set structure and #JsonManagedReference on single object but I retrieve
No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: com.model.tablesField.TableUI["data"]->java.util.ArrayList[0]->com.domain.Car["carType"]->com.domain.CarType_$$_jvst744_f["handler"])
I tried also with #JsonIgnore on Set structure but it doesn't work for the same issues.
This is my spring configuration
private Properties getHibernateProperties() {
Properties properties = new Properties();
properties.put(PROPERTY_NAME_HIBERNATE_DIALECT, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
// properties.put(PROPERTY_NAME_HIBERNATE_SHOW_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
properties.put(PROPERTY_NAME_HIBERNATE_FORMAT_SQL, env.getRequiredProperty(PROPERTY_NAME_HIBERNATE_FORMAT_SQL));
properties.put("hibernate.enable_lazy_load_no_trans",true);
return properties;
and this is part of one of my several entities:
/**
* Car generated by hbm2java
*/
#Entity
#Table(name = "car", catalog = "ATS")
public class Car implements java.io.Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Integer idCar;
#JsonManagedReference
private CarType carType;
#JsonManagedReference
private Fleet fleet;
private String id;
private int initialKm;
private String carChassis;
private String note;
#JsonBackReference
private Set<Acquisition> acquisitions = new HashSet<Acquisition>(0);
public Car() {
}
public Car(CarType carType, Fleet fleet, int initialKm, String carChassis) {
this.carType = carType;
this.fleet = fleet;
this.initialKm = initialKm;
this.carChassis = carChassis;
}
public Car(CarType carType, Fleet fleet, String id, int initialKm, String carChassis, String note,
Set<Acquisition> acquisitions) {
this.carType = carType;
this.fleet = fleet;
this.id = id;
this.initialKm = initialKm;
this.carChassis = carChassis;
this.note = note;
this.acquisitions = acquisitions;
}
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "id_car", unique = true, nullable = false)
public Integer getIdCar() {
return this.idCar;
}
public void setIdCar(Integer idCar) {
this.idCar = idCar;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_carType", nullable = false)
public CarType getCarType() {
return this.carType;
}
public void setCarType(CarType carType) {
this.carType = carType;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id_fleet", nullable = false)
public Fleet getFleet() {
return this.fleet;
}
public void setFleet(Fleet fleet) {
this.fleet = fleet;
}
#Column(name = "id", length = 5)
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
#Column(name = "initialKm", nullable = false)
public int getInitialKm() {
return this.initialKm;
}
public void setInitialKm(int initialKm) {
this.initialKm = initialKm;
}
#Column(name = "carChassis", nullable = false, length = 20)
public String getCarChassis() {
return this.carChassis;
}
public void setCarChassis(String carChassis) {
this.carChassis = carChassis;
}
#Column(name = "note", length = 100)
public String getNote() {
return this.note;
}
public void setNote(String note) {
this.note = note;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "car")
public Set<Acquisition> getAcquisitions() {
return this.acquisitions;
}
public void setAcquisitions(Set<Acquisition> acquisitions) {
this.acquisitions = acquisitions;
}
}
one method that uses the query:
#Override
#RequestMapping(value = { "/cars/{idFleet}"}, method = RequestMethod.GET)
public #ResponseBody TableUI getCars(#PathVariable int idFleet) {
TableUI ajaxCall=new TableUI();
try {
ajaxCall.setData(fleetAndCarService.findCarsByIdFleet(idFleet));
return ajaxCall;
} catch (QueryException e) {
ErrorResponse errorResponse= ErrorResponseBuilder.buildErrorResponse(e);
LOG.error("Threw exception in FleetAndCarControllerImpl::addCar :" + errorResponse.getStacktrace());
return ajaxCall;
}
}
two class for the query:
public interface DefRdiRepository extends JpaRepository<DefRdi, Integer>{
//#Query("SELECT CASE WHEN COUNT(c) > 0 THEN true ELSE false END FROM DefRdi c WHERE c.parName = ?1 AND c.description= ?2")
//Boolean existsByParNameAndDescription(String parName, String description);
//Query method of spring, I put findBy and then the key of research
DefRdi findByParNameAndDescription(String parName, String description);
}
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
Where is my error? I don't want Set object but only the single reference. The problem is only when I serialize. Thanks
UPDATE:
I use #JSonIgnore on all set collectionts and Eager instead lazy ad all works fine, but is there a way to retrieve all the information only when I want, for example having two different query?
So it doesn't work
#Override
#Transactional
public List<Car> findByFleetIdFleet(int idFleet) {
List<Car> carList= carRepository.findByFleetIdFleet(idFleet);
for (Car car:carList){
Hibernate.initialize(car.getCarType());
Hibernate.initialize(car.getFleet());
}
return carList;
// return carRepository.findByFleetIdFleet(idFleet);
}
All collections need to be fetched eagerly when loading them from data base, in order to get serialized by Spring. Make sure you fetch them eagerly (e.g. FetchMode.JOIN). You could also swap #JsonManagedReference from wanted fields with #JsonIgnore to black listed fields, Spring automatically serialises every field without annotation.
Update:
Changing the data repository to something like that should work, I am not sure it compiles, but I think you will get the point:
#EntityGraph(value = "some.entity.graph", type = EntityGraph.EntityGraphType.FETCH)
#Query(
value = "SELECT c FROM Car c INNER JOIN FETCH c.acquisitions WHERE c.id = :idFleet"
)
public interface CarRepository extends JpaRepository<Car, Integer>, CarRepositoryCustom {
//Query method of spring, I put findBy and then the key of research
List<Car> findByFleetIdFleet(int idFleet);
}
For more information look at this post and read the official documentation.
Workaround:
There seems to be a workaround, however fetching those collections eager like shown above should have a positive performance impact, since there is no need for loading proxies afterwards. Also no open transactions are needed at controller level.
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.