Play Framework: JPA one-to-many relationship between three entities? - sql

I am building a web application using the play framework and I am running into problems with creating the database model using this schema
**Client** – (many to many) – **Events**
\ /
(one to many) (one to many)
\ /
**COMMENTS**
I am able to create and add the relationships
Event event = new Event("Party");
Client client = new Client("test#gmail.com","fname","lname","street","phonenum");
Comment comment = new Comment("test comment!");
//Add relationships
client.addEvent(event);
event.addClient(client);
event.addComment(comment);
comment.addEvent(event);
event.addClient(client);
client.addComment(comment);
But when I try to persist the data
client.save();
event.save();
comment.save();
I receive the error
[error] Test CommentModelTest.testClientEventCommentRelationship failed:
javax.persistence.PersistenceException: org.h2.jdbc.JdbcSQLException: Unique index or primary
key violation: "PRIMARY_KEY_4A ON PUBLIC.EVENT_CLIENT(EVENT_ID, CLIENT_EMAIL)"; SQL statement:
[error] insert into event_client (client_email, event_id) values (?, ?) [23505-172]
The class definitions are shown below, I would really appreciate it if someone could help me solve this issue. Am I defining the relationship incorrectly? Thanks in advance!
Client Class:
#Entity
public class Client extends Model {
#Id
public String email;
#Required
public String firstName;
#Required
public String lastName;
public String homeAddress;
public String phoneNumber;
#ManyToMany(mappedBy="clients", cascade=CascadeType.ALL)
public List<Event> events = new ArrayList<Event>();
#OneToMany(mappedBy="client", cascade=CascadeType.ALL)
public List<Comment> comments = new ArrayList<Comment>();
Event Class:
#Entity
public class Event extends Model {
#Id
public Long id;
#Required
public String eventName;
#ManyToMany(cascade=CascadeType.ALL)
public List<Client> clients = new ArrayList<Client>();
#OneToMany(mappedBy="event", cascade=CascadeType.ALL)
public List<Comment> comments = new ArrayList<Comment>();
Comment Class:
#Entity
public class Comment extends Model {
#Id
public Long id;
#Required
public String message;
#ManyToOne(cascade=CascadeType.PERSIST)
public Client client;
#ManyToOne(cascade=CascadeType.PERSIST)
public Event event;
---Edit:-------------------------------
I realized where I went wrong. In case you are running into a similar error, check to see if you are using the models correctly! In my JUnit test I tried adding the same client to an event twice, causing the primary key (email) to be duplicated.
the corrected relationship code should be as follows:
client.addEvent(event);
event.addClient(client);
event.addComment(comment);
comment.addEvent(event);
comment.addClient(client);
client.addComment(comment);

The exception says that you are trying to add the same relationship twice.
"PRIMARY_KEY_4A ON PUBLIC.EVENT_CLIENT(EVENT_ID, CLIENT_EMAIL)"
I guess this is on the table between the events and the clients, so it maps the ManyToMany relationship.
client.addEvent(event);
HERE --> event.addClient(client);
event.addComment(comment);
comment.addEvent(event);
HERE --> event.addClient(client);
client.addComment(comment);
It's added twice and there is a unique index that prevents that.

Related

Optaplanner: problems with InverseRelationShadowVariable

have a many-1 relationship pupil-formGroup: pupils are assigned to a formGroup and a formGroup can contain many pupils. I have attempted to implement an InverseRelationShadowVariable having watched your video/tutorial https://www.youtube.com/watch?v=ENKHGBMDaCM (which does not quite correspond with the latest optaplanner documentation I realise)
FormGroup extracts
#Entity
#PlanningEntity
public class FormGroup {
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
Pupil extracts
#Entity
#PlanningEntity
public class Pupil
#PlanningVariable(valueRangeProviderRefs = "formGroupRange")
#ManyToOne
private FormGroup formGroup;
Config extracts
<solutionClass>org.acme.optaplanner.domain.Plan</solutionClass>
<entityClass>org.acme.optaplanner.domain.Pupil</entityClass>
<entityClass>org.acme.optaplanner.domain.FormGroup</entityClass>
I believe I've followed the steps in the videoexactly (don't we all say that) but at solve time I get hundreds of errors... Repetitions of the following
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:728)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:774)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:178)
Any hint gratefully received...
The InverseRelationShadowVariable creates a bi-directional relationship between the genuine planning entity (Pupil) and the planning value (FormGroup). This may become problematic if you re-use your planning domain classes for other purposes, such as ORM persistence or serialization.
In this case, Jackson is unable to serialize Pupil, because it references a FormGroup, which has a List containing a reference back to that Pupil. See? An endless loop.
Solve this issue by adding the #JsonIgnore annotation on top of your inverse relation property and breaking that loop for Jackson:
#Entity
#PlanningEntity
public class FormGroup {
#JsonIgnore // THIS IS THE FIX
#InverseRelationShadowVariable(sourceVariableName = "formGroup")
#OneToMany(mappedBy = "formGroup", fetch = FetchType.EAGER)
private List<Pupil> pupilList = new ArrayList<Pupil>();
public List<Pupil> getPupilList() {
return pupilList;
}
public Integer getPupilCount() {
return pupilList.size();
}
...

How to construct Spring Data repository query two Parameters with IN and same list?

This is my Entity:
#Data
#Entity
#IdClass(EtlJobExecutionTriggersId.class)
#Table(name = "ETL_JOB_EXEC_TRIGGERS")
public class EtlJobExecutionTriggers {
#Id private Long jobExecIdUs;
#Id private Long jobExecIdDs;
private LocalDate cobDate;
}
And here is the Composite Primary Key Class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Embeddable
#EqualsAndHashCode
public class EtlJobExecutionTriggersId implements Serializable {
private Long jobExecIdUs;
private Long jobExecIdDs;
}
And here is my Spring Repo:
public interface EtlJobExecTriggersRepo extends JpaRepository<EtlJobExecutionTriggers, EtlJobExecutionTriggersId> {
String SQL_ = "select o from EtlJobExecutionTriggers o where o.jobExecIdDs in (:ids) or o.jobExecIdUs in (:ids) order by o.jobExecIdUs, o.jobExecIdDs";
#Query(EtlJobExecTriggersRepo.SQL_)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInSQL(#Param("ids") List<Long> jobExecIdList);
}
The #Query works as expected, but I would like not to write any SQL and instead express the same Query using only Spring Data repository query.
I have tried the following (and other variants)
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List<Long> jobExecIdDsList)
But i keep getting errors when Booting. The above interface method yields the following exception for the OrderBy part:
org.springframework.data.mapping.PropertyReferenceException: No property jobExecIdDs found for type Long! Traversed path: EtlJobExecutionTriggers.jobExecIdUs.
So what am I doing wrong here? or is it not possible to express this particular query via Spring Data Repo query?
As I have written in my comment I fixed the Order by issue, but I am still unable to make it work with only one method parameter (List jobExecIdList)
When I make it with two (List jobExecIdDsList, List jobExecIdUsList)
Like this:
List<EtlJobExecutionTriggers> findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsAscJobExecIdDsAsc(List<Long> jobExecIdDsList, List<Long> jobExecIdUsList);
it actually works but I can't get to work with only one list, as in the #Query("....") method
I think using your own custom id generator conflicts with Spring Data Repository query.
// You shoud have two parameters in your method as below.
List findAllByJobExecIdDsInAndJobExecIdUsInOrderByJobExecIdUsJobExecIdDs(List jobExecIdDsList,List jobExecIdUsList);

Hibernate manual and auto-generated primary key

I am having a requirement where if the user enters value for the primary key, then I need to use that when creating an entity and if in case the user does not provide value, the primary key needs to be auto-generated like R00001, R0002 etc.I would like to know how I could achieve this and any guidance on that
Try to take advantage of the IdentifierGenerator interface and define an implementation of your own.
public class MyEntityIdGenerator implements IdentifierGenerator{
public Serializable generate(SessionImplementor session, Object object)
throws HibernateException {
MyEntity entity = (MyEntity)object;
if(entity.getId()==null){
Connection con = session.connection();
// retrieve next sequence val from database for example
return nextSeqValue;
}
}
}
Then add appropriate annotations on the id field in your entity:
#Id
#GenericGenerator(name="myCustomGen", strategy="com.example.MyEntityGenerator")
#GeneratedValue(generator="myCustomGen")

JPQL and Entities (java.lang.IllegalArgumentException)

I am creating a web application that needs a table of notifications to display to various users. For some reason the JPQL query I've written is throwing a java.lang.IllegalArgumentException. My application already has a transaction table, structured and queried using an identical approach (afaik), which works perfectly.
I have been shuffling the code around, changing variable names and character cases for hours trying to get this to work but I'm still getting the exception every time. Does anyone have any idea where I'm going wrong?
My NotificationEntity is as follows:
#Table(name="notificationentity")
#NamedQuery(name="fetch_user_notifications", query="SELECT n FROM NotificationEntity n WHERE n.notificationrecipient=:username")
#Entity
public class NotificationEntity implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#NotNull
String notificationSender;
#NotNull
String notificationrecipient;
... other fields + methods
}
The JPQL query is called from an EJB (NotificationStorageServiceBean) that implements an interface (NotificationStorageService) with the following method:
#Override
public synchronized List<NotificationEntity> getUserNotificationList(String username)
{
List notifications;
notifications = em.createNamedQuery("fetch_user_notifications").setParameter("notificationrecipient", username).getResultList();
return notifications;
}
And the EJB method is called from a CDI backing bean for my .xhtml UI, using the FacesContext's currently logged in user to provide the argument for these methods.
#EJB
NotificationStorageService notificationStore;
public List<NotificationEntity> getUserNotificationList()
{
return notificationStore.getUserNotificationList(this.currentUser);
}
The exact error I get is:
java.lang.IllegalArgumentException: You have attempted to set a parameter value using a name of notificationrecipient that does not exist in the query string SELECT n FROM NotificationEntity n WHERE n.notificationrecipient=:username.
The parameter name in a JPQL query starts with a colon. So just use
setParameter("username", username)

Best way to set transient attribute on entity instance after Metadata.create

I'm currently setting the result of a jpql query on a transient attribute of several instances of entities attached with composition using BeforeDetachEntityListener.
Since I'm also using Metadata.create to create them, I would like to be able to do the same operation after creating them. What's the best way to handle the situation?
You can set values at object creation time with #PostConstruct
public class MyEntity extends StandardEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "USER_ID")
protected User creator;
#PostConstruct
protected void init() {
setCreator(AppBeans.get(UserSessionSource.class).getUserSession().getUser());
}
}
More information about how to initialize data in entities can be found in the docs at 5.8.3.1 Entity Fields Initialization and 5.8.3 Assigning Initial Values