Multiple jpa:repositories in xml config, how to co

2020-01-29 06:16发布

I have researched and found an explaination and sample code as to how to use spring data jpa with multiple datasources which refers to configuring multiple jpa:repositories in the xml configuration as follows:

<jpa:repositories base-package="org.springframework.data.jpa.repository.sample"
    entity-manager-factory-ref="entityManagerFactory">
    <repository:exclude-filter type="assignable" expression="org.springframework.data.jpa.repository.sample.AuditableUserRepository" />
</jpa:repositories>
<jpa:repositories base-package="org.springframework.data.jpa.repository.sample"
    entity-manager-factory-ref="entityManagerFactory-2"
    transaction-manager-ref="transactionManager-2">
    <repository:include-filter type="assignable" expression="org.springframework.data.jpa.repository.sample.AuditableUserRepository" />
</jpa:repositories>

How would you declare both of the above jpa:repositories configurations using java configuration and the @EnableJpaRepositories annotation?

The annotation seems to support only one set of attributes (i.e. for one jpa:repository only) and it is not possible to declare the annotation multiple times.

2条回答
冷血范
2楼-- · 2020-01-29 06:57

I created a 'minimal' multiple datasource project to help me work out how to do this. There are 7 Java classes and other config in there, so I will only post key extracts in this answer. You can get the full project from GitHub: https://github.com/gratiartis/multids-demo

The demo sets up two JPA entities:

@Entity public class Foo { /* Constructors, fields and accessors/mutators */ }
@Entity public class Bar { /* Constructors, fields and accessors/mutators */ }

Associated with these we will create two repositories. Thanks to the awesomeness of Spring Data, we can get ourselves some pretty full-featured repositories purely by defining interfaces which extend JpaRepository:

public interface FooRepository extends JpaRepository<Foo, Long> {}
public interface BarRepository extends JpaRepository<Bar, Long> {}

Now we need to ensure that each of these maps to a table in its own database.

To achieve this, we will need two separate entity managers, each of which has a different datasource. However, in a Spring Java config @Configuration class, we can only have one @EnableJpaRepositories annotation and each such annotation can only reference one EntityManagerFactory. To achieve this, we create two separate @Configuration classes: FooConfig and BarConfig.

Each of these @Configuration classes will define a DataSource based on an embedded HSQL database:

@Bean(name = "fooDataSource")
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setName("foodb").setType(EmbeddedDatabaseType.HSQL).build();
}
@Bean(name = "barDataSource")
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
            .setName("bardb").setType(EmbeddedDatabaseType.HSQL).build();
}

@Bean(name = "barEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean lef = 
            new LocalContainerEntityManagerFactoryBean();
    lef.setDataSource(dataSource());
    lef.setJpaVendorAdapter(jpaVendorAdapter);
    lef.setPackagesToScan("com.sctrcd.multidsdemo.domain.bar");
    lef.setPersistenceUnitName("barPersistenceUnit");
    lef.afterPropertiesSet();
    return lef.getObject();
}
@Bean(name = "fooEntityManagerFactory")
public EntityManagerFactory entityManagerFactory() {
    LocalContainerEntityManagerFactoryBean lef = 
            new LocalContainerEntityManagerFactoryBean();
    lef.setDataSource(dataSource());
    lef.setJpaVendorAdapter(jpaVendorAdapter);
    lef.setPackagesToScan("com.sctrcd.multidsdemo.domain.foo");
    lef.setPersistenceUnitName("fooPersistenceUnit");
    lef.afterPropertiesSet();
    return lef.getObject();
}

Each configuration should define an EntityManagerFactory, as above, which references its own dataSource() @Bean method. It also defines a path to the @Entity beans which it manages. You need to make sure that @Entity beans for different data sources are in different packages.

At this point it's worth noting that if each of these configurations uses the default namings for key persistence beans (i.e. entityManagerFactory), then Spring will see that there are two beans with the EntityManager interface, both of which have the same name. So one will be chosen. This leads to errors such as:

Not an managed type: class com.sctrcd.multidsdemo.domain.bar.Bar

This can be seen in the branch of the demo project here: https://github.com/gratiartis/multids-demo/tree/1-unnamed-entitymanager-beans

This is because in that example, Spring has wired up the beans relating to the "foodb" database, and Bar is not an entity in that database. Unfortunately the BarRepository has been wired up with the Foo entity manager.

We resolve this issue by naming all our beans in each of config class. i.e.

@Bean(name = "fooDataSource") public DataSource dataSource() { .. }
@Bean(name = "fooEntityManager") public EntityManager entityManager() { .. }

At this point if you were to run the tests in the project, you might see warnings such as:

 No bean named 'entityManagerFactory' is defined.

This is because ... drumroll ... we do not have an EntityManagerFactory with the default name "entityManagerFactory". We have one called "fooEntityManagerFactory" and another called "barEntityManagerFactory". Spring is looking for something with a default name, so we need to instruct it to wire things up differently.

As it turns out, this is incredibly simple to do. We just need to put the correct references in the @EnableJpaRepositories annotation for each @Configuration class.

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "fooEntityManagerFactory", 
        transactionManagerRef = "fooTransactionManager",
        basePackages = {"com.sctrcd.multidsdemo.integration.repositories.foo"})
public class FooConfig {
    // ...
}

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "barEntityManagerFactory", 
        transactionManagerRef = "barTransactionManager",
        basePackages = { "com.sctrcd.multidsdemo.integration.repositories.bar" })
public class BarConfig {
    // ...
}

As you can see, each of these @EnableJpaRepositories annotations defines a specific named EntityManagerFactory and PlatformTransactionManager. They also specify which repositories should be wired up with those beans. In the example, I have put the repositories in database-specific packages. It is also possible to define each individual repository by name, by adding includeFilters to the annotation, but by segregating the repositories by database, I believe that things should end up more readable.

At this point you should have a working application using Spring Data repositories to manage entities in two separate databases. Feel free to grab the project from the link above and run the tests to see this happening. Hopefully this answer is useful to more folks, as I have spent a decent amount of time working out to do this as cleanly as possible with as little code as I could manage. Any ideas for improvement of the answer or demo project are welcome.

查看更多
趁早两清
3楼-- · 2020-01-29 07:13

You may try put it on two @Configuration classes (one @EnableJpaRepositories per @Configuration).

查看更多
登录 后发表回答