Spring boot - many to many association not removing join table data - spring

I have an issue with a many-to-many relation in Spring Boot. Code is as follows:
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
and
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE}, mappedBy = "tags", fetch = EAGER)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}
Of course, I have the task_tag table where, after inserting a tag in a task and saving that task, an entry appears. However, when I delete a tag (or clear them), the entries do not get deleted from the join table. This is the test:
#Test
void entityIntegration() {
Task task = taskRepo.save(...);
Tag tag1 = Tag.builder().name(randomString()).build();
Tag tag2 = Tag.builder().name(randomString()).build();
Tag tag3 = Tag.builder().name(randomString()).build();
Tag tag4 = Tag.builder().name(randomString()).build();
final List<Tag> allTags = Arrays.asList(tag1, tag2, tag3, tag4);
tagRepo.saveAll(allTags);
task.addTag(tag1);
taskRepo.save(task);
final Long task1Id = task.getId();
assertTrue(tag1.getTasks().stream().map(Task::getId).collect(Collectors.toList()).contains(task1Id));
task.clearTags();
task = taskRepo.save(task);
tag1 = tagRepo.save(tag1);
assertTrue(task.getTags().isEmpty());
assertTrue(tag1.getTasks().isEmpty());
task.addTags(allTags);
task = taskRepo.save(task); // FAILS, duplicate key ...
}
I delete tag1 but when I try to add it back to the task, I get
The task_tag table does have a composite index formed on those two (and only) columns.
What am I doing wrong? I followed each and every suggestion and advice - using set instead of lists, having helper methods, cleaning up etc...
I can't find the bug.
Thank you!

The biggest thing that sticks out to me is that your Tag's equals and hash-code aren't matched with each other.
Your "equals" drives equality based on the object's name being the same, which makes logical sense to the mantra of "A tag is a name". But the hash-code drives based on the "id" being equivalent and doesn't use the name at all.
Forgetting JPA/Hibernate for a moment, just plain old Collections themselves get very unpredictable when these two are out of synch.
You can read more about that here and specifically why a hash-code that doesn't match equality would end up hashing to the wrong bucket and resulting in confusing keeping it all straight in HashSets: Why do I need to override the equals and hashCode methods in Java?
There are many ways to put them back in synch (using libraries like Lombok and code-generation tools in your IDE come to mind), but instead of prescribing one, I will simply point to this web-resource that, conveniently, created a Tag with the exact same concept for his example, so I suspect you can just use this exact same pattern yourself.
https://vladmihalcea.com/the-best-way-to-use-the-manytomany-annotation-with-jpa-and-hibernate/
Here's another helpful SO thread I found that talks about relationships and identity/equals/hashCode as it impacts JPA: The JPA hashCode() / equals() dilemma

Kindly add DELETE keyword to the cascade property of many to many annotation . And i believe ur annotation for task property of Tag class should be changed as below .
You can give this below mapping a try
public class Task {
#Id
#GeneratedValue
private Long id;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")}
)
#Builder.Default
private Set<Tag> tags = new HashSet<>();
public void addTags(Collection<Tag> tags) {
tags.forEach(this::addTag);
}
public void addTag(Tag tag) {
this.tags.add(tag);
tag.getTasks().add(this);
}
public void removeTag(Tag tag) {
tags.remove(tag);
tag.getTasks().remove(this);
}
public void removeTags() {
for (Iterator<Tag> iterator = this.tags.iterator(); iterator.hasNext(); ) {
Tag tag = iterator.next();
tag.getTasks().remove(this);
iterator.remove();
}
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Task)) return false;
return id != null && id.equals(((Task) o).getId());
}
#Override
public int hashCode() {
return id.intVal();
}
}
public class Tag {
#Id
#GeneratedValue
private Long id;
#NotNull
#Column(unique = true)
private String name;
#ManyToMany(cascade = {PERSIST, MERGE,DELETE}, mappedBy = "tags", fetch = EAGER)
#JoinTable(
name = "task_tag",
joinColumns = {#JoinColumn(name = "tag_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "task_id", referencedColumnName = "id")}
)
#Builder.Default
private final Set<Task> tasks = new HashSet<>();
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Tag tag = (Tag) o;
return Objects.equals(name, tag.name);
}
#Override
public int hashCode() {
return id.intVal();
}
}

Related

Spring Data JPA transient field returns wrong value

I have two models: Deck and Word with a one-to-many relationship. The Deck model has a transient field showWordsToRevise. I am trying to initialize that field with the amount of words in List<Words> with a statusRepeating == true field.
public class Deck {
#Id
#Column(name = "deck_id")
private Long id;
#Transient
private Boolean showWordsToRevise;
#JsonManagedReference
#OneToMany(mappedBy = "deck", cascade = CascadeType.ALL)
private List<Word> words;
public Boolean getShowWordsToRevise() {
System.out.println(this.words.stream().map(w -> w.getStatusRepeating()).count() + " words to revise" + java.time.LocalDateTime.now().toString());
return this.words.stream().map(w -> w.getStatusRepeating()).count() > 0;
}
public void setShowWordsToRevise(Boolean showWordsToRevise) {
this.showWordsToRevise = showWordsToRevise;
}
}
and
public class Word {
#Id
#Column(name = "word_id")
private Long id;
private LocalDate last_checked;
#Enumerated(EnumType.STRING)
private WordGroup wordGroup = WordGroup.newUnseen;
#Transient
private Boolean statusRepeating = false;
#ManyToOne
#JsonBackReference
#JoinColumn(name="deck_id",referencedColumnName="deck_id")
private Deck deck;
public Boolean getStatusRepeating() {
if (this.wordGroup == WordGroup.first && Period.between(this.last_checked,LocalDate.now()).getDays() > 1) return true;
if (this.wordGroup == WordGroup.second && Period.between(this.last_checked,LocalDate.now()).getDays() > 2) return true;
return this.wordGroup == WordGroup.third && Period.between(this.last_checked, LocalDate.now()).getDays() > 7;
}
public void setStatusRepeating(Boolean statusRepeating) {
this.statusRepeating = statusRepeating;
}
}
The problem is that when there are no words in List<Words> with statusRepeating == true the showWordsToRevise in Word still returns true.
JSON
Hibernate logs twice for the same request, first prints the wrong number, then prints the right number. upd it shows 0 at second print because there is no words in List<Words>
Hibernate:
select
words0_.deck_id as deck_id8_3_0_,
words0_.word_id as word_id1_3_0_,
words0_.word_id as word_id1_3_1_,
words0_.body as body2_3_1_,
words0_.deck_id as deck_id8_3_1_,
words0_.definition as definiti3_3_1_,
words0_.example as example4_3_1_,
words0_.last_checked as last_che5_3_1_,
words0_.transcription as transcri6_3_1_,
words0_.word_group as word_gro7_3_1_
from
word words0_
where
words0_.deck_id=?
1 words to revise2021-10-07T10:28:30.059128400
Hibernate:
select
words0_.deck_id as deck_id8_3_0_,
words0_.word_id as word_id1_3_0_,
words0_.word_id as word_id1_3_1_,
words0_.body as body2_3_1_,
words0_.deck_id as deck_id8_3_1_,
words0_.definition as definiti3_3_1_,
words0_.example as example4_3_1_,
words0_.last_checked as last_che5_3_1_,
words0_.transcription as transcri6_3_1_,
words0_.word_group as word_gro7_3_1_
from
word words0_
where
words0_.deck_id=?
0 words to revise2021-10-07T10:28:30.060126900
You need to update your Deck class and change getShowWordsToRevise() method so that you count the words that have statusRepeating == true:
public class Deck {
#Id
#Column(name = "deck_id")
private Long id;
#Transient
private Boolean showWordsToRevise;
#JsonManagedReference
#OneToMany(mappedBy = "deck", cascade = CascadeType.ALL)
private List<Word> words;
public Boolean getShowWordsToRevise() {
long wordsToReviseCount = this.words.stream().filter(WordLadder::getStatusRepeating).count();
System.out.println(wordsToReviseCount + " words to revise " + java.time.LocalDateTime.now());
return wordsToReviseCount > 0;
}
public void setShowWordsToRevise(Boolean showWordsToRevise) {
this.showWordsToRevise = showWordsToRevise;
}
}

I want to input boolean value in ChallengeDto

public class ChallengeDto {
private Long id;
private Category category;
private String title;
private String subTitle;
private boolean like;
private int totalScore;
private int requiredScore;
public ChallengeDto(Long id, Category category, String title, String subTitle, boolean like, int totalScore, int requiredScore) {
this.id = id;
this.category = category;
this.title = title;
this.subTitle = subTitle;
this.like = like;
this.totalScore = totalScore;
this.requiredScore = requiredScore;
}
}
I created challengeDto that include challenge's properties(id, category, title, subtitle, totalScore, requiredScore) and like property(can know that if i like challenge or not).
If I put like button, that information stored challengeLike table.
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "challenge_id")
private Challenge challenge;
private LocalDateTime createDate;
}
Now I'm trying to write a code to retrieve challengeDto that checks if I clicked like or not, but I'm having a problem... I can't think of what kind of code to make.
#Repository
#RequiredArgsConstructor
public class ChallengeDtoRepository {
private final EntityManager em;
#Transactional
public List<ChallengeDto> findChallenges(Long userId) {
return em.createQuery(
"select new " +
"com.example.candy.controller.challenge.ChallengeDto(c.id,c.category,c.title,c.subTitle,????,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" and cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
}
}
try to rename the field to likeDone or something different than like, it makes the code ambiguous.
However, just simply do:
cl.likeDone
which means:
return em.createQuery(
"select new " +
"com.example.random.demo.dto.ChallengeDto(c.id,c.category,c.title,c.subTitle,cl.likeDone,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" where cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
However, try to use JPA if you don't have any mandatory condition to use native query or jpql.
JPA implementation:
#Repository
public interface ChallengeLikeRepository extends JpaRepository<ChallengeLike, Long> {
List<ChallengeLike> findAllByUser_Id(long userId);
}
Just call the repository method from service layer and map to your required dto:
public List<ChallengeDto> findChallenges(Long userId) {
List<ChallengeLike> entities = this.repository.findAllByUser_Id(userId);
return entities.stream().map(this::mapToDto).collect(Collectors.toList());
}
The mapToDto() method converts the entity to corresponding ChallengeDto
private ChallengeDto mapToDto(ChallengeLike x) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
For your convenience, some properties has been added or changed in some classes. The #Builder annotation has been added to the ChallengeDto class. The rest of the corresponding entity and other classes:
a) ChallengeLike.java
#Entity
#Data
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
#JsonIgnoreProperties("challengeLikes")
private User user;
#ManyToOne
#JoinColumn(name = "challenge_id")
#JsonIgnoreProperties("challengeLikes")
private Challenge challenge;
private boolean likeDone;
private LocalDateTime createDate;
}
b) Challenge.java
#Entity
#Data
public class Challenge {
#Id
private Long id;
private Category category;
private String title;
private String subTitle;
private int totalScore;
private int requiredScore;
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
}
c) Category.java
public enum Category {
CAT_A,
CAT_B
}
Update
If you want to fetch Challenge entity instead of ChallengeLike and map that to ChallengeDto, first implement ChallangeRepository:
#Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long> {
}
Add the fetchType to EAGER in Challange Entity class:
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
And to map the Challenge to ChallengeDto, you can add another mothod as follows:
private ChallengeDto mapToDto(Challenge x) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(!x.getChallengeLikes().isEmpty() && x.getChallengeLikes().get(0).isLikeDone())
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(x.getUserId()) // if you have user reference in Challenge, remove this otherwise.
.build();
}
finally, to incorporate everything properly, change the caller:
public List<ChallengeDto> findChallenges(Long userId) {
List<Challenge> entities = this.repository.findAll();
List<ChallengeDto> entitiesWithoutChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() == null
|| x.getChallengeLikes().isEmpty())
.map(this::mapToDto).collect(Collectors.toList());
List<ChallengeDto> entitiesInferredFromChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() != null && !x.getChallengeLikes().isEmpty())
.flatMap(x -> x.getChallengeLikes().stream())
.map(this::mapToDto)
.collect(Collectors.toList());
entitiesInferredFromChallengeLikes.addAll(entitiesWithoutChallengeLikes);
return entitiesInferredFromChallengeLikes;
}
Final Update
Well, I finally understood properly what you expected. Adopt the following changes to the previous solution and you will get exactly what you want.
Change the 2 occurrence of the following in the findChallanges method:
.map(this::mapToDto)
To:
.map(x -> mapToDto(x, userId))
And the two mapToDto functions will be changed to follows:
private ChallengeDto mapToDto(ChallengeLike x, long userId) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.getUser().getId() == userId && x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
private ChallengeDto mapToDto(Challenge x, long userId) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(false)
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(userId)
.build();
}

Spring Boot + JPA (Hibernate) - ConcurrentModificationException when deleting entity in OneToMany Bidirectional

I want to remove some objects from my collection:
public class Path {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "path", cascade = CascadeType.ALL, orphanRemoval = true)
List<Point> points;
public void removePoint(Point point) {
point.setPath(null);
this.getPoints().remove(point);
}
public class Point{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "point", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private List<PointOperation> operations;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "path_id", foreignKey = #ForeignKey(name = "FK_point_path"), nullable = false)
private Path path;
public void removePointOperation(PointOperation pointOperation) {
pointOperation.setPoint(null);
this.getOperations().remove(pointOperation);
}
When I want to delete all points with nested operations I get NPE error:
private void removeOldPoints(Window window) {
if (window.getPath().getPoints() != null) {
window.getPath().getPoints().forEach(s -> {
if (s.getOperations() != null && !s.getOperations().isEmpty()) {
s.getOperations().forEach(s::removePointOperation); // NPE here!
}
// Optional.ofNullable(s.getOperations()).ifPresent(s::removePointOperations);
window.getPath().removePoint(s);
});
}
}
Why? Because I remove items from collection that is in use? If yes how can I solve my problem?
stack trace:
java.util.ConcurrentModificationException: null
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997)
at org.hibernate.collection.internal.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:883)
at java.base/java.lang.Iterable.forEach(Iterable.java:74)
at pl.xxx.window.service.WindowPathService.lambda$removeOldPoints$0(WindowPathService.java:64)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
UPDATE
Unfortunately after implementing this solution with Iterator -> Iterating through a Collection, avoiding ConcurrentModificationException when removing objects in a loop
private void removeOldPoints(Window window) {
if (window.getPath().getPoints() != null) {
for (Iterator<Point> pointIterator = window.getPath().getPoints().iterator(); pointIterator.hasNext(); ) {
Point point = pointIterator.next();
if (point.getOperations() != null && !point.getOperations().isEmpty()) {
for (Iterator<PointOperation> operationIterator = point.getOperations().iterator(); operationIterator.hasNext(); ) {
PointOperation pointOperation = operationIterator.next();
point.removePointOperation(pointOperation);
operationIterator.remove();
}
}
window.getPath().removePoint(point);
pointIterator.remove();
}
}
}
I have java.util.ConcurrentModificationException: null in line:
operationIterator.remove();

Getting ConstraintViolationException while saving a row with embedded key in the table with many-to-many mapping between two entities using Spring JPA

In our spring boot Restful WebService, we have two master tables with many-to-many relationship between them. But in the transaction table, we want one extra field (current_time) as part of the embedded key other than the primary keys of the two tables. Now, we’ve created a separate class for defining embedded primary key using #Embeddable. Now, while inserting one transaction row to transaction table using Spring JPA, I am manually setting the primary keys in the corresponding entity and calling the save method on corresponding repository. But It is giving me ConstraintViolationException as the current_time is going with null value even if I have manually set it. Any help would be highly appreciated.
First Entity is as follows :
#Entity
#Table(name = "project")
public class Project {
#Id
#GenericGenerator(name = "projectid", strategy = "com.sample.upload.entity.ProjectIDGenerator")
#GeneratedValue(generator = "projectid")
#Column(name = "projectid")
private String projectID;
#Column(name = "project_name")
private String projectName;
#Column(name = "project_descr")
private String projectDesc;
#Column(name = "project_input_path")
private String projectPath;
#Column(name = "project_creation_time")
private Calendar projectCreationTime;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "project_migration", joinColumns = #JoinColumn(name = "projectid", referencedColumnName = "projectid"), inverseJoinColumns = #JoinColumn(name = "migratorid", referencedColumnName = "migratorid"))
private List<Migrator> migrators;
#Column(name = "account_name")
private String accountName;
#Column(name = "account_group")
private String accountGroup;
public String getProjectID() {
return projectID;
}
public void setProjectID(String projectID) {
this.projectID = projectID;
}
public String getAccountName() {
return accountName;
}
public void setAccountName(String accountName) {
this.accountName = accountName;
}
public String getAccountGroup() {
return accountGroup;
}
public void setAccountGroup(String accountGroup) {
this.accountGroup = accountGroup;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getProjectDesc() {
return projectDesc;
}
public void setProjectDesc(String projectDesc) {
this.projectDesc = projectDesc;
}
public String getProjectPath() {
return projectPath;
}
public void setProjectPath(String projectPath) {
this.projectPath = projectPath;
}
public Calendar getProjectCreationTime() {
return projectCreationTime;
}
public void setProjectCreationTime(Calendar projectCreationTime) {
this.projectCreationTime = projectCreationTime;
}
public List<Migrator> getMigrators() {
return migrators;
}
public void setMigrators(List<Migrator> migrators) {
this.migrators = migrators;
}
}
Second Entity :
#Entity
#GenericGenerator(name = "generatorName", strategy = "increment")
#Table(name = "migrator")
public class Migrator {
#Id
#GeneratedValue(generator = "generatorName")
#Column(name = "migratorid")
private String migratorId;
#Column(name = "src_tech_name")
private String srcTechName;
#Column(name = "dest_tech_name")
private String destTechName;
#Column(name = "migrator_name")
private String migratorName;
#Column(name = "migrator_type")
private String migratorType;
public String getMigratorId() {
return migratorId;
}
public void setMigratorId(String migratorId) {
this.migratorId = migratorId;
}
public String getSrcTechName() {
return srcTechName;
}
public void setSrcTechName(String srcTechName) {
this.srcTechName = srcTechName;
}
public String getDestTechName() {
return destTechName;
}
public void setDestTechName(String destTechName) {
this.destTechName = destTechName;
}
public String getMigratorName() {
return migratorName;
}
public void setMigratorName(String migratorName) {
this.migratorName = migratorName;
}
public String getMigratorType() {
return migratorType;
}
public void setMigratorType(String migratorType) {
this.migratorType = migratorType;
}
#Override
public String toString() {
return "Technology [migratorId=" + migratorId + ", srcTechName=" + srcTechName + ", destTechName="
+ destTechName + ", migratorName=" + migratorName + ", migratorType=" + migratorType + "]";
}
}
The join (transaction) table's entity :
#Entity
#Table(name = "project_migration")
public class ProjectMigration {
#EmbeddedId
private ProjectMigrationID migrationId;
#Column(name ="migration_finish_time")
private Calendar migrationFinishTime;
#Column(name ="time_in_millis_for_migration")
private long timeInMillisForMigration;
#Column(name ="migration_status")
private String migrationStatus;
#Column(name ="migrated_codebase_path")
private String migratedCodeBasePath;
The embedded Primary Key class is as follows:
#Embeddable
public class ProjectMigrationID implements Serializable {
private static final long serialVersionUID = -3623993529011381924L;
#Column(name = "projectid")
private String projectId;
#Column(name = "migratorid")
private String migratorId;
#Column(name = "migration_start_time")
private Calendar migrationStartTime;
public ProjectMigrationID() {
}
public ProjectMigrationID(String projectId, String migratorId, Calendar migrationStartTime) {
this.projectId = projectId;
this.migratorId = migratorId;
this.migrationStartTime = migrationStartTime;
}
The snippet from service Class :
for (String migratorId : data.getMigratorIds()) {
Migrator migrator = migratorRepository.findByMigratorId(migratorId);
migrators.add(migrator);
}
if (projectId != null) {
project = projectRepository.findByProjectID(projectId);
System.out.println(project==null);
project.setMigrators(migrators);
System.out.println("I am here");
if (project != null) {
//project.setMigrationStatus("In Progress");
ProjectMigrationID pmId = new ProjectMigrationID();
pmId.setProjectId(project.getProjectID());
pmId.setMigratorId(project.getMigrators().get(0).getMigratorId());
pmId.setMigrationStartTime(new GregorianCalendar());
ProjectMigration pm = new ProjectMigration();
pm.setMigrationId(pmId);
pm.setMigrationStatus("Pending");
projectMigrationRepository.save(pm);
That's because of the #JoinTable where the date is not included and it skips the insertion. If you include a column with all the primary keys needed, it will work as expected.
Only the columns mapped via #JoinTable will be included during insertion or update (defaults to true when mapped)
Either include the date time column in the Project class or use association without #JoinTable.
I'm editing via mobile. So please ignore typos if any.

JPA insert issue - OneToMany/ManyToOne with EmbeddedId

I've an issue where parent is generating multiple insert statements thereby having challenge with saving child. Code:
#Entity
#Table(name="PARENT")
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParentPK id;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "LAST_NAME")
private String lastName;
// bi-directional many-to-one association to ParDepend
#OneToMany(fetch=FetchType.EAGER, mappedBy="parent", cascade={CascadeType.ALL}, orphanRemoval=true)
private List<ParDepend> parDepends;
//getters, setters, add (for parDepends), remove (for parDepends)
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Borrower other = (Borrower) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
#Embeddable
public class ParentPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#SequenceGenerator(name = "parSeq", sequenceName = "PARENT_SEQ", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parSeq")
#Column(name="PAR_SK_SEQ")
private long parSkSeq;
#Column(name="PAR_REV_NUM")
private long parRevNum;
//getter, setters
//JPA generated equals and hashcode
}
#Entity
#Table(name="PAR_DEPEND")
public class ParDepend implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
private ParDependPK id;
private BigDecimal age;
//bi-directional many-to-one association to Parent
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumns({
#JoinColumn(name="PAR_REV_NUM", referencedColumnName="PAR_REV_NUM"),
#JoinColumn(name="PAR_SK_SEQ", referencedColumnName="PAR_SK_SEQ")
})
private Parent parent;
//getters, setters
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
BorrDepend other = (BorrDepend) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}
#Embeddable
public class ParDependPK implements Serializable {
//default serial version id, required for serializable classes.
private static final long serialVersionUID = 1L;
#Column(name="PAR_SK_SEQ", insertable=false, updatable=false, nullable=false)
private long parSkSeq;
#Column(name="PAR_REV_NUM", insertable=false, updatable=false, nullable=false)
private long parRevNum;
#SequenceGenerator(name = "parDependSeq", sequenceName = "PAR_DEPEND_SEQ", allocationSize = 1, initialValue = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "parDependSeq")
#Column(name="SEQ_NUM")
private long seqNum;
#Column(name="REV_NUM")
private long revNum;
//getter setter
//JPA generated equals and hashcode
}
I'm using dozer to convert UI returned data to JPA entity mapping. Then using Spring Repository to save and flush Borrower entity. It is generating borrower sequence twice and inserting nulls for borrower, instead of actual value. My assumption is that actual values are in memory to be inserted with first sequence, while second sequence will null data is getting inserted. Any suggestions on issue with JPA mappings? I do not see dozer an issue as logs shows JPA entity is property created as
{parent=Parent [id=ParentPK [parSkSeq=0, parRevNum=0], firstName=ABCD, lastName=XYZ, parDepends=[ParDepend [id=ParDependPK [parSkSeq=0, parRevNum=0, seqNum=0, revNum=0], age=31]]]}
JPA Ecliselink version 2.5.0-RC1
Spring Data JPA framework version 1.7.1.RELEASE
Please, let me know of any other information required for this question.
Edit: update dozer logs.

Resources