Reputation: 725
In order to initially fill the database with data, I created entity instances and wrote a service class that should save them in the database.
In a very simplified way, my model consists of three classes A, B and C. The instances of the classes A and B both reference (the same) instances of C (annotated with @ManyToOne
):
a1 -> c1
a2 -> c2
a3 -> c1
b1 -> c1
b2 -> c2
For A and B I use JpaRepositories
for persistence.
Then I try to save the instances I created programmatically:
@Transactional
public void save(A a1, A a2, A a3, B b1, B b2) {
aRepo.save(a1);
aRepo.save(a2);
aRepo.save(a3);
bRepo.save(b1);
bRepo.save(b2);
}
I get an exception ("detached entity passed to persist: C") in the third line, because the instance c1 got saved already in the first line.
How can I avoid that? I thought that marking my save method in my service with @Transactional
would be sufficient already, but obviously it's not.
Of course I could just write
aRepo.save(Arrays.asList(a1, a2, a3));
bRepo.save(Arrays.asList(b1, b2));
but that only shifts my problem as the exception then occurs in the second line when the b1 that references the already saved c1 should get saved.
How can I save the data structures I created in one method without getting that exception?
Here's the rest of the code:
@Entity
public class A {
@Id
@GeneratedValue
@Access(AccessType.FIELD)
private Long id;
@ManyToOne(cascade = CascadeType.ALL)
@Access(AccessType.FIELD)
private C c;
public A(C c) {
this.c = c;
}
public C getC() {
return c;
}
}
Class B looks exactly the same, class C only contains the ID parameter.
The instances are created within a test case:
public class DBTest extends AbstractH2TestCase {
@Test
public void testDataStorage() {
final AbstractApplicationContext context = new AnnotationConfigApplicationContext(DBTest.class);
try {
final PersistService persistService = context.getBean(PersistService.class);
C c1 = new C();
C c2 = new C();
A a1 = new A(c1);
A a2 = new A(c2);
A a3 = new A(c1);
B b1 = new B(c1);
B b2 = new B(c2);
persistService.save(b1, b2, a1, a2, a3);
} finally {
context.close();
}
}
}
The superclass for the test I found somewhere on the web and modified it a bit:
@Configuration
@ComponentScan(basePackageClasses = A.class)
@EnableJpaRepositories
public class AbstractH2TestCase {
public AbstractH2TestCase() {
super();
}
@Bean
public DataSource dataSource() {
return new EmbeddedDatabaseBuilder().setType(H2).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
JpaVendorAdapter jpaVendorAdapter) {
final LocalContainerEntityManagerFactoryBean lef = new LocalContainerEntityManagerFactoryBean();
lef.setDataSource(dataSource);
lef.setJpaVendorAdapter(jpaVendorAdapter);
final String thisPackageAndSubpackages = this.getClass().getPackage().getName();
lef.setPackagesToScan(thisPackageAndSubpackages);
return lef;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
final HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
@Bean
public PlatformTransactionManager transactionManager() {
return new JpaTransactionManager();
}
}
If you want a running version of the code - I checked it in here:
https://github.com/BernhardBln/stackexchange
It's a maven project.
Upvotes: 0
Views: 2092
Reputation: 707
You're missing
@EnableTransactionManagement
on you DBTest-Class
@EnableTransactionManagement
public class DBTest extends AbstractH2TestCase {
Upvotes: 2
Reputation: 9816
Your @Transactional
annotation is useless because you're not using a container (like J2EE or Spring) to actually figure out that the method must be run in one transaction, so each call to save()
is a transaction and that's why you're getting a detached entity.
Try using Spring as in this example or the docs.
Upvotes: 0