PropertyAccessException when saving entity with #Embeddable - java

I am following this map a many-to-many association with extra columns tutorial but wasn't quite successfully.
So I have the following entities...
#Data
#Entity
#Table(name = "PEOPLE")
public class People implements Serializable {
#Id
#SequenceGenerator(name = "people", sequenceName = "people_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "people")
private long peopleId;
private String peopleName;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(
mappedBy = "people",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PeopleStats> peopleStats;
public void addStats(Stats stats) {
if (this.peopleStats == null) {
this.peopleStats = new ArrayList<>();
}
PoepleStats pStats = PoepleStats.builder().people(this).stats(stats).build();
this.peopleStats.add(pStats);
}
}
#Data
#Entity
#Table(name = "STATS")
public class Stats implements Serializable {
#Id
#SequenceGenerator(name = "stats", sequenceName = "stats_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "stats")
private long statsId;
private String statsName;
private String statsDescription;
}
#Data
#Entity
#Table(name = "PEOPLE_STATS")
public class PeopleStats implements Serializable {
#EmbeddedId
private PeopleStatsId peopleStatsId;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("peopleId")
private People people;
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("statsId")
private Stats stats;
private long value;
}
#Data
#Embeddable
#EqualsAndHashCode
public class PeopleStatsId implements Serializable {
// Putting #Column(name = "people_id") or not doesn't seem to have any effect
private long peopleId;
// Same goes for this
private long statsId;
}
And then with the following unit test..
#RunWith(SpringRunner.class)
#DataJpaTest
public class PeopleRepositoryTest {
#Autowired
private TestEntityManager entityManager;
#Test
public void testSavePeople() {
// People object created
people.addStats(Stats.builder().statsId(new Long(1)).statsName("a").statsDescription("b").build());
this.entityManager.persistAndFlush(people);
}
}
The table generated by hibernate was as such:
Hibernate: create table people_stats (value bigint not null, people_people_id bigint not null, stats_stats_id bigint not null, primary key (people_people_id, stats_stats_id))
And this is the stacktrace..
javax.persistence.PersistenceException:
org.hibernate.PropertyAccessException: Could not set field value 1
value by reflection : [class
com.sample.shared.entity.PeopleStatsId.peopleId] setter of
com.sample.shared.entity.PeopleStatsId.peopleId at
org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:149)
at
org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:157)
at
org.hibernate.internal.ExceptionConverterImpl.convert
and so on ... 63 more
I came across this similar issue, with solution but not working. After trying the first solution, which is creating a new PeopleStatsId object for the #EmbeddedId, it throws me the same error.
Anyone can guide me along? Thanks.
Update 1: I have uploaded a POC on github.
Update 2:
public void addStats(Stats stats) {
if (this.peopleStats == null) {
this.peopleStats = new ArrayList<>();
}
PeopleStats pStats = PeopleStats.builder().peopleStatsId(new PeopleStatsId()).people(this).stats(stats).build();
this.peopleStats.add(pStats);
}
It is now throwing detached entity error.
Caused by: org.hibernate.PersistentObjectException: detached entity
passed to persist: com.sample.Stats at
org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:124)
at
org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:807)
... 68 more
Update 3:
I have changed CascadeType.ALL to MERGE and it seem to solve the problem, but I'm not so sure why though. I even removed the portion in update 2 about .peopleStatsId(new PeopleStatsId()) and it works as well. Now I'm even more puzzled.
In People Class:
#OneToMany(
mappedBy = "people",
cascade = CascadeType.MERGE,
orphanRemoval = true
)
private List<PeopleStats> peopleStats;
public void addStats(Stats stats) {
if (this.peopleStats == null) {
this.peopleStats = new ArrayList<>();
}
PeopleStats pStats = PeopleStats.builder().people(this).stats(stats).build();
this.peopleStats.add(pStats);
}

You need to instatiate peopleStatsId in your PeopleStats class. So change line:
#EmbeddedId
private PeopleStatsId peopleStatsId;
to this:
#EmbeddedId
private PeopleStatsId peopleStatsId = new PeopleStatsId();
Hibernate is trying to set fields of PeopleStatsId but the instance is equal to null so the NullPointerException is thrown.

So after help from #K.Nicholas and research, I think I have managed to resolve the issue and learned new things from it.
#K.Nicholas was right about settings the fields for People and Stats but it wasn't quite clear to me initially. Anyway, it got to me later me.
Basically, all other classes stays pretty much the same except for People class.
#Data
#Entity
#Builder
#Table(name = "PEOPLE")
public class People implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name = "people", sequenceName = "people_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "people")
private long peopleId;
private String peopleName;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#OneToMany(
mappedBy = "people",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<PeopleStats> peopleStats;
// Maintain the state of objects association
public void addStats(Stats stats) {
if (this.peopleStats == null) {
this.peopleStats = new ArrayList<>();
}
// Important to ensure that a new instance of PeopleStatsId is being passed into the field
// otherwise, PropertyAccessException will be encountered
PeopleStats pStats = PeopleStats.builder()
.people(this)
.stats(stats)
.peopleStatsId(new PeopleStatsId(this.getPeopleId(), stats.getStatsId()))
.build();
this.peopleStats.add(pStats);
}
}
Take note of the comment in addStats method where I need to pass in a new instance of PeopleStatsId object to initialize the PeopleStatsId object which should have been done so in the first place except that it wasn't. Lesson learnt.
I also mentioned that I met with the issue of detached entity previously. It was because I was trying to set in the Stats id field when it wasn't required.
According to Hibernate Guide,
detached
the entity has an associated identifier, but is no longer associated with a persistence context (usually because the persistence
context was closed or the instance was evicted from the context)
In my post, I was trying to set in Stats to People, then persist it.
#Test
public void testSavePeople() {
// People object created
people.addStats(Stats.builder().statsId(new Long(1)).statsName("a").statsDescription("b").build());
this.entityManager.persistAndFlush(people);
}
The .statsId(new Long(1)) was the problem, because it was considered as a detached entity since there was an id. CascadeType.MERGE would work in this case is because I think due to the saveOrUpdate feature? Anyway, without setting statsId, CascadeType.ALL would work just fine.
An sample of the unit test (working):
#Test
public void testSavePeopleWithOneStats() {
// Creates People entity
People people = this.generatePeopleWithoutId();
// Retrieve existing stats from StatsRepository
Stats stats = this.statsRepository.findById(new Long(1)).get();
// Add Stats to People
people.addStats(stats);
// Persist and retrieve
People p = this.entityManager.persistFlushFind(people);
assertThat(p.getPeopleStats().size()).isEqualTo(1);
}
I had a data-h2.sql script which loaded in Stats data upon starting of unit test, that's why I can retrieve it from statsRepository.
I have also updated my poc in github.
Hope this helps with whoever comes next.

Related

Hibernate 4.3 Cascade Merge Through Multiple Lists With Embeded ID

Hibernate 4.3.11
I have an issue saving the following object graph in hibernate. The Employer is being saved using the merge() method.
Employer
|_ List<EmployerProducts> employerProductsList;
|_ List<EmployerProductsPlan> employerProductsPlan;
The Employer & EmployerProducts have a auto generated pk. The EmployerProductsPlan is a composite key consisting of the EmployerProducts id and a String with the plan code.
The error occurs when there is a transient object in the EmployerProducts list that cascades to List<EmployerProductsPlan>. The 1st error that I encountered which I have been trying to get past was an internal hibernate NPE. This post here perfectly describes the issue that I am having which causes the null pointer Hibernate NullPointer on INSERTED id when persisting three levels using #Embeddable and cascade
The OP left a comment specifying what they did to resolve, but I end up with a different error when changing to the suggested mapping. After changing the mapping, I am now getting
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.webexchange.model.EmployerProductsPlan#com.webexchange.model.EmployerProductsPlanId#c733f9bd]
Due to other library dependencies, I cannot upgrade above 4.3.x at this time. This project is using spring-boot-starter-data-jpa 1.3.3. No other work is being performed on the session other than calling merge() and passing the employer object.
Below is the mappings for each class:
Employer
#Entity
#Table(name = "employer")
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerNo"})
public class Employer implements java.io.Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "EMPLOYER_NO", unique = true, nullable = false)
private Long employerNo;
.....
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employer", orphanRemoval = true)
private List<EmployerProducts> employerProductsList = new ArrayList<>(0);
}
EmployerProducts
#Entity
#Table(name = "employer_products")
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"employerProductsNo"})
public class EmployerProducts implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "employer_products_no", unique = true, nullable = false)
private Long employerProductsNo;
#ManyToOne
#JoinColumn(name = "employer_no", nullable = false)
private Employer employer;
......
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employerProducts", orphanRemoval = true)
private List<EmployerProductsPlan> employerProductsPlanList = new ArrayList<>(0);
}
EmployerProductsPlan
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"id"})
#Entity
#Table(name="employer_products_plan")
public class EmployerProductsPlan implements Serializable {
#EmbeddedId
#AttributeOverrides({ #AttributeOverride(name = "plan", column = #Column(name = "epp_plan", nullable = false)),
#AttributeOverride(name = "employerProductsNo", column = #Column(name = "employer_products_no", nullable = false)) })
private EmployerProductsPlanId id;
#ManyToOne
#JoinColumn(name = "employer_products_no")
#MapsId("employerProductsNo")
private EmployerProducts employerProducts;
}
I am populating the employerProducts above with the same instance of the EmployerProducts object that is being saved. It is transient and has no id populated as it does not existing in the db yet.
EmployerProductsPlanId
#Accessors(chain = true) // has to come before #Getter and #Setter
#lombok.Getter
#lombok.Setter
#lombok.EqualsAndHashCode(of = {"plan", "employerProductsNo"})
#Embeddable
public class EmployerProductsPlanId implements Serializable {
private String plan;
private Long employerProductsNo;
// This was my previous mapping that was causing the internal NPE in hibernate
/* #ManyToOne
#JoinColumn(name = "employer_products_no")
private EmployerProducts employerProducts;*/
}
UPDATE:
Showing struts controller and dao. The Employer object is never loaded from the db prior to the save. Struts is creating this entire object graph from the Http request parameters.
Struts 2.5 controller
#lombok.Getter
#lombok.Setter
public class EditEmployers extends ActionHelper implements Preparable {
#Autowired
#lombok.Getter(AccessLevel.NONE)
#lombok.Setter(AccessLevel.NONE)
private IEmployerDao employerDao;
private Employer entity;
....
public String save() {
beforeSave();
boolean newRecord = getEntity().getEmployerNo() == null || getEntity().getEmployerNo() == 0;
Employer savedEmployer = newRecord ?
employerDao.create(getEntity()) :
employerDao.update(getEntity());
setEntity(savedEmployer);
return "success";
}
private void beforeSave() {
Employer emp = getEntity();
// associate this employer record with any products attached
for (EmployerProducts employerProduct : emp.getEmployerProductsList()) {
employerProduct.setEmployer(emp);
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.setEmployerProducts(employerProduct));
}
// check to see if branding needs to be NULL. It will create the object from the select parameter with no id
// if a branding record has not been selected
if (emp.getBranding() != null && emp.getBranding().getBrandingNo() == null) {
emp.setBranding(null);
}
}
}
Employer DAO
#Repository
#Transactional
#Service
#Log4j
public class EmployerDao extends WebexchangeBaseDao implements IEmployerDao {
private Criteria criteria() {
return getCurrentSession().createCriteria(Employer.class);
}
#Override
#Transactional(readOnly = true)
public Employer read(Serializable id) {
return (Employer)getCurrentSession().load(Employer.class, id);
}
#Override
public Employer create(Employer employer) {
getCurrentSession().persist(employer);
return employer;
}
#Override
public Employer update(Employer employer) {
getCurrentSession().merge(employer);
return employer;
}
}
As of right now, my solution is to loop through the EmployerProducts and check for new records. I called a persist on the new ones before calling the merge() on the parent Employer. I also moved the logic I had associating all the keys into the dao instead of having it in my Struts action. Below is what my update() method in the Employer DAO now looks like
public Employer update(Employer employer) {
// associate this employer record with any products attached
for (EmployerProducts employerProduct : employer.getEmployerProductsList()) {
employerProduct.setEmployer(employer);
if (employerProduct.getEmployerProductsNo() == null) {
// The cascade down to employerProductsPlanList has issues getting the employerProductsNo
// automatically if the employerProduct does not exists yet. Persist the new employer product
// before we try to insert the new composite key in the plan
// https://stackoverflow.com/questions/54517061/hibernate-4-3-cascade-merge-through-multiple-lists-with-embeded-id
List<EmployerProductsPlan> plansToBeSaved = employerProduct.getEmployerProductsPlanList();
employerProduct.setEmployerProductsPlanList(new ArrayList<>());
getCurrentSession().persist(employerProduct);
// add the plans back in
employerProduct.setEmployerProductsPlanList(plansToBeSaved);
}
// associate the plan with the employer product
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.getId().setEmployerProductsNo(employerProduct.getEmployerProductsNo())
);
}
return (Employer)getCurrentSession().merge(employer);
}

JPA Error: InvalidDataAccessApiUsageException: detached entity passed to persist

I have two entities mapped to one another using the oneToMany annotation. One entity is bookedBus and the second is drivers The drivers entity would already have a row inserted into that would later become a foreign reference (FK) to bookedBus entity(PK). Below are the two entities, setters and getter have been skipped for brevity.
First entity
#Entity
#Table(name = "bookedBuses")
public class BookedBuses implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "driver_id")
private Drivers driver;
}
Second entity
#Entity
public class Drivers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "driver")
private List<BookedBuses> bookedBus;
}
Now When I try to save to the booked bus entity it throws the following exception
org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.bus.api.entity.Drivers; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.bus.api.entity.Drivers
Below is how I tried saving to the bookedBus entity
BookedBuses bookedRecord = new BookedBuses();
bookedRecord.setBookedSeats(1);
bookedRecord.setBookedBusState(BookedBusState.LOADING);
bookedRecord.setBus(busService.getBusByPlateNumber(booking.getPlateNumber()));
bookedRecord.setRoute(booking.getRoute());
infoLogger.info("GETTING DRIVER ID ======= " + booking.getDriver().getId());
Drivers drivers = new Drivers(booking.getDriver().getId());
List<BookedBuses> d_bu = new ArrayList<>();
drivers.setBooked(d_bu);
drivers.addBooked(bookedRecord);
bookedRecord.setDriver(drivers);
bookedBusService.save(bookedRecord);
My BookBusService Save Method as requested
#Autowired
private BookedBusRepository bookedBusRepo;
public boolean save(BookedBuses bookedRecord) {
try {
bookedBusRepo.save(bookedRecord);
return true;
} catch (DataIntegrityViolationException ex) {
System.out.println(ex);
AppConfig.LOGGER.error(ex);
return false;
// Log error message
}
}
1st you have some mix up in naming: you have Driver & Drivers. Like this:
private Drivers driver;
Also selecting variable names like this:
BookedBuses bookedRecord = new BookedBuses();
will cause a lot of confusion. Do not mix plural & singular between types and preferably also do not introduce names that might not be easily associated like record. Also this:
private List<BookedBuses> bookedBus;
which should rather be like:
private List<BookedBus> bookedBuses;
(and would alsoi require change to your class name BookedBuses -> BookedBus)
Anyway the actual problem seems to lie here:
Drivers drivers = new Drivers(booking.getDriver().getId());
You need to fetch existing entity by id with a help of repository instead of creating a new one with id of existing. So something like:
Drivers drivers = driverRepo.findOne(booking.getDriver().getId()); // or findById(..)
It seems that you have a constructor (that you did not show) that enables to create a driver with id. That is not managed it is considered as detached. (You also have drivers.addBooked(bookedRecord); which you did not share but maybe it is trivial)
Note also some posts suggest to changeCascadeType.ALL to CascadeType.MERGE whether that works depends on your needs. Spring data is able to do some merging on save(..) based on entity id but not necessarily in this case.
This line
Drivers drivers = new Drivers(booking.getDriver().getId());
If you already have the driver ID available with you then there's no need to pull the driver ID again from the DB.
After removing the Cascade attribute from #OneToMany & #ManyToOne your code should work.
#Entity
#Table(name = "bookedBuses")
public class BookedBuses implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
`
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "driver_id")
private Drivers driver;
}
#Entity
public class Drivers implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "driver_id")
private List<BookedBuses> bookedBus;
}

Persisting with JPA leaves nested object's db foreign key field null

I have tables with structure:
orders
- id: bigint(20)
- amount: bigint(20)
order_details
- id: bigint(20)
- payment_type: varchar(255)
- order_fk: bigint(20)
Entities:
MyOrderEntity
#Entity
#Table(name = "orders")
public class MyOrderEntity {
#Id
#GeneratedValue(strategy = IDENTITY)
public Long id;
public Long amount;
#OneToOne(fetch = LAZY, mappedBy = "order", cascade = ALL)
public MyOrderDetailsEntity details;
}
MyOrderDetailsEntity
#Entity
#Table(name = "order_details")
public class MyOrderDetailsEntity {
#Id
#GeneratedValue(strategy = IDENTITY)
public Long id;
#OneToOne
#JoinColumn(name = "order_fk")
public MyOrderEntity order;
public String paymentType;
}
Repository:
#Repository
public interface MyOrderRepository extends JpaRepository<MyOrderEntity, Long> {}
I'm persisting MyOrderEntity in such way:
MyOrderDetailsEntity details = new MyOrderDetailsEntity();
details.paymentType = "default";
MyOrderEntity order = new MyOrderEntity();
order.amount = 123L;
order.details = details;
myOrderRepository.save(order);
After order saving I have null value in order_details.order_fk field.
I want that order_details.order_fk will be filled by order.id.
How can I do this?
You need also to explicitly set the MyOrderEntity to MyOrderDetailsEntity. JPA implementation does not do it for you. So add line:
details.order = order;
before save.
You can also add following method to MyOrderEntity:
#PrePersist
private void prePersist() {
if(null!=details) details.order=this;
}
to avoid boilerplate code everywhere you set the MyOrderDetailsEntity to MyOrderEntity.
But the best way is to set MyOrderDetailsEntity.details field private and create a setter like:
setDetails(MyOrderDetailsEntity details) {
this.details = details;
details.order = this;
}
to keep it always set correctly, even before persisting. Best strategy depends on the case.
See this question and answers for more details.

How to update database entries that have oneToMany relations with JPA without creating duplicates

I have a class (Material) which has three OneToMany relationships with other classes. Persisting these into the database is no problem and the relation works. Getting the material from the database also works fine. But I have been struggling with updating the data in the database properly. For reference I am working with JPA, Hibernate, Spring Boot and PostgreSQL. My setup is like this:
Material entity:
#Entity
#Table(name= "MATERIAL")
public class Material {
#Id
#SequenceGenerator(name = "materialGen", sequenceName = "material_seq", allocationSize = 1)
#GeneratedValue(generator = "materialGen")
#Column(name = "id")
private long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "mat_id", cascade = CascadeType.PERSIST)
private List<AltNames> mat_alt_names;
One of the relations: (I only included one in the example)
#Entity
#Table(name= "MAT_ALT_NAMES")
public class AltNames{
#Id
#SequenceGenerator(name = "altGen", sequenceName = "alt_seq", allocationSize = 1)
#GeneratedValue(generator = "altGen")
#Column(name = "id")
private long id;
#JsonIgnore
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "mat_id", nullable = false)
private Material mat_id;
#Column(name = "name")
private String name;
The rest controller:
#RequestMapping(value = "editmaterial/{mat_id}", method = RequestMethod.POST, consumes = "application/json")
public ResponseEntity<?> postEditMaterial(#PathVariable("mat_id") String id, #RequestBody Material material){
Material returnMaterial = materialService.editMaterial(Long.valueOf(id), material);
return new ResponseEntity<Object>(returnMaterial, HttpStatus.OK);
}
The service class with relevant method:
#Service
#Transactional
public class MaterialService {
#Transactional(propagation = Propagation.REQUIRED)
public Material editMat
erial(long id, Material material) {
EntityManager nem = createEntityManager();
Material oldMaterial = nem.find(Material.class, id);
//Checks if the material is found
if (oldMaterial == null) {
log.error("No materials found by id");
}
nem.getTransaction().begin();
oldMaterial.setName(material.getName());
oldMaterial.setAvg_price(material.getAvg_price());
for (Location l : material.getMat_location()) {
l.setMat_id(material);
}
for (AltNames a : material.getMat_alt_names()) {
a.setMat_id(material);
}
for (Price p : material.getMat_price()) {
p.setMat_id(material);
}
oldMaterial.setMat_alt_names(material.getMat_alt_names());
oldMaterial.setMat_location(material.getMat_location());
oldMaterial.setMat_price(material.getMat_price());
nem.flush();
nem.getTransaction().commit();
nem.close();
return getMaterialByName(material.getName());
}
#Transactional
public EntityManager createEntityManager(){
EntityManagerFactory emf = Persistence.createEntityManagerFactory("default");
EntityManager newEm = emf.createEntityManager();
return newEm;
}
With any feedback I will update the post to give more relevant information and remove irrelevant code. My problem now is that this code does update the data but also creates a duplicate. I've tried several other methods, including merge, not using getTransaction, not using commit but using merge instead. It either doesn't update the data, misses the data in the oneToMany relations or creates a duplicate.
As an alternative I wouldn't mind creating a duplicate and deleting the original, but I don't yet know how to delete the original without creating conflict.
Any feedback would be appreciated, thank you for your time.

EclipseLink how to delete orphans entities from the "one side" in a ManyToOne relationship

After searching an answer, I have seen the #PrivateOwner annotation but it doesn't solve my issue.
I'm using EclipseLink
here is the issue:
I have 2 Entities related with a 1:n bidirectional relationship.
A ComponentEntity can be related to one TransferDetailsEntity
A TransfertDetailsEntity is related to one or multiple ComponentEntity.
what I want to achieve is: when I delete the last Component referencing a TransfertDetails, this TransfertDetails should be deleted.
Same if I change the last reference to a TransfertDetails.
in short : As soon as a TransfertDetails is not referenced by any Component, it should be deleted.
As a workaround I call this method :
#Override
public void removeOrphanTransfer() {
for (TransferDetailsEntity transfer : transferDetailsRepository.findAll()) {
if (transfer.getComponents().isEmpty()) {
transferDetailsRepository.delete(transfer);
}
}
}
That works but it's not really efficient since it search through the entire table. and it's quite ugly...
here is the (simplified) code for Entities :
TransfertDetailsEntity:
#Entity
#Table(name = TransferDetailsEntity.TABLE_NAME)
#Getter
#Setter
#NoArgsConstructor
public class TransferDetailsEntity extends AbstractEntity {
[...]
#Id
#Column(name = ID, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
[...]
#OneToMany(mappedBy = "transferDetails")
private List<ComponentEntity> components;
[...]
}
ComponentEntity:
#Entity
#Table(name = ComponentEntity.TABLE_NAME, uniqueConstraints = #UniqueConstraint(name = ComponentEntity.TABLE_NAME
+ AbstractEntity.SEPARATOR + AbstractEntity.CONSTRAINT,
columnNames = { ComponentEntity.COLUMN_NAME_SERIAL, ComponentEntity.COLUMN_NAME_TYPE }))
#Getter
#Setter
#ToString(callSuper = true, exclude = { "parent" })
#NoArgsConstructor
public class ComponentEntity extends AbstractEntity {
[...]
#Id
#Column(name = ID, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
protected Long id;
[...]
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = COLUMN_TRANSFER_DETAILS)
private TransferDetailsEntity transferDetails;
[...]
}
As mentioned earlier, #PrivateOwner on the #OneToMany annotation (in TransfertDetailsEntity) doesn't work...
any help appreciated
There is no automatic JPA or EclipseLink feature that will do this for you, your application will have to handle this.
The easiest I can think of is on removal of a ComponentEntity, get the referenced TransfertDetailsEntity and check its components list to see if it has other ComponentEntity references and remove it if it does not. You should be removing each ComponentEntity reference from the TransfertDetailsEntity.components list when you delete it anyway, so this list should be up to date and not incur any database hits.

Categories