Bernhard
Bernhard

Reputation: 725

How to persist initial data with JpaRepository

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

Answers (2)

PepperBob
PepperBob

Reputation: 707

You're missing

@EnableTransactionManagement

on you DBTest-Class

    @EnableTransactionManagement
    public class DBTest extends AbstractH2TestCase {

Upvotes: 2

Giovanni Botta
Giovanni Botta

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

Related Questions