详解基于Spring Boot与Spring Data JPA的多数据源配置
由于项目需要,最近研究了一下基于springBoot与SpringDataJPA的多数据源配置问题。以下是传统的单数据源配置代码。这里使用的是Spring的Annotation在代码内部直接配置的方式,没有使用任何XML文件。
@Configuration @EnableJpaRepositories(basePackages="org.lyndon.repository") @EnableTransactionManagement @PropertySource("classpath:application.properties") publicclassJpaConfig{ privatestaticfinalStringDATABASE_DRIVER="db.driver"; privatestaticfinalStringDATABASE_URL="db.url"; privatestaticfinalStringDATABASE_USER="db.user"; privatestaticfinalStringDATABASE_PASSWORD="db.password"; privatestaticfinalStringPACKAGES_TO_SCAN="packages.to.scan"; privatestaticfinalStringHIBERNATE_DIALECT="hibernate.dialect"; privatestaticfinalStringHIBERNATE_SHOW_SQL="hibernate.show.sql"; @Resource privateEnvironmentenv; @Bean publicDataSourcedataSource(){ DruidDataSourcesource=newDruidDataSource(); source.setDriverClassName(env.getRequiredProperty(DATABASE_DRIVER)); source.setUrl(env.getRequiredProperty(DATABASE_URL)); source.setUsername(env.getRequiredProperty(DATABASE_USER)); source.setPassword(env.getRequiredProperty(DATABASE_PASSWORD)); returnsource; } @Bean publicLocalContainerEntityManagerFactoryBeanentityManagerFactory(){ LocalContainerEntityManagerFactoryBeanfactory=newLocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource()); factory.setPersistenceProviderClass(HibernatePersistenceProvider.class); factory.setPackagesToScan(env.getRequiredProperty(PACKAGES_TO_SCAN)); factory.setJpaProperties(hibernateProperties()); factory.afterPropertiesSet(); returnfactory; } @Bean publicPlatformTransactionManagertransactionManager(){ JpaTransactionManagermanager=newJpaTransactionManager(); manager.setEntityManagerFactory(entityManagerFactory().getObject()); returnmanager; } @Bean publicHibernateExceptionTranslatorhibernateExceptionTranslator(){ returnnewHibernateExceptionTranslator(); } privatePropertieshibernateProperties(){ Propertiesproperties=newProperties(); properties.put(HIBERNATE_DIALECT,env.getRequiredProperty(HIBERNATE_DIALECT)); properties.put(HIBERNATE_SHOW_SQL,env.getRequiredProperty(HIBERNATE_SHOW_SQL)); returnproperties; } }
但是这一配置是不能简单地扩展到多数据源配置的,因为SpringBoot默认会为开发人员做很多工作,而这些工作与多数据源的配置相冲突,因此需要修改原来的配置内容。网上有很多讲解多数据源配置的文章,但是这些文章大多使用的是XML配置的方式,而且没有使用SpringBoot等比较新的Spring框架技术(比如很多人使用实现AbstractRoutingDataSource这一Spring提供的抽象类的方式,还需要切面的支持,无疑是相当繁琐的),已经不适用于在最新的工程项目中使用了,因为最新的Spring框架可以为我们完成很多事情,我们只需要去适应新的方法即可。为此,我通过研究Spring的官方文档和不断调试,实现了新的多数据源配置的方法,在此贴出,仅供参考。
首先,Spring的JPA是直接支持多数据源配置的,因此我们可以在配置文件或者代码中直接配置多个数据源。由于多数据源配置可能会共享一些配置信息,因此使用继承体系实现这种配置是最合适的。在例子中,我的ORM框架使用的是hibernate,而与Hibernate相关的配置信息我都放在了配置基类BaseJpaConfg中,代码如下。
@Configuration @EnableTransactionManagement @PropertySource("classpath:application.properties") publicclassBaseJpaConfig{ privatestaticfinalStringHIBERNATE_DIALECT="hibernate.dialect"; privatestaticfinalStringHIBERNATE_SHOW_SQL="hibernate.show.sql"; @Resource protectedEnvironmentenv; @Bean publicHibernateExceptionTranslatorhibernateExceptionTranslator(){ returnnewHibernateExceptionTranslator(); } protectedPropertieshibernateProperties(){ Propertiesproperties=newProperties(); properties.put(HIBERNATE_DIALECT,env.getRequiredProperty(HIBERNATE_DIALECT)); properties.put(HIBERNATE_SHOW_SQL,env.getRequiredProperty(HIBERNATE_SHOW_SQL)); returnproperties; } }
该类提供Hibernate相关信息。它有两个子类,分别是SpringJpaConfig以及MysqlJpaConfig,分别配置了两个数据源,连接到两个不同的数据库Schema,代码如下。
@Configuration @EnableJpaRepositories(basePackages={"org.lyndon.repository1"},entityManagerFactoryRef="springEntityManagerFactory", transactionManagerRef="springTransactionManager") publicclassSpringJpaConfigextendsBaseJpaConfig{ privatestaticfinalStringDATABASE_DRIVER="db.driver"; privatestaticfinalStringDATABASE_URL="db.url"; privatestaticfinalStringDATABASE_USER="db.user"; privatestaticfinalStringDATABASE_PASSWORD="db.password"; privatestaticfinalStringPACKAGES_TO_SCAN="packages.to.scan"; @Bean @ConfigurationProperties(prefix="datasource.primary") publicDataSourcedataSource(){ DruidDataSourcesource=newDruidDataSource(); source.setDriverClassName(env.getRequiredProperty(DATABASE_DRIVER)); source.setUrl(env.getRequiredProperty(DATABASE_URL)); source.setUsername(env.getRequiredProperty(DATABASE_USER)); source.setPassword(env.getRequiredProperty(DATABASE_PASSWORD)); returnsource; } @Bean(name="springEntityManagerFactory") publicLocalContainerEntityManagerFactoryBeanspringEntityManagerFactory(){ LocalContainerEntityManagerFactoryBeanfactory=newLocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource()); factory.setPersistenceProviderClass(HibernatePersistenceProvider.class); factory.setPackagesToScan(env.getRequiredProperty(PACKAGES_TO_SCAN).split(",")); factory.setJpaProperties(hibernateProperties()); factory.afterPropertiesSet(); returnfactory; } @Bean(name="springTransactionManager") @Primary publicPlatformTransactionManagertransactionManager(){ JpaTransactionManagermanager=newJpaTransactionManager(); manager.setEntityManagerFactory(springEntityManagerFactory().getObject()); returnmanager; } }
@Configuration @EnableJpaRepositories(basePackages={"org.lyndon.repository2"},entityManagerFactoryRef="mysqlEntityManagerFactory", transactionManagerRef="mysqlTransactionManager") publicclassMysqlJpaConfigextendsBaseJpaConfig{ privatestaticfinalStringDATABASE_DRIVER="db.driver"; privatestaticfinalStringDATABASE_URL="db.url2"; privatestaticfinalStringDATABASE_USER="db.user"; privatestaticfinalStringDATABASE_PASSWORD="db.password"; privatestaticfinalStringPACKAGES_TO_SCAN="packages.to.scan2"; @Bean @ConfigurationProperties(prefix="datasource.secondary") publicDataSourcedataSource2(){ DruidDataSourcesource=newDruidDataSource(); source.setDriverClassName(env.getRequiredProperty(DATABASE_DRIVER)); source.setUrl(env.getRequiredProperty(DATABASE_URL)); source.setUsername(env.getRequiredProperty(DATABASE_USER)); source.setPassword(env.getRequiredProperty(DATABASE_PASSWORD)); returnsource; } @Bean(name="mysqlEntityManagerFactory") publicLocalContainerEntityManagerFactoryBeanmysqlEntityManagerFactory(){ LocalContainerEntityManagerFactoryBeanfactory=newLocalContainerEntityManagerFactoryBean(); factory.setDataSource(dataSource2()); factory.setPersistenceProviderClass(HibernatePersistenceProvider.class); factory.setPackagesToScan(env.getRequiredProperty(PACKAGES_TO_SCAN).split(",")); factory.setJpaProperties(hibernateProperties()); factory.afterPropertiesSet(); returnfactory; } @Bean(name="mysqlTransactionManager") publicPlatformTransactionManagertransactionManager(){ JpaTransactionManagermanager=newJpaTransactionManager(); manager.setEntityManagerFactory(mysqlEntityManagerFactory().getObject()); returnmanager; } }
两份配置代码使用了各自的数据源、实体管理对象以及事务管理对象。这里要注意的有两点,首先是实体管理对象。SpringBoot框架为方便开发者,默认会寻找名为“entityManagerFactory”的Bean作为实体管理的实现,但是我们这里使用了两个实体管理对象,名称也并不是默认的“entityManagerFactory”,因此必须在各自的EnableJpaRepositories的Annotation中指明使用的实体管理对象。第二点,就是事务管理对象。SpringBoot会提供一个默认的事务管理对象的实现,但是我们在这里使用了两个不同的事务管理对象,因此我们也需要在各自的EnableJpaRepositories的Annotation中指明使用的事务管理对象。除此以外,我们还必须指定其中一个事务管理对象为主要对象(使用Primary这一Annotation),让Spring能有主次地使用相应的事务管理对象。
以上就是本文的主要内容。关于Service的配置等问题,由于和单数据源时是一样的,因此就不再赘述了。基于上述代码,我们就可以使用SpringBoot实现多数据源之间的无缝切换了,是不是很轻松?如果你不想使用代码配置的方式,也可以使用XML文件代替,配置的核心属性与上文相同,可以自己加以琢磨。希望对大家的学习有所帮助,也希望大家多多支持毛票票。