I'm trying to persist an ObservableList with JPA using the eclipselink API Version 2.6 with derby. JPA needs POJOs to persist. For JavaFX-Properties I can use this:
#Transient // avoid persisting
private StringProperty street = new SimpleStringProperty();
#Access(AccessType.PROPERTY) //
public String getStreet() {
return street.get();
}
public void setStreet(final String newStreet) {
street.set(newStreet);
}
Works fine!
Well, the first guess is to use it in the same way for an ObservableList. My first try was to use a normal List:
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Address> adrList;
public List<Address> getAdrList() {
return adrList;
}
public void setAdrList(final List<Address> adrList) {
this.adrList = adrList;
}
Works well! Let's change the List to an ObservableList. According to the StringProperty, I would use something like this:
private ObservableList<Address> adrList;
#Access(AccessType.PROPERTY)
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public List<Address> getAdrList() {
return adrList;
}
public void setAdrList(final List<Address> adrList) {
this.adrList.setAll(adrList);
}
This works until the point I access the adrList. I get a NullPointerException cause the adrList is Null.
My Question is: How can I use my ObervableList with JPA? I thought I could cast the ObservableList to a List as long it's just an Implementation of List. But It seems not to work. Has anyone an idea how to solve this problem?
Thanks in advance! :)
Related
Lets say I have an entity like this,
#Entity(name = "Post")
#Table(name = "post")
public class Post {
#Id
#GeneratedValue
private Long id;
private String title;
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
FetchType.LAZY
)
#JsonManagedReference
private List<PostComment> comments = new ArrayList<>();
#OneToOne(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY
)
private PostDetails details;
}
I want clone this entity, modified a few fields and then persist the new cloned entity in the database. What's the best approach to achieve this?
You can use a copy using constructor or the builder pattern, or to set the properties using simple setters. Then, persist the new entity instance using the EntityManager's persist() method. To avoid issues with duplicated id values, you must not copy the id field and instead let the JPA generate a new id when the entity is persisted.
Code example
public class Post {
// constructor, getters and setters are omitted
public Post(Post post) {
//Don’t copy id!
this.title = post.getTitle();
this.comments = new ArrayList<>(post.getComments());
this.details = new PostDetails(post.getDetails());
}
}
public class PostDetails {
// constructor, getters and setters are omitted
public PostDetails(PostDetails details) {
// Don’t copy id!
this.description = details.getDescription();
// copy other fields as needed
}
}
// Usage
public class PostsRepository{
#Autowired
EntityManager em;
public void saveCopy(Post originalPost){
Post clonedPost = new Post(originalPost);
em.persist(clonedPost);
}
}
Also I would highly recommend you to write tests in order to check that you are doing everything fine. Moreover you will more space for experiments.
i'm trying to get Jsonb objects from my DB. When try getting all Lessons from the DB im getting an error
Schwerwiegend: Generating incomplete JSON
Warnung: StandardWrapperValve[rest.ApplicationConfig]: Servlet.service() for servlet rest.ApplicationConfig threw exception
java.lang.StackOverflowError
I figuered out that its because of the way my Class is build. My Lesson class has a List of questions showns below
#Entity
public class Lesson extends EntityWithIntId {
private String lessonName;
#OneToMany(mappedBy="lessonID",cascade = CascadeType.ALL)
private List<Question> questions = new ArrayList<>();
public Lesson() {
}
public Lesson(String lessonName) {
this.lessonName = lessonName;
}
public void addQuestion(Question question) {
this.questions.add(question);
}
// getter setter
}
Question Class:
#Entity
public class Question extends EntityWithIntId {
#ManyToOne
#JoinColumn(name = "lessonID")
private Lesson lessonID;
private String question;
#ElementCollection
#MapKeyColumn(name = "answer")
#Column(name = "solution")
#CollectionTable(name = "answers", joinColumns = #JoinColumn(name = "questionID"))
Map<String, Integer> answers = new LinkedHashMap<>();
public Question() {
}
public Question(Lesson lessonID) {
this.lessonID = lessonID;
}
public Question(String question) {
this.question = question;
}
public Question(Lesson lessonID, String question) {
this.lessonID = lessonID;
this.question = question;
}
public void addAnswer(String answer) {
this.answers.put(answer, 0);
}
// getter setter
}
So i tested a bit and noticed that if i build the connection bewteen Lesson and Question like this:
#Entity
public class Lesson extends EntityWithIntId {
private String lessonName;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "lessonID")
private List<Question> questions = new ArrayList<>();
}
#Entity
public class Question extends EntityWithIntId {
private int lessonID;
private String question;
#ElementCollection
#MapKeyColumn(name = "answer")
#Column(name = "solution")
#CollectionTable(name = "answers", joinColumns = #JoinColumn(name = "questionID"))
Map<String, Integer> answers = new LinkedHashMap<>();
}
Everything works as expected and i get my Json Object. The issue is that i cant change my whole DB structure and my programm only for this. I also have to use JsonB so there is no other choice.
There has to be a way to get a proper Json object from my current DB.
Here is the #Get method:
#GET
#Produces({MediaType.APPLICATION_JSON})
public Response getAllLesssons() {
if (super.findAll() != null) {
return Response.ok(super.findAll()).build();
} else {
String message = "Lessons not found!";
return Response.ok().type(MediaType.TEXT_PLAIN).entity(message).build();
}
}
Anyone knows how to solve this problem?
So i solved this issue by removing methods in my Entities which start with get.... and are not returning a Class variable. For examle getLessonById() or something like that. I guess JSON sees them as class variable returner and wants to get data from it. Also Bidirectional relations are causing endless loops so either you use specific annotations to ignore those or you just avoid them completly.
You can use annotation #JsonbTransient (javax.json.bind.annotation.JsonbTransient) to prevent serialization filds causes circle in the object graph.
I have two entities with fields that I´d like to localize. However, I´m not sure how to implement that correctly, because I would need to have a reference to the entities as well as a reference to the field that is translated, in order to have a shared "i18n" table.
#Entity
public class EntityA {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> description;
}
Second entity
#Entity
public class EntityB {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Translation> shortDescription;
}
Translation Entity
#Entity
#Table(name = "i18n")
public class Translation {
private String languageCode;
private String translation;
//private String referenceToEntity
//private String referenceToField
}
Is there a given way to enable internationalization on entity fields in Spring or at least some kind of workaround to make it working without too much overhead?
EDIT
I´ve written a short post about how I solved it using XmlAnyAttribute https://overflowed.dev/blog/dynamical-xml-attributes-with-jaxb/
I did some research and found this #Convert JPA annotation. You would need to encapsulate the name and description properties into an object (that implements AttributeConverter), and use a convertion class to specify how it will be translated when persisted, and how will it be translated when retreived.
To execute translations on persistence and retrieval, you can consume Google translate's API.
Here:
#Entity
public class EntityA {
#Convert(converter = DescriptionConverter.class)
private Description description
// getters and setters
},
The encapsulated object, something like:
public class Description {
private String name;
private String language;
private String description;
// Getters and Setters.
}
And the translation applies here:
#Converter
public class DescriptionConverter implements AttributeConverter<Description, String> {
#Override
public String convertToDatabaseColumn(Description description) {
// consume Google API to persist.
}
#Override
public Document convertToEntityAttribute(String description) {
// consume Google API to retrieve.
}
}
this tutorial helped me a lot. i hope it will help you too. i used the second way and it's work perfectly.Localized Data – How to Map It With Hibernate
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.
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();
}