I am using Spring Boot with Spring Data JPA and Spring Data REST.
I have a class called TestExection, it is a class setup to have multiple TestResults. When a TestExecution is created there are no results created to set for it.
Later when the TestExecution is run TestResult objects are created.
These TestResult objects have a @OneToOne relation to the TestExecution and prior to saving a TestResult the TestExecution object is set on the TestResult and the save(TestResult) is called.
// Create RESULT object
TestResult testResult = new TestResult(someTestExection......);
save(testResult)
If I make a call via REST to see the testResults/1/testExecution I can see the testExecution associated with the testResult. If I make the same call to testExecutions/1/testResults it returns a empty [].
2 questions:
1) Do I need to expclicitly set the newly created TestResult object into the TestExecutions's TestResult Set?
testExecution.getTestResults().add(testResult);
save(testExecution)
When I try this it causes the attached stack trace.
Hibernate: update test_execution set description=?, owner=?, version=? where id=? and version=?
[WARNING]
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.springframework.boot.maven.AbstractRunMojo$LaunchRunner.run(AbstractRunMojo.java:478)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.StackOverflowError
at com.miw.mcb.server.model.TestExecution.hashCode(TestExecution.java:32)
at com.miw.mcb.server.model.TestResult.hashCode(TestResult.java:30)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:448)
at com.miw.mcb.server.model.TestExecution.hashCode(TestExecution.java:32)
at com.miw.mcb.server.model.TestResult.hashCode(TestResult.java:30)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:448)
OR
2) Is there some way to have it setup with a relationship so that when TestResult sets a TestExecution it can be linked and added to its list?
TestExecution
@Data
@Entity
@Table(name = "test_execution")
//@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TestExecution {
private @Id @GeneratedValue Long id;
// TODO set back to lazy
@ManyToMany(fetch = FetchType.EAGER)
//@JsonBackReference
private Set<TestResult> testResults;
// TODO set back to lazy
@ManyToMany(fetch = FetchType.EAGER)
private Set<TestSuite> testSuites;
TestResult
@Data
@Entity
@Table(name = "test_result")
//@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class TestResult {
private @Id @GeneratedValue Long id;
// TODO switch back to lazy
@OneToOne(fetch = FetchType.EAGER)
//@JsonManagedReference
private TestExecution testExecution;
// TODO switch back to lazy
@OneToOne(fetch = FetchType.EAGER)
private TestSuite testSuite;
Both Repositories are setup the same
TestExecutionRepository
@Repository
@Transactional
public interface TestExecutionRepository extends PagingAndSortingRepository<TestExecution, Long> {
TestResultRepository
@Repository
@Transactional
public interface TestResultRepository extends PagingAndSortingRepository<TestResult, Long> {
AppConfig
@Configuration
@ComponentScan({ "com.miw.mcb.server.dao.repository", "com.miw.mcb.server.model" })
public class AppConfig {
}
SpringBootApp (ReactAndSpringDataRestApplication)
@SpringBootApplication
public class ReactAndSpringDataRestApplication {
public static void main(String[] args) {
SpringApplication.run(ReactAndSpringDataRestApplication.class, args);
}
}
UPDATE: I made the updates for @ManyToOne and @OneToMany but there is still the problem of how the TestResults
are propagated to TestExecution
Thank your for the explanation on ManyToOne and OneToMany. I thought I had the mapping incorrect.
After that I got the following stack trace when running:
testExecution.getTestResults().add(testResult);
save(testExecution)
** Update for result.setTestExecution(execution); execution.getResults().add(result); repo.save(execution); without saving result
first **
java.lang.StackOverflowError: null
at com.miw.mcb.server.model.TestExecution.hashCode(TestExecution.java:26)
at com.miw.mcb.server.model.TestResult.hashCode(TestResult.java:35)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:448)
at com.miw.mcb.server.model.TestExecution.hashCode(TestExecution.java:26)
at com.miw.mcb.server.model.TestResult.hashCode(TestResult.java:35)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
at org.hibernate.collection.internal.PersistentSet.hashCode(PersistentSet.java:448)
at com.miw.mcb.server.model.TestExecution.hashCode(TestExecution.java:26)
at com.miw.mcb.server.model.TestResult.hashCode(TestResult.java:35)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)...
** To fix this I had to implement the hasCode() function for test result with Lombok **
@EqualsAndHashCode(exclude={"id", "testExecution", "device", "testCase"})
I think you're making wrong choices for the JPA annotations. Moreover, I don't understand how this even works.
In fact, a @ManyToMany
relationship represents a kind of relationship in which every element of an entity can be related with anyone from the other entity. You need an intermediate table for that. This means a single TestResult
could be linked with many executions.
On the other side, you're providing a @OneToOne
mapping, which is just the other way, provide only one entity instance from the other side for this object.
The way you manage it, looks like what you want to have is a @OneToMany
, which will allow TestExecution
to control the testResults
collection, meaning a test execution can have more than one result, but each result belongs only to one execution.
TestExecution:
@OneToMany(fetch=FetchType.EAGER, cascade = CascadeType.ALL, mappedBy="testExecution")
private Set<TestResult> testResults;
TestResult:
@ManyToOne(fetch=FetchType.EAGER)
@JoinColumn(name="id_test_result")
private TestExecution testExecution;
This way you let TextExecution manage its TestResult collection (using CascadeType.ALL
propagates the changes into the collection when saving the TestExecution object).
See also: