Às vezes, o mesmo projeto envolve vários bancos de dados, ou seja, várias fontes de dados. Várias fontes de dados podem ser divididas em duas situações:
1) Dois ou mais bancos de dados não têm correlação e são independentes um do outro. De fato, isso pode ser desenvolvido como dois projetos. Por exemplo, no desenvolvimento de jogos, um banco de dados é um banco de dados de plataforma e os outros bancos de dados correspondentes aos jogos da plataforma também estão disponíveis;
2) Dois ou mais bancos de dados são relacionamentos com escravos principais, como o MySQL constrói um mestre-mestre, seguido de vários escravos; ou cópia de escravo mestre construído com MHA;
Atualmente, existem aproximadamente duas maneiras de construir fontes multi-dados da primavera e você pode escolher de acordo com a situação de fontes multi-dados.
1. Use arquivos de configuração de mola para configurar diretamente várias fontes de dados
Por exemplo, no caso de nenhuma correlação entre dois bancos de dados, você pode configurar diretamente várias fontes de dados no arquivo de configuração da mola e executar configurações de transação, como mostrado abaixo:
<Contexto: Componente-Scan-Base-Package = "NET.AAZJ.SERVICE, NET.AAZJ.AOP" /> <Contexto: Componente-Scan Base-Package = "net.aazj.aop" /> <!-Introduce arquivos de propriedade-> <Contexto: Property-placeholder Location = "ClassPath: ConfIc /DB.Properties" /" init-method = "init" Destro-Method = "Close"> <Propriedade name = "url" value = "$ {jdbc_url}" /> <propriedade name = "nome de usuário" value = "$ {jdbc_username}" /> <nome da propriedade "" seveda = "$ {jdbc_Umername}" /> value = "0" /> <!-Número máximo de conexões usadas pelo pool de conexões-> <nome da propriedade = "maxactive" value = "20" /> <!-Número máximo de conexões disponíveis-> <nome da propriedade = "maxidle" value = "20" /> <!-Número mínimo de conexões disponíveis-> <o nome da propriedade = "minidle" "0" " /> <! name = "maxwait" value = "60000" /> < /bean> <bean id = "sqlSessionFactory"> <propriedade name = "DataSource" ref = "DataSource" /> <Nome da propriedade "configlocation" value = "classpath: config /mybatis-config.xml" /> <weengy = "mapper" maper = "myloclfig.xml" /> <weengy = "maper =" mapper "mapper /" myloclfig.xml " /> <weengy =" maper = "mapper" mapper /"myloclfig.xml" />. value = "ClassPath*: config/maperes/**/*. xml"/> </i bean> <!-Gerenciador de transações para um único JDBC DataSource-> <bean id = "transactionManager"> <!-Nome "DataSource-Ref =" DataSource "/> </Bean> <! transaction-manager="transactionManager" /> <bean> <property name="basePackage" value="net.aazj.mapper" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/></bean> <!-- Enables the use of the @AspectJ style of Spring AOP --><aop:aspectj-autoproxy/>Configuração da segunda fonte de dados
<bean name = "DataSource_2" init-method = "init" Destroy-method = "Close"> <propriedade name = "url" value = "$ {JDBC_URL_2}" /> <Nome da propriedade = "Nome" ") Inicialize o tamanho da conexão-> <propriedade name = "Initialsize" value = "0" /> <!-Número máximo de conexões usadas no pool de conexões-> <propriedade name = "maxactive" value = "20" /> <!-máximo de pool de conexão com odle 0-> <names name = "maxidle" = "20" /> <!-minimum odle pool-> Tempo-> <Nome da propriedade = "maxwait" value = "60000" /> < /bean> <bean id = "sqlsessionfactory_slave"> <propriedade name = "DataSource" ref = "DataSource_2" /> <names name = "configLocation" "" ClassPath: config /mybtis-Config-2.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-fig-2. value = "Classpath*: config/mappers2/**/*. xml"/> </i bean> <!-Gerenciador de transações para um único JDBC DataSource-> <bean id = "transactionManager_2"> <! transaction-manager = "transactionManager_2" /> <Bean> <propriedade name = "BasEpackage" value = "net.aazj.mapper2" /> <names name = "sqlsessionFactoryBeanName" value = "sqlsessionFactory_2" /> < /bean> Como mostrado acima, configuramos duas fontes de dados, duas SQLSessionFactory, dois gerentes de transação e a chave está na configuração do MappersCannerConfigurer - usando a propriedade SQLSessionFactoryBeanName para injetar diferentes nomes SQLSessptórios. Dessa forma, injetaremos o SQLSessionFactory correspondente nas interfaces do mapeador correspondentes a diferentes bancos de dados.
Deve -se notar que essa configuração de vários bancos de dados não suporta transações distribuídas, ou seja, vários bancos de dados não podem ser operados na mesma transação. A vantagem dessa configuração é que ela é muito simples, mas não é flexível. Não é muito adequado para a configuração da fonte de vários dados do tipo de escravo mestre. A configuração da fonte de vários dados do tipo mestre-escravo precisa ser particularmente flexível e requer configuração detalhada de acordo com o tipo de negócio. Por exemplo, para algumas instruções de seleção demoradas, esperamos colocá-las no escravo e, para atualizar, excluir e outras operações, só podemos executá-las no mestre. Além disso, para algumas declarações selecionadas com requisitos altos em tempo real, também podemos precisar colocá -los no mestre - por exemplo, em um cenário, vou ao shopping para comprar uma arma, e a operação de compra é definitivamente o mestre. Após a conclusão da compra, precisamos re-interparar as armas e moedas de ouro que possuo. Em seguida, essa consulta também pode precisar impedir que elas sejam executadas no mestre e não podem ser executadas no escravo, porque pode haver atrasos no escravo. Não queremos que os jogadores achem que, após o sucesso da compra, eles não conseguem encontrar armas na mochila.
Portanto, para a configuração da fonte de vários dados do tipo mestre-escravo, a configuração flexível é necessária de acordo com a empresa, que as selecionas podem ser colocadas no escravo e que as seleções não podem ser colocadas no escravo. Portanto, a configuração acima da fonte de dados não é muito adequada.
2. Configuração da fonte multi-dados baseada em abstractroutingDataSource e AOP
O princípio básico é que definimos uma classe DataSource ThreadLocalRountingDataSource nós mesmos para herdar abstractrotingDataSource e, em seguida, injetar fontes de dados mestre e escravo na ThreadLocalRountingDataSource no arquivo de configuração e, em seguida, a fonte de dados da AOP. Vamos ver a implementação do código abaixo:
1) Primeiro defina uma enumeração para representar diferentes fontes de dados:
pacote net.aazj.enums; /** * categoria de fonte de dados: mestre/escravo */public Enum DataSources {Master, Slave} 2) Use o Headlocal para salvar a chave de qual fonte de dados escolher para cada thread:
pacote net.aazj.util; importação net.aazj.enums.dataSources; classe pública DataSourCetyManager {private Static Final ThreadLocal <DataSources> DataSourCetypes = new ThreadLocal <DataSourceces> () {@Override Protected DataSources InitialValue () {return DataSourcececes.Master; }}; public static datasources get () {return dataSourCetypes.get (); } public static void Set (DataSources DataSourCetype) {DataSourCetypes.Set (DataSourCetype); } public static void reset () {DataSourCetypes.Set (DataSources.master0); }} 3) Definir ThreadLocalRountingDataSource e herdar abstractroutingDataSource:
pacote net.aazj.util; importar org.springframework.jdbc.dataSource.lookup.abstractroutingDataSource; classe pública threadlocalrountingDataSource estende abstractroutingDataSource {@Override Protected Object determineCurrentLookKey () {return datasourCetyManager.get (); }}4) Injetar fontes de dados mestre e escravo em threadlocalrountingDataSource no arquivo de configuração:
<Contexto: Componente-Scan Base-Package = "NET.AAZJ.SERVICE, NET.AAZJ.AOP" /> <Contexto: Componente-Scan Base-Package = "net.aazj.aop" /> <!-Introduce Arquivos de propriedade-> <Contextion: PROPRIEDADE-PLATEDER LOCAL = "Classe: Data: Config /DB.Properties" /> <! init-method = "init" Destro-Method = "Close"> <Propriedade name = "url" value = "$ {jdbc_url}" /> <propriedade name = "nome de usuário" value = "$ {jdbc_username}" /> <nome da propriedade "" seveda = "$ {jdbc_Umername}" /> value = "0" /> <!-Número máximo de conexões usadas pelo pool de conexões-> <nome da propriedade = "maxactive" value = "20" /> <!-Número máximo de conexões disponíveis-> <nome da propriedade = "maxidle" value = "20" /> <!-Número mínimo de conexões disponíveis-> <o nome da propriedade = "minidle" "0" " /> <! name = "maxwait" value = "60000" /> < /bean> <!-Configurar escravo da fonte de dados-> <nome do bean = "DataSourcesLave" init-method = "init" Destroy-method = "Close"> <Nome da propriedade = "url" = "$ {jdbc_url_slave"> <nome da propriedade = "url" = "$ {jdbc_url_slave"> value = "$ {jdbc_username_slave}" /> <propriedade name = "senha" value = "$ {jdbc_password_slave}" /> <!-inicialize tamanho de conexão-> <propriedades name = "initialsize" value = "0" /> <!-máximo de conexões usadas pelo pool de conexões = <weonSize "mamal =" 0 " /!-! <propriedade name = "maxidle" value = "20" /> <!-pool de conexão ocioso mínimo-> <propriedade name = "minidle" value = "0" /> <!-Obtenha tempo de espera de conexão máxima-> <names de names = "maxwait" value = "60000" /> < /bean> <bEan = "datasource"> <weead = names = "60000" /> < /bean> <bEan = "datasource"> <weengy = names = "60000" /> < /bean> <BEAN = "DataSource">> /> <propriedade name = "TargetDataSources"> <mapa key-type = "net.aazj.enums.datasources"> <entradas key = "master" value-ref = "datasourcemaster"/> <! id = "sqlSessionFactory"> <propriedade name = "DataSource" ref = "DataSource"/> <propriedade name = "configLocation" value = "classPath: config/mybatis-config.xml"/> <names name = "mapperlocations" = "ClassPath*: config/! JDBC DataSource-> <bean id = "transactionManager"> <propriedade name = "DataSource" ref = "DataSource" /> < /bean> <!-Definição de transações usando anotações-> <-norma <-nortation driven-man-manyer = "transactionManager" /> <ikean> <nome da propriedade = " name = "sqlSessionFactoryBeanName" value = "sqlSessionFactory"/> -> </i bean> No arquivo de configuração da primavera acima, definimos o DataSourCemaster e o DataSourcesLave para o banco de dados Master e o Banco de Dados de Escravo, respectivamente, e depois o injeta no <Bean Id = "DataSource"> para que nosso DataSource possa selecionar DataSourceMaster e DataSourcesLave de acordo com as diferentes teclas.
5) Use a Spring AOP para especificar a chave do DataSource, para que o DataSource selecione DataSourCemaster e DataSourcesLave de acordo com a chave:
pacote net.aazj.aop; importação net.aazj.enums.datasources; importação net.aazj.util.dataSourCetyManager; importar org.aspectj.lang.joinpoint; importar org.aspectj.lang.annotation.aspect; importar org.aspectj.lang.annotation.before; importar org.aspectj.lang.annotation.pointcut; importar org.springframework.sterotype.component; @Aspect // para aOP @componente // para classe automática de scanPublic DataSourceInterceptor {@PointCut ("Execution (public * net.aazj.service .. *. GetUser (..))") public void DataSourcecececeslave () {}; @Before ("DataSourcesLave ()") public vazio antes (junção JP) {DataSourCetyManager.Set (DataSources.Slave); } // ...} Aqui definimos uma classe de aspecto. Usamos @, antes de chamar DataSourCetyManager.Set (DataSources.Slave) antes do método em conformidade com @PointCut ("Execution (public * net.aazj.service .. *. GetUser (..)) é chamado DataSources e o tipo de chave é definido para DataSources. Portanto, as instruções SQL para este método serão executadas no banco de dados de escravos.
Podemos expandir continuamente o aspecto do DataSourceInterceptor e fazer várias definições para especificar a fonte de dados correspondente à fonte de dados apropriada para um determinado método de serviço.
Dessa forma, podemos usar as poderosas funções da Spring AOP para configurá -la com muita flexibilidade.
6) Análise do princípio do abstractroutingDataSource
ThreadlocalRountingDataSource herda abstractRoutingDataSource, implementando seu método abstrato protegido de objeto abstrato determinamLeRentLookupKey (); implementando assim a função de roteamento para diferentes fontes de dados. Vamos começar com o código -fonte para analisar os princípios:
Classe abstrata pública abstractroutingDataSource estende o abstratoDataSource implementa Inicializando o BEANABSTRACTROUTingDataSource implementa Inicializando oBean. Então, quando a primavera inicializa o feijão, ele chamará a interface do InitializingBean Void AfterPropertiESSET () lança exceção; Vamos ver como o abstractroutingDataSource implementa essa interface: @Override public void depoispropertiesset () {if (this.TargetDataSources == NULL) {lançar novo ilegalargumentException ("Propriedade 'TargetDataSources' é exigido"); } this.ResolVedDataSources = new Hashmap <Object, DataSource> (this.TargetDataSources.size ()); para (map.entry <objeto, objeto> Entrada: this.targetDatasources.entrySet ()) {objeto lookupKey = resolvespeciifiedlowslowKey (Entry.getKey ()); DataSource DataSource = ResolvesPecifiedDataSource (Entry.getValue ()); this.ResolvedDataSources.put (LookUpKey, DataSource); } if (this.DefaultTargetDataSource! = null) {this.ResolvedDefaultDataSource = resolvesPecifiedDataSource (this.DefaultTargetDataSource); }} TargetDatasources é o DataSourCemaster e o DataSourcesLave que injetaremos no arquivo de configuração XML. O método AfterPropertiesset é injetado.
DataSourCemaster e DataSourcesLave para construir um hashmap - ResolvedDataSources. É conveniente obter a fonte de dados correspondente do mapa de acordo com a chave posteriormente.
Vamos dar uma olhada em como a conexão getConnection () lança sqlexception; Na interface abstractDataSource é implementada:
@Override Public Connection getConnection () lança sqLexception {return determineTargetDatasource (). GetConnection (); }A chave é determinareTargetDataSource (), que pode ser vista com base no nome do método, e devemos decidir qual DataSource usar aqui:
DataSource protegido DeconmarETargetDataSource () {Assert.NotNull (this.ResolvedDataSources, "DataSource Router não inicializado"); Objeto LookUpKey = DetermineCurrentLookupKey (); DataSource DataSource = this.ResolvedDataSources.get (LookUpKey); if (DataSource == null && (this.lenientfallback || lookupKey == null)) {DataSource = this.resolvedDefaultDataSource; } if (DataSource == NULL) {TOME NOVA ILGLELGALSTATEEXCECTION ("Não é possível determinar o DataSource de destino para a chave de pesquisa [" + LookUpKey + "]"); } retornar DataSource;} Objeto LookUpKey = DetermineCurrentLookupKey (); Este método é implementado por nós, onde obtemos o valor -chave salvo no Threadlocal. Depois de obter a chave, obtenha a fonte de dados correspondente à chave no mapa inicializada ResolvEDDataSources a partir de AfterPropertiESSET (). O valor da chave salvo no Threadlocal é definido antes de chamar os métodos relevantes no serviço através da AOP. Ok, aqui está feito!
3. Resumo
A partir deste artigo, podemos experimentar o poder e a flexibilidade da AOP.
O exposto acima é a classificação de informações do processamento de origem multi-dados do Mybatis. Espero que possa ajudar os amigos necessitados