简体   繁体   中英

Using 2 DataSources for 2 DBs with Spring

I've been trying unsuccessfully to solve a problem of using 2 DBs with the same schema in Spring. The problem I'm trying to solve is creating a web page for a restaurant that is based in 2 different cities, so I thought using a separate database for each city would be the best solution.

I am only getting results from one database, the other one is for some reason unused. The databases are termed BA and KE for the city and I'm using a City enum with the same values.

BAConfig.java

@Configuration
@PropertySources(
    {@PropertySource("classpath:jpa.properties"),
            @PropertySource("classpath:jdbc.properties")})
@EnableTransactionManagement    // Enable use of the @Transactional annotation
@ComponentScan(basePackages = "dao")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class BAConfig {

@Autowired
private Environment environment;

@Bean(name="dataSourceBA")
public DataSource buildDataSource()
{
    HikariConfig hkcfg = new HikariConfig();
    hkcfg.setJdbcUrl(environment.getRequiredProperty("jdbc.urlBA"));
    hkcfg.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
    hkcfg.setUsername(environment.getRequiredProperty("jdbc.username"));
    hkcfg.setPassword(environment.getRequiredProperty("jdbc.password"));
    HikariDataSource ds = new HikariDataSource(hkcfg);
    return ds;
}

public static LocalContainerEntityManagerFactoryBean entityManagerFactoryBuilder(DataSource ds)
{
    LocalContainerEntityManagerFactoryBean emf = new LocalContainerEntityManagerFactoryBean();
    emf.setDataSource(ds);
    emf.setJpaVendorAdapter(new EclipseLinkJpaVendorAdapter());
    emf.setPackagesToScan("model"); // Look for entities in this package

    Properties props = new Properties();
    props.setProperty("databasePlatform", "org.eclipse.persistence.platform.database.PostgreSQLPlatform");
    props.setProperty("generateDdl", "true");
    props.setProperty("showSql", "true");
    props.setProperty("eclipselink.weaving", "false");
    props.setProperty("eclipselink.ddl-generation", "create-tables");
    emf.setJpaProperties(props);
    return emf;
}

@Bean(name="BAEM")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("dataSourceBA") DataSource ds) {
    return entityManagerFactoryBuilder(ds);
}


@Bean(name = "txManagerBA")
JpaTransactionManager transactionManager(@Qualifier("BAEM") EntityManagerFactory em) {
    JpaTransactionManager transactionManager = new JpaTransactionManager(em);
    return transactionManager;
}
}

KEConfig.java

@Configuration
@PropertySources(
    {@PropertySource("classpath:jpa.properties"),
            @PropertySource("classpath:jdbc.properties")})
@EnableTransactionManagement    // Enable use of the @Transactional annotation
@ComponentScan(basePackages = "dao")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class KEConfig {

@Autowired
private Environment environment;

@Bean("dataSourceKE")
public DataSource buildDataSource()
{
    HikariConfig hkcfg = new HikariConfig();
    hkcfg.setJdbcUrl(environment.getRequiredProperty("jdbc.urlKE"));
    hkcfg.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName"));
    hkcfg.setUsername(environment.getRequiredProperty("jdbc.username"));
    hkcfg.setPassword(environment.getRequiredProperty("jdbc.password"));
    HikariDataSource ds = new HikariDataSource(hkcfg);
    return ds;
}

@Bean(name="KEEM")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(@Qualifier("dataSourceKE")DataSource ds) {
    return BAConfig.entityManagerFactoryBuilder(ds);
}


@Bean(name = "txManagerKE")
JpaTransactionManager transactionManager(@Qualifier("KEEM") EntityManagerFactory em) {
    JpaTransactionManager transactionManager = new JpaTransactionManager(em);
    return transactionManager;
}
}

These are both imported into the MainConfig.java class and use the following properties file.

jdbc.properties

jdbc.driverClassName=org.postgresql.Driver
jdbc.urlBA=jdbc:postgresql://localhost:5432/BambooBA
jdbc.urlKE=jdbc:postgresql://localhost:5432/BambooKE

Here is the rest controller for the given entity.

ReservationsController.java

@RestController
@RequestMapping("/reservations")
public class ReservationsController {

@Autowired
private ReservationsService reservationsService;

@RequestMapping(value = "/getAll/{c}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<Reservations>> getAll(@PathVariable City c) {
    try {
        List<Reservations> reservations = new ArrayList<Reservations>();
        switch(c)
        {
            case BA: reservations = reservationsService.findAllBA(); break;
            case KE: reservations = reservationsService.findAllKE(); break;
        }
        return new ResponseEntity<List<Reservations>>(reservations, HttpStatus.OK);
    } catch (NoSuchElementException e)
    {
        return new ResponseEntity<List<Reservations>>(HttpStatus.NOT_FOUND);
    }
}
}

Here is the reservations service, where i've been trying to pull dummy data (in both DBs the id is 1).

ReservationsService.java

@Service
public class ReservationsService {

@Autowired
private ReservationsDao reservationsDao;

@Transactional("txManagerBA")
public List<Reservations> findAllBA() throws NoSuchElementException {
    reservationsDao.setEM(City.BA);
    List<Reservations> reservations = new ArrayList<Reservations>();
    reservations.add(reservationsDao.find(1));
    if(reservations.size() == 0)
    {
        throw new NoSuchElementException();
    }
    return reservations;
}

@Transactional("txManagerKE")
public List<Reservations> findAllKE() throws NoSuchElementException {
    reservationsDao.setEM(City.KE);
    List<Reservations> reservations = new ArrayList<Reservations>();
    reservations.add(reservationsDao.find(1));
    if(reservations.size() == 0)
    {
        throw new NoSuchElementException();
    }
    return reservations;
}
}

And here is the DAO superclass (the particular DAO inherits from this class and only has a super constructor in it).

BaseDao.java

public abstract class BaseDao<T>{

@PersistenceContext(unitName = "BAEM")
EntityManager emBA;

@PersistenceContext(unitName = "KEEM")
EntityManager emKE;

EntityManager em;

protected final Class<T> type;

protected BaseDao(Class<T> type) {
    this.type = type;
}

public void setEM(City c)
{
    switch(c) {
        case BA: em = emBA; break;
        case KE: em = emKE; break;
    }
}

public T find(Integer id) {
    return em.find(type, id);
}

public List<T> findAll() {
    return em.createQuery("SELECT e FROM " + type.getSimpleName() + " e", type).getResultList();
}
}

The debug (breakpoint set in BaseDAO in the find() function) shows that the correct persistence unit is being used to retrieve data (when i move all the way down to persistenceUnitInfo.nonJtaDataSource.jdbcUrl the URL is correct).

Yet only one of the databases is being used, no matter the request. I have also tried using an AbstractRoutingDataSource but with the same problem - the database would get set on the first request and from then only that database would be used, indifferent to the request.

Here is the configuration that we are using in our spring4 application with Hikaripool The @Qualifier annotation will be useful to differentiate when you have multiple database data sources and @Primary to make the default datasource when used with @AutoWiredand when using other datasource make use of @Qualifier annotation along with @AutoWired

@Bean(destroyMethod = "close")
@Primary
@Qualifier("tspDataSource")
public DataSource dataSource() {

    HikariConfig config = new HikariConfig("/hikari-tsp.properties");
    config.addDataSourceProperty("cachePrepStmts", "true");
    config.addDataSourceProperty("prepStmtCacheSize", "250");
    config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
    final HikariDataSource ds = new HikariDataSource(config);
    return ds;
}

and second one

 @Bean(destroyMethod = "close")
    @Qualifier("fdxDataSource")
    public DataSource fdxDataSource() {
        HikariConfig config = new HikariConfig("/hikari-fdx.properties");
        config.addDataSourceProperty("cachePrepStmts", "true");
        config.addDataSourceProperty("prepStmtCacheSize", "250");
        config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
        final HikariDataSource ds = new HikariDataSource(config);
        return ds;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM