Spring data JPA #PreRemove ConcurrentModificationException when removing from parent enity - java

I have a case where a participant can register courses.
Basically I have the following entity configuration (getters and setters omitted as well as other useless properties) :
#Entity
#Table(name = "course")
public class Course {
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "course")
private Set<Registration> registrations;
}
#Entity
#Table(name = "participant")
public class Participant {
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "participant")
private Set<Registration> registrations;
}
#Entity
#Table(name = "registration")
public class Registration {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "participant_id")
private Participant participant;
#PreRemove
private void removeRegistrationFromHolderEntities() {
course.getRegistrations().remove(this);
participant.getRegistrations().remove(this);
}
}
Then I can from my viewmodel delete a registration or a course (I have also removed unnecessary stuff) :
#Command
public void deleteRegistration(Registration reg) {
registrationMgr.delete(reg);
}
#Command
public void deleteCourse(Course crs) {
courseMgr.delete(crs);
}
Problem :
If I delete a registration, I need the #PreRemove function so I can remove the references. Without this the remove is ignored (no error, simply ignored)
If I delete a course, I have to remove the #PreRemove function else I get a ConcurrentModificationException (evidently...)
I also cannot remove references from the deleteRegistration method (instead of #PreRemove) because participant registrations are lazily loaded (would raise failed to lazily initialize a collection of role: ..., could not initialize proxy - no Session exception).
What is the best approach here ?
I use Java 11 with Spring Boot 1.0.4 (and spring-boot-starter-data-jpa).
EDIT :
The managers/repositories or defined this way (same for registration and participant) so it should be transactional (I don't have #EnableTransactionManagement on my main class but it should not be required as I don't use transactions outside of repositories) :
#Transactional
#Component("courseMgr")
public class CourseManager {
#Autowired
CourseRepository courseRepository;
public void saveOrUpdate(Course course) {
courseRepository.save(course);
}
public void delete(Course course) {
courseRepository.delete(course);
}
}
public interface CourseRepository extends CrudRepository<Course, Long> {
...
}
EDIT2 :
I think I have found a pretty simple solution :
I have removed the #PreRemove method from the entity, then instead of removing the references like this in the deleteRegistration method (which I had tried but was causing failed to lazily initialize a collection of role exception) :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
I simply set parents to null, I don't care as it will be deleted...
#Command
public void deleteRegistration(Registration reg) {
reg.setCourse(null);
reg.setParticipant(null);
registrationMgr.delete(reg);
}
So now I can also delete a course without triggering the ConcurrentModificationException in the #PreRemove.
EDIT3 : My bad, registration was not removed with the solution above (still no error but nothing happens). I ended with this instead, which finally works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...

You have setup your repositories incorrectly. You need a composite PK for Registration and you need to understand that bidirectional mappings are really for query only. Further, bidirectional mappings in Course and Participate present challenges because the ManyToOne relationship through the Registration entity is FetchType.EAGER by default. With all the cascade and fetch annotations you have you are asking for a complicated combination of things from JPA and it seems like you really haven't sorted it all out yet. Start with the basics, be sure to print your SQL statements, and proceed from there if you want to try to finesse more from JPA.
#Entity
#Data
public class Course {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Participant {
#Id
private Integer id;
private String name;
}
#Entity
#Data
public class Registration {
#EmbeddedId
private RegistrationPK id;
#ManyToOne
#MapsId("participant_id")
private Participant participant;
#ManyToOne
#MapsId("course_id")
private Course course;
}
#Embeddable
#Data
public class RegistrationPK implements Serializable {
private static final long serialVersionUID = 1L;
private Integer course_id;
private Integer participant_id;
}
Is your basic Entities. The RegistrationRepository needs an additional query.
public interface RegistrationRepository extends JpaRepository<Registration, RegistrationPK> {
Set<Registration> findByCourse(Course c);
}
And to use all this in an example:
#Override
public void run(String... args) throws Exception {
create();
Course c = courseRepo.getOne(1);
Set<Registration> rs = read(c);
System.out.println(rs);
deleteCourse(c);
}
private void create() {
Course c1 = new Course();
c1.setId(1);
c1.setName("c1");
courseRepo.save(c1);
Participant p1 = new Participant();
p1.setId(1);
p1.setName("p1");
participantRepo.save(p1);
Registration r1 = new Registration();
r1.setId(new RegistrationPK());
r1.setCourse(c1);
r1.setParticipant(p1);
registrationRepo.save(r1);
}
private Set<Registration> read(Course c) {
return registrationRepo.findByCourse(c);
}
private void deleteCourse(Course c) {
registrationRepo.deleteAll( registrationRepo.findByCourse(c) );
courseRepo.delete(c);
}

OK solution was pretty simple.
I indeed need to remove the references from the deleteRegistration method. This is what I had tried but was causing failed to lazily initialize a collection of role exception :
#Command
public void deleteRegistration(Registration reg) {
reg.getCourse().getRegistrations().remove(reg);
reg.getParticipant.getRegistrations().remove(reg);
registrationMgr.delete(reg);
}
The trick is that I also have to save the course entity before trying to delete the registration.
This works :
#Command
public void deleteRegistration(Registration reg) {
// remove reference from course, else delete does nothing
Course c = getRegistration().getCourse();
c.getRegistrations().remove(getRegistration());
courseMgr.saveOrUpdate(c);
// delete registration from the database
registrationMgr.delete(reg);
}
No need to remove reference from participant...
#PreRemove was doing the job, but that way I can now also delete a course without triggering the ConcurrentModificationException.

Related

Java listen on entity's child collection change

I'm trying to detect if entity's relation is updated, but nothing that I've tried or found is working.
What I have for short is like:
#Entity
#EntityListeners(KidsListener.class)
#Table(name = "user")
public class User {
#Setter(AccessLevel.PRIVATE)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Access(AccessType.PROPERTY)
private Long id;
#Setter(AccessLevel.NONE)
#OneToMany(fetch = FetchType.LAZY, mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Kid> kids = new HashSet<>(0);
#Version
#Column(name = "version")
private Long version;
}
I've tried both solutions - EntityListeners and implementing PostCollectionUpdateEventListener
public class KidsListener implements PostCollectionUpdateEventListener {
#PostPersist
#PostUpdate
private void afterAnyUpdate(User user) {
// do something
}
#Override
public void onPostUpdateCollection(PostCollectionUpdateEvent event) {
PersistentCollection collection = event.getCollection(); // do something
}
}
Nothing of those works, even version for parent User entity isn't changing.
The save part is just something like:
User user = userRepository.findById(id);
for(Kid kid : mappedFromDto.getKids()){
user.getKids().add(kid);
}
userRepository.save(user);
I can't listen on Kid change because I want to send out (in one request) a pack of all changes in User's kids.
Sorry, but I have to ask. Did you register the listener?
public class CustomIntegrator implements Integrator {
public void integrate(
Metadata metadata,
SessionFactoryImplementor sessionFactory,
SessionFactoryServiceRegistry serviceRegistry) {
listenerRegistry.appendListeners(
EventType.POST_COLLECTION_UPDATE,
new KidsListener()
);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
}
}
You also have to create a META-INF/services/org.hibernate.integrator.spi.Integrator file that contains the fully qualified name of the integrator class.
After that, you should receive all events.
Please note that there are other events as well, like POST_COLLECTION_REMOVE and POST_COLLECTION_RECREATE that you might have to listen for as well to capture inserts/deletes.

LazyInitializationException Spring Boot

I know there are a lot of similar threads out there but i just can't figure it out from those threads on how to overcome this problem.
I have 3 classes Car, Brand, Color.
A Car has just one Brand and a list of Colors.
Brand has a List of Cars.
Color does not have any relation.
Getters, Setters, ToString and Constructors are not provided for simplicity sake.
I'm able to save objects into database and database is already populated.
--------------------------------------------------------------------------------
#Entity
#Table(catalog = "spring_project")
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String model;
#ManyToMany(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable( name = "car_color", catalog = "spring_project",
joinColumns = { #JoinColumn(name = "car_id") },
inverseJoinColumns = { #JoinColumn(name = "colors_id") }
)
private List<Color> colors = new ArrayList<>();
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;
--------------------------------------------------------------------------------
#Entity
#Table(catalog = "spring_project")
public class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "brand", fetch = FetchType.LAZY)
private List<Car> cars = new ArrayList<>();
--------------------------------------------------------------------------------
#Entity
#Table(catalog = "spring_project")
public class Color {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
--------------------------------------------------------------------------------
Everything runs just fine if i fetch like Eager, but i know it is a bad practice and it should be used Lazy loading instead. But i keep getting the LazyInitializationException.
I understand from the error that a session is required but i dont know how to provide one since im working with Spring Data JPA neither where i should declare one...
#SpringBootApplication
public class SrpingJpaApplication {
private static final Logger log =
LoggerFactory.getLogger(SrpingJpaApplication.class);
public static void main(String[] args) {
SpringApplication.run(SrpingJpaApplication.class, args);
}
#Bean
public CommandLineRunner demo(CarRepository carRepository,
ColorRepository colorRepository,
BrandRepository brandRepository) {
return (args) -> {
log.info("Reads all cars....");
for (Car c : carRepository.findAll()) {
System.out.println(c.toString());
}
};
}
}
Thank you so much.
Edited----->>>
The error is thrown on c.toString();
Error: Caused by: org.hibernate.LazyInitializationException: could not initialize
proxy [com.readiness.moita.SrpingJPA.Models.Brand#1] - no Session
The default for the #OneToMany annotation is FetchType.LAZY so your collections are loaded lazily.
In order to be able to access the collection after you've retrieved the object you need to be in a transactional context (you need an open session)
When you call:
carRepository.findAll();
internally a new session is created, the object is retrieved and as soon as the findAll method returns the session is closed.
What you should do is make sure you have an open session whenever you access the lazy collection in your Car object (which the toString does).
The simplest way is to have another service handle the car loading and annotate the showCars method with #Transactional the method is in another service because of the way AOP proxies are handled.
#Service
public CarService {
final CarRepository carRepository;
public CarService(CarRepository carRepository) {
this.carRepository = carRepository;
}
#Transactional
public void showCars(String... args) {
for (Car c : carRepository.findAll()) {
System.out.println(c.toString());
}
}
}
and then you call:
#Bean
public CommandLineRunner demo(CarService carService) {
return (args) -> service.showCars(args);
}
Because the FetchType of Brand is lazy, it will not automatically be loaded into the session with call to fetchAll(). To have it automatically load into the session, you need to:
Change
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;
to
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
Ex
#ManyToOne(cascade=CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="brand_id", referencedColumnName="id")
private Brand brand;
If you do not want to set the fetch type to eager, then you need to move your call to toString to a service method Ex
#Component
public CarService implements ICarService {
#Autowired
CarRepository carRepository;
#Transactional
public void printAllCars() {
for (Car c : carRepository.findAll()) {
System.out.println(c.toString());
}
}
}
The correct way to do this however would be to write a criteria query or hql

Failed to lazily initialize a collection of role during conversion of object to json

I am new to spring and while fetching records from a table having relationship with other tables getting this lazily initialling error.
I have read a lot online but not getting a appropriate approach.
Table1:
#SuppressWarnings("serial")
#Entity
public class Terminal extends BaseEntity {
#Column(length = 100, unique = true)
private String shortName;
#Column
private short number; // short stores up to 32767 value
#Column
private String description;
#OneToMany
#JoinColumn(name = "terminal_id", referencedColumnName = "uuid")
#Cascade({ CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DELETE })
private Set<BusinessHour> businessHour;
public String getShortName() {
return shortName;
}
public void setShortName(String shortName) {
this.shortName = shortName;
}
public short getNumber() {
return number;
}
public void setNumber(short number) {
this.number = number;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Set<BusinessHour> getBusinessHour() {
return businessHour;
}
public void setBusinessHour(Set<BusinessHour> businessHour) {
this.businessHour = businessHour;
}
Table2:
#SuppressWarnings("serial")
#Entity
public class BusinessHour extends BaseEntity {
#Column
private DayOfWeek dayOfWeek;
#Column
private LocalTime startOfOperation;
#Column
private LocalTime endOfOperation;
public DayOfWeek getDayOfWeek() {
return dayOfWeek;
}
}
Service Code:
#Service
public class TerminalServiceImpl implements TerminalService {
#Autowired
TerminalRepository terminalRepository;
Iterable<Terminal> allTerminals = terminalRepository.findAll();
List<Terminal> terminalList = new ArrayList<Terminal>();
for (Terminal terminal : allTerminals) {
terminalList.add(terminal);
}
return terminalList;
}
Terminal Repository code:
#Transactional
public interface TerminalRepository extends CrudRepository<Terminal, Long> {
}
Code where i got error during debug:
private List<Terminal> updateTerminalList() {
List<Terminal> allTerminals = terminalService.fetchAllTerminal();
return allTerminals;
}
public void terminalWrapperRun() {
try {
Payload payload = createTerminalPayload(applicationId);
String json3 = object2Json(payload);
kafkaRESTUtils.sendServerPayload(json3);
} catch (Exception e1) {
e1.printStackTrace();
}
}
public String object2Json(Object dataArray) throws JsonProcessingException {
return mapper.writeValueAsString(dataArray);
}
Error:
com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role: terminal.model.Terminal.businessHour, could not initialize proxy - no Session (through reference chain:
Getting exception while converting fetching object to json. which i found due to proxy object return due to fetch type lazy(which i want to kept as it is).
I believe this issue relates to the LAZY loading of Collections by default by your ORM.
#OneToMany
#JoinColumn(name = "terminal_id", referencedColumnName = "uuid")
#Cascade({ CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DELETE })
private Set<BusinessHour> businessHour;
The #OneToMany annotation has a fetch property which is set to LAZY by default.
FetchType fetch() default LAZY;
OneToMany reference
This means that it will only be loaded when the data is accessed. In the case of your example, this will happen when you try to create the JSON string. By this point, however, you are outside the scope of the ORM session so it does not know how to load the data.
Therefore you have 2 options.
Change your annotation to eagerly load the data (which means the BusinessHour Set will be loaded at the same time as the parent object
#OneToMany(fetch = FetchType.EAGER)
perform your JSON generation within an ORM session (I would only really recommend doing this is the first option causes performance issues).
If I recall correctly this is the kind of error caused by an Entity being detached from the EntityManager at the time of its use (being it a Proxy it cannot perform a database query to retrive the data).
You can use:
#OneToMany(fetch = FetchType.EAGER)
...
private Set<BusinessHour> businessHour;
Using FetchType=EAGER means that any query against your entity will load the whole bunch of annotated entities.
Imho, this is only a sensible action if you are 100% sure that your entity will only be used for your special business case.
In all other cases - like programming a data acess as a library, or accepting different kinds of queries on your entities, you should use entity graphs (https://docs.oracle.com/javaee/7/tutorial/persistence-entitygraphs002.htm) or explicit loading (Hibernate.initialize, join-fetch, see for example https://vladmihalcea.com/hibernate-facts-the-importance-of-fetch-strategy/).
If your use case only is the transformation, you have two good options:
Transform your entity to JSON within a Transactional method (as PillHead suggested)
Load your entity explicitly with all the entities needed (via entity graphs or Hibernate.initialize) within the transaction, and then convert to JSON where you need it.

Persist a collection of data to a lazy collection in Spring Framework

I'm newbie in Spring. The picture above shows my db schema:
I need to download a new real time data for each station each minute and I have to assign it to that station. I would like to manage to:
persist
without the use of FetchType.EAGER (which resulted to be too much slow for this purpose)
persist in a single time (i.e a single communication with db, without iterating on a list the persist function on each single RealTimeData) the collection of RealTimeData collected in the previous minute.
This is what I would like to do, if I should take a completely different way for doing something like that any advice is appreciated.
The station class is this:
#RooJavaBean
#RooToString
#RooJpaActiveRecord(finders = { "findStationsByNum" })
#RooJson
public class Station {
private String address;
private String name;
private Integer num;
#ManyToOne(cascade = CascadeType.ALL)
private Location location;
#ManyToOne
private City city;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "station", fetch=FetchType.LAZY)
private Set<RealTimeData> real_time_data = new HashSet<RealTimeData>();
}
And the RealTimeData class is this:
#RooJavaBean
#RooToString
#RooJpaActiveRecord(finders = { "findRealTimeDatasByCollect_dateEquals" })
#RooJson
public class RealTimeData {
private Integer available_bike_stands;
/* ... ... ... */
#Temporal(TemporalType.TIMESTAMP)
#DateTimeFormat(style = "M-")
private Date collect_date;
#ManyToOne
private Station station;
}
You can read the details of the solution here:
http://forum.spring.io/forum/spring-projects/roo/106714-bulk-operations-with-spring-roo
However synthesizing it suggests to use this:
#PersistenceContext
private EntityManager entityManager;
#Transactional
public void storeList(List<MyEntity> entities) {
int imported = 0;
for (MyEntity e: entities) {
entityManager.persist(e);
if (++imported % 50 == 0) {
entityManager.flush();
entityManager.clear();
}
}
}
Or if you are using Spring Roo this:
#Transactional
public void storeList(List<MyEntity> entities) {
int imported = 0;
for (MyEntity e: entities) {
e.persist();
if (++imported % 50 == 0) {
e.flush();
e.clear();
}
}
}
Setting in the property of your persistence.xml:
<property name="hibernate.jdbc.batch_size" value="50"/>

JPA ManyToMany deleting

I have two classes:
public class Keyword {
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "keywords")
private Set<Thesis> theses = new HashSet<Thesis>();
}
public class Thesis {
#ManyToMany(cascade = CascadeType.ALL)
private Set<Keyword> keywords = new HashSet<Keyword>();
}
Now I want to be able to delete a keyword and delete a thesis without associeted objects. How shopuld I do that? I tried with #OnDelete(action=OnDeleteAction.NO_ACTION), did not work, I tried with
#PreRemove
void onPreRemove() {
this.getTheses().clear();
this.persist();
this.flush();
}
and didn't sucess. What's the correct way to do it?
Ok, seams my solution to set the Set<> to null was not enought. This way it seams to work:
#PreRemove
void onPreRemove() {
log.debug("in preRemove");
//this.getTheses().clear();
for (Thesis s : this.getTheses()) {
s.getKeywords().remove(this);
s.persist();
s.flush();
}
this.persist();
this.flush();
entityManager.flush();
}

Categories