Save with Spring data jpa save method in Many to Many - java

There is a many-to-many relationship between students and an edition of a course, which would be the enrollment in that course. This relationship can be seen in the EditionEnrollment class.
public class EditionEnrollment {
#EmbeddedId
private Id id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "student_id", insertable = false, updatable = false)
protected Student student;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "edition_id", insertable = false, updatable = false)
protected E edition;
#ColumnDefault("false")
#Column(nullable = false)
protected Boolean isDropped = false;
public EditionEnrollment() {
}
public EditionEnrollment(Student student, E edition) {
this(student, edition, false);
}
public EditionEnrollment(Student student, E edition, Boolean isDropped) {
id = new Id(student.getId(), edition.getId());
this.student = student;
this.edition = edition;
this.isDropped = isDropped;
}
// methods
#Embeddable
public static class Id implements Serializable {
#Column(name = "student_id")
private Long studentId;
#Column(name = "edition_id")
private Long editionId;
public Id() {
}
public Id(Long studentId, Long editionId) {
this.studentId = studentId;
this.editionId = editionId;
}
//methods
}
Repository
public interface EditionEnrollmentRepository extends JpaRepository<EditionEnrollment, EditionEnrollment.Id> {}
Service
#Service
public record EditionEnrollmentService(CourseEnrollmentRepository repository) {
public void save(CourseEnrollment courseEnrollment) {
repository.save(courseEnrollment);
}
public Optional<CourseEnrollment> findById(CourseEnrollment.Id id) {
return repository.findById(id);
}
// other methods
}
There is a list of students to be enrolled, but it is possible that some of them are already enrolled before and they cannot be enrolled again. My solution to this problem was to ask if said enrollment existed and in that case, then I did not call the save method.
AtomicInteger count = new AtomicInteger();
AtomicInteger saved = new AtomicInteger();
selectedStudents.getListDataView().getItems().forEach(student -> {
count.getAndIncrement();
CourseEnrollment ce = new CourseEnrollment(student, edition);
if (courseEnrollmentService.findById(ce.getId()).isEmpty()) {
courseEnrollmentService.save(ce);
saved.getAndIncrement();
}
});
But with this solution, although it works, a query is executed twice when the student has not been enrolled, the one of the findById of the if and another that executes the save method
select ...
from course_enrollment courseenro0_
where courseenro0_.edition_id=?
and courseenro0_.student_id =?;
select ...
from course_enrollment courseenro0_
where courseenro0_.edition_id=?
and courseenro0_.student_id =?;
insert
into course_enrollment (is_dropped, evaluation_id, edition_id, student_id)
values (?, ?, ?, ?)
How could I improve this?

Related

Spring Data JPA Many to Many Service Repository Problem

I am trying to add ManyToMany entity to my application. I created entity but cannot implement it.
Actor class
#Entity
#Table(name = "actor")
public class Actor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(nullable = false, name = "actor_name")
private String actorName;
#ManyToMany(mappedBy = "actor", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<Movie> movie = new HashSet<Movie>();
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getActorName() { return actorName; }
public void setActorName(String actorName) {
this.actorName = actorName;
}
public Set<Movie> getMovie() {
return movie;
}
public void setMovie(Set<Movie> movie) {
this.movie = movie;
}
}
In movie class I have
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "movie_actor",
joinColumns = {#JoinColumn(name = "movie_id")},
inverseJoinColumns = {#JoinColumn(name = "actor_id")}
)
Set<Actor> actor = new HashSet<Actor>();
........................
public Set<Actor> getActor () {
return actor;
}
public void setActor(Set<Actor> actor){
this.actor = actor;
}
I created my entity just like this but in MovieService;
Actor actor = ActorRepository.findByActorName(movie.getActor().getActorName());
movie.setActor(actor);
This part gives me error. movie.getActor().getActorName() method cannot find. Where do I need to look? In IDE it also says method getActorName and setActorName is never used. I am also adding my ActorRepository and ActorService to closer look to the problem.
ActorRepository
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorName(String actorName);
}
ActorService
#Service
public class ActorService {
private ActorRepository actorRepository;
#Autowired
public ActorService(ActorRepository actorRepository) {
this.actorRepository = actorRepository;
}
public List<Actor> getAllActor() {
return actorRepository.findAll();
}
}
After adding ManyToMany I was using is as OneToMany entity. Services is works for OneToMany. How can I use them for ManyToMany? I need to add multiple actors to my movies. I couldn't find MVC projects for ManyToMany implementation.
You're invoking movie.getActor().getActorName() which basically does a getActorName() on a Set<Actor> object.
You're basically treating the relation as a ManyToOne instead of a OneToMany
You could use the following to fetch the first Actor of the Set
ActorRepository.findByActorName(movie.getActors().iterator().next().getActorName());
But then of course, you don't have all your Actor's names
What you could do is the following
public interface ActorRepository extends JpaRepository<Actor, Integer> {
Set<Actor> findByActorNameIn(List<String> actorName);
}
And invoke it that way
ActorRepository.findByActorNameIn(
movie.getActors()
.stream()
.map(Actor::getName)
.collect(Collectors.toList())
);

JPA distinct inner join with Condition on joined table

I have two tables below:
#Entity
#Table(name="COLLEGE")
public class College {
private Long collegeId;
private List<Student> students;
#Id
#Column(name = "COLLEGE_ID")
public Long getCollegeId() {
return this.collegeId;
}
public void setCollegeId(final Long collegeId) {
this.collegeId= collegeId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "college")
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
public List<Student> getStudents() {
if (this.students== null) {
this.students= new ArrayList<>();
}
return this.students;
}
public void setStudents(List<Student> students){
this.students = students
}
}
#Entity
#Table(name="STUDENT")
public class Student {
private Long studentId;
private College college;
private String name;
private String department;
#Id
public Long getStudentId() {
return this.studentId;
}
public void setStudentId(Long studentId) {
this.studentId = studentId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "COLLEGE_ID")
public getCollege() {
return this.college;
}
public setCollege(College college) {
this.college = college;
}
......
......
}
I want to get the college object with list of students of a specific department. The SQL query is below:
select *
from COLLEGE c
inner join STUDENT s on c.COLLEGE_ID= s.collegeId
where c.COLLEGE_ID=12345 and s.DEPARTMENT="ENGINEERING";
So far I have tried the below JPA query but it is returning multiple College objects.
SELECT DISTINCT c
FROM COLLEGE c
INNER JOIN c.students s
where c.collegeId= :collegeid and s.department = :department
How to return a single college object with list of students with filtered department?
NOTE: I can't alter the entity objects used here.
Try to use this query, using JOIN FETCH instead INNER JOIN:
SELECT DISTINCT c FROM College c
JOIN FETCH c.students s
WHERE c.collegeId= :collegeid
AND s.department = :department

Many to Many Hibernate mapping with extra column

I've seen this question on here a few times, however none of the answers fix my issue.
I'm trying to deconstruct a many-to-many relationship down to seperate many-to-one and one-to-many entities so I can add additional columns. From what I have, the main entity saves to the database, but the intermediate does not. If anyone can figure out what's going on I would very much appreciate it. I tried doing this the other way with the primary key composite (aka: #AssociationOverride) but it also did not work. I've scowered the web but cannot find an answer to my issue here.
This is my main entity, MaintOrder:
#Entity
#Table(name="maint_orders")
public class MaintOrder extends PersistedObject implements java.io.Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy="maintOrder")
private Set<ManPowerLine> manPower = new HashSet<ManPowerLine>() ;
public void addManPower(ManPower manPower, Integer quantity, Float price) {
ManPowerLine mpLine = new ManPowerLine();
mpLine.setManPower(manPower);
mpLine.setMaintOrder(this);
mpLine.setManPowerID(manPower.getManPowerID());
mpLine.setMaintOrderID(this.getMaintOrderID());
mpLine.setQuantity(quantity);
mpLine.setPrice(price);
this.manPower.add(mpLine);
// Also add the association object to the employee.
manPower.getMaintOrder().add(mpLine);
}
... getters and setters
}
Here is my secondary entity, ManPower:
#Entity
#Table(name="man_power")
public class ManPower extends PersistedObject implements java.io.Serializable {
...id's, etc
#OneToMany(mappedBy="manPower", fetch = FetchType.EAGER)
private Set<ManPowerLine> maintOrder = new HashSet<ManPowerLine>();
public Set<ManPowerLine> getMaintOrder(){
return maintOrder;
}
public void setMaintOrder(Set<ManPowerLine> maintOrder){
this.maintOrder = maintOrder;
}
... other getters and setters
}
Here is my intermediate entity, ManPowerLine:
#Entity
#Table(name = "man_power_line")
#IdClass(ManPowerLineID.class)
public class ManPowerLine extends PersistedObject implements java.io.Serializable {
#Id
private Long maintOrderID;
#Id
private Long manPowerID;
#Column(name="quantity")
private Integer quantity;
#Column(name="price")
private Float price;
#ManyToOne
#JoinColumn(name = "maintOrderID", updatable = false, insertable = false, referencedColumnName = "maint_order_id")
private MaintOrder maintOrder;
#ManyToOne
#JoinColumn(name = "manPowerID", updatable = false, insertable = false, referencedColumnName = "man_power_id")
private ManPower manPower;
... other getters and setters
}
And my ID entity, ManPowerLineID:
public class ManPowerLineID implements java.io.Serializable {
private Long maintOrderID;
private Long manPowerID;
public Long getMaintOrderID(){
return maintOrderID;
}
public Long getManPowerID(){
return manPowerID;
}
public void setMaintOrderID(Long maintOrderID){
this.maintOrderID = maintOrderID;
}
public void setManPowerID(Long manPowerID){
this.manPowerID = manPowerID;
}
#Override
public int hashCode(){
return (int)(maintOrderID + manPowerID);
}
#Override
public boolean equals(Object obj) {
if( obj instanceof ManPowerLine){
ManPowerLineID otherID = (ManPowerLineID)obj;
boolean hey = (otherID.maintOrderID == this.maintOrderID) && (otherID.manPowerID == this.manPowerID);
return hey;
}
return false;
}
}
Finally the code which utilizes this is as follows:
private void insertObject( ) {
ServiceLocator locator = new ServiceLocator();
SessionFactory sf = locator.getHibernateSessionFactory();
Session sess = sf.openSession();
Transaction tx = sess.beginTransaction();
MaintOrder m = new MaintOrder();
... various setters to m
Set manPowerSet = new HashSet();
for(int i = 0; i < manPowerSet.size(); i++){
ManPower mp = new ManPower();
mp = (ManPower) manPowerSet.iterator().next();
m.addManPower(mp, quantity, cost);
}
sess.saveOrUpdate(m);
tx.commit();
sess.close();
}
Is it possible that I need to use more then just m.addManPower to add to the line? I've tried adding m.setManPowerLine, but it does not change the result.
Anyways I know its a lot of code to look at, but thanks in advance.
Turns out I fixed my own issue on this one. The problem was that I didn't set cascade = CascadeType.ALL, in ALL the right places. Specifically Here:
#OneToMany(mappedBy="manPower", fetch = FetchType.EAGER)
private List<ManPowerLine> maintOrder = new ArrayList<ManPowerLine>();
Should be:
#OneToMany(mappedBy="manPower", cascade = CascadeType.All)
private List<ManPowerLine> maintOrder = new ArrayList<ManPowerLine>();

Is it considered a best practice to synchronize redundant column properties with associations in JPA 1.0 #IdClass implementations?

Consider the following table:
CREATE TABLE Participations
(
roster_id INTEGER NOT NULL,
round_id INTEGER NOT NULL,
ordinal_nbr SMALLINT NOT NULL ,
was_withdrawn BOOLEAN NOT NULL,
PRIMARY KEY (roster_id, round_id, ordinal_nbr),
CONSTRAINT participations_rosters_fk FOREIGN KEY (roster_id) REFERENCES Rosters (id),
CONSTRAINT participations_groups_fk FOREIGN KEY (round_id, ordinal_nbr) REFERENCES Groups (round_id , ordinal_nbr)
)
Here the JPA 1.0 #IdClass entity class:
#Entity
#Table(name = "Participations")
#IdClass(value = ParticipationId.class)
public class Participation implements Serializable
{
#Id
#Column(name = "roster_id", insertable = false, updatable = false)
private Integer rosterId;
#Id
#Column(name = "round_id", insertable = false, updatable = false)
private Integer roundId;
#Id
#Column(name = "ordinal_nbr", insertable = false, updatable = false)
private Integer ordinalNbr;
#Column(name = "was_withdrawn")
private Boolean wasWithdrawn;
#ManyToOne
#JoinColumn(name = "roster_id", referencedColumnName = "id")
private Roster roster = null;
#ManyToOne
#JoinColumns(value = {#JoinColumn(name = "round_id", referencedColumnName = "round_id"), #JoinColumn(name = "ordinal_nbr", referencedColumnName = "ordinal_nbr")})
private Group group = null;
public Participation()
{
}
public Integer getRosterId()
{
return rosterId;
}
public void setRosterId(Integer rosterId)
{
this.rosterId = rosterId;
}
public Integer getRoundId()
{
return roundId;
}
public void setRoundId(Integer roundId)
{
this.roundId = roundId;
}
public Integer getOrdinalNbr()
{
return ordinalNbr;
}
public void setOrdinalNbr(Integer ordinalNbr)
{
this.ordinalNbr = ordinalNbr;
}
public Boolean getWasWithdrawn()
{
return wasWithdrawn;
}
public void setWasWithdrawn(Boolean wasWithdrawn)
{
this.wasWithdrawn = wasWithdrawn;
}
public Roster getRoster()
{
return roster;
}
// ???
public void setRoster(Roster roster)
{
this.roster = roster;
}
public Group getGroup()
{
return group;
}
// ???
public void setGroup(Group group)
{
this.group = group;
}
...
}
In general, should the association setters synchronize with the redundant fields, here rosterId, roundId, and ordinalNbr?:
// ???
public void setGroup(Group group)
{
this.group = group;
this.roundId = group.getRoundId();
this.ordinalNbr = group.getOrdinalNbr();
}
Thanks
Yes, they should be kept in synch. Although because they are part of the Id you should never be changing these, so it is really only an issue for new objects.
If you do not keep them in synch, then for a new object they will be null/0, which is probably not good. There is no magic in JPA that will keep these in synch for you.
If you read the object from the database, then they will be in synch of coarse, but you are responsible for maintaining your object's state once in memory, including both duplicate fields, and bi-directional mappings.
If you are using JPA 2.0, why bother having the duplicate Ids at all. You can remove the routersId and the roundId and just add the #Id to the #ManyToOnes.

EJB3 - handling non-standard link tables

I have a situation where I am working with EJB3 and a legacy database. I have a situation where there is a many-to-many relationship between two tables A and B, defined through a third (link) table L.
The complication is that the link table has other fields in it other than the PK's of tables A and B. The columns are standard timestamp and user columns to record who generated the link. These two additional columns are preventing me from defining the many-many relationship using a join table annotation, as they are not nillable and so must be populated.
Does anyone know of a way around this limitation? I could define One-to-many relationships from the link table to each of the other tables in the relationship, but this is not very elegant.
Thanks,
Yes, it is but you need to make it elegant. The following super-class can be used to define arbitrary many-to-many relationship as an entity:
#MappedSuperclass
public abstract class ModelBaseRelationship {
#Embeddable
public static class Id implements Serializable {
public Long entityId1;
public Long entityId2;
#Column(name = "ENTITY1_ID")
public Long getEntityId1() {
return entityId1;
}
#Column(name = "ENTITY2_ID")
public Long getEntityId2() {
return entityId2;
}
public Id() {
}
public Id(Long entityId1, Long entityId2) {
this.entityId1 = entityId1;
this.entityId2 = entityId2;
}
#Override
public boolean equals(Object other) {
if (other == null)
return false;
if (this == other)
return true;
if (!(other instanceof Id))
return false;
final Id that = (Id) other;
return new EqualsBuilder().append(this.entityId1, that.getEntityId1()).append(this.entityId1, that.getEntityId2()).isEquals();
}
#Override
public int hashCode() {
return new HashCodeBuilder(11, 111).append(this.entityId1).append(this.entityId2).toHashCode();
}
protected void setEntityId1(Long theEntityId1) {
entityId1 = theEntityId1;
}
protected void setEntityId2(Long theEntityId2) {
entityId2 = theEntityId2;
}
}
protected Id id = new Id();
public ModelBaseRelationship() {
super();
}
public ModelBaseRelationship(ModelBaseEntity entity1, ModelBaseEntity entity2) {
this();
this.id.entityId1 = entity1.getId();
this.id.entityId2 = entity2.getId();
setVersion(0);
}
#EmbeddedId
public Id getId() {
return id;
}
protected void setId(Id theId) {
id = theId;
}
}
The example of entity based on this super class (fragment):
#Entity(name = "myRealEntity")
#Table(name = "REAL_TABLE_NAME", uniqueConstraints = { #UniqueConstraint(columnNames = {
"FIRST_FK_ID", "SECOND_FK_ID" }) })
#AttributeOverrides( {
#AttributeOverride(name = "entityId1", column = #Column(name = "FIRST_FK_ID")),
#AttributeOverride(name = "entityId2", column = #Column(name = "SECOND_FK_ID"))
})
public class ModelBaseRelationshipReferenceImpl extends ModelBaseRelationship {
private Entity1OfManyToManyRelationship entity1;
private Entity2OfManyToManyRelationship entity2;
...
#ManyToOne
#JoinColumn(name = "FIRST_FK_ID", insertable = false, updatable = false)
public Entity1OfManyToManyRelationship getEntity1OfManyToManyRelationship() {
return entity1;
}
#ManyToOne
#JoinColumn(name = "SECOND_FK_ID", insertable = false, updatable = false)
public Entity2OfManyToManyRelationship getEntity2OfManyToManyRelationship () {
return entity2;
}
...
}

Categories

Resources