在之前的文章中我們已經(jīng)介紹了SpringJDBC的多數(shù)據(jù)源實(shí)現(xiàn)(在一個(gè)項(xiàng)目中操作多個(gè)數(shù)據(jù)庫),比較常見的多數(shù)據(jù)源的支持方式有兩種:
把數(shù)據(jù)源作為參數(shù)傳入到調(diào)用方法內(nèi)部,需要我們手動(dòng)傳參。
不同的包下面的接口函數(shù)自動(dòng)注入不同的數(shù)據(jù)源。
第一種方法比較麻煩,會(huì)增加額外的代碼。所以我們使用第二種方式來實(shí)現(xiàn)JPA的多數(shù)據(jù)源支持。
首先來修改.yml文件,配置兩個(gè)數(shù)據(jù)源:
server: port: 8888spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: Family: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: mysql://localhost:3306/Family?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 Family2: driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: mysql://localhost:3306/Family2?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 jpa: # Hibernate 創(chuàng)建數(shù)據(jù)庫表的時(shí)候,默認(rèn)使用的數(shù)據(jù)庫存儲(chǔ)引擎是 MyISAM # database-platform在建表的時(shí)候?qū)⒋鎯?chǔ)引擎切換為 InnoDB database-platform: org.hibernate.dialect.MySQL5InnoDBDialect hibernate: # 在Hibernate每次加載的時(shí)候,都會(huì)驗(yàn)證數(shù)據(jù)庫中的表結(jié)構(gòu)是否跟model類中字段的定義是一致的,如果不一致就拋出異常 ddl-auto: validate naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl database: mysql # 在日志打印出執(zhí)行的sql語句 show-sql: true1234567891011121314151617181920212223242526272829303132復(fù)制代碼類型:[java]
在Dao文件下創(chuàng)建文件夾db和db2,將model中的Pets.java移動(dòng)到db文件下并把Dao文件下原有的PetsRepository也移動(dòng)到db文件夾下。在db2下新創(chuàng)建一個(gè)實(shí)體類Doctor和DoctorRepository接口:
package com.javafamily.familydemo.dao.db2;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;import javax.persistence.*;@Data@Entity@Builder@Table(name = "doctor")public class Doctor { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private long id; @Column(nullable = false) private String name; @Column(nullable = false) private String title; }123456789101112131415161718192021222324復(fù)制代碼類型:[java]
package com.javafamily.familydemo.dao.db2;import com.javafamily.familydemo.model.Pets;import org.springframework.data.jpa.repository.JpaRepository;public interface DoctorRepository extends JpaRepository<Doctor, Long> { }12345678復(fù)制代碼類型:[java]
現(xiàn)在我們需要實(shí)現(xiàn)將Family數(shù)據(jù)源給PetsRepository使用,Family2數(shù)據(jù)源給db2使用。
接下來我們來實(shí)現(xiàn)配置:在config文件下創(chuàng)建JPAFamilyConfig.java和JPAFamily2Config.java
package com.javafamily.familydemo.config;import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.Primary;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;import javax.persistence.EntityManager;import javax.sql.DataSource;import java.util.Map;@Configuration@EnableTransactionManagement@EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactoryFamily", transactionManagerRef = "transactionManagerFamily", // 換成你自己的Repository所在位置 basePackages = {"com.javafamily.familydemo.dao.db"})public class JPAFamilyConfig { @Resource private JpaProperties jpaProperties; @Primary @Bean(name = "familyDataSource") @ConfigurationProperties(prefix = "spring.datasource.family") public DataSource familyDataSource() { return DataSourceBuilder.create().build(); } @Primary // 實(shí)體管理器 @Bean(name = "entityManagerFamily") public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactoryFamily(builder).getObject().createEntityManager(); } @Primary // 實(shí)體工廠 @Bean(name = "entityManagerFactoryFamily") public LocalContainerEntityManagerFactoryBean entityManagerFactoryFamily(EntityManagerFactoryBuilder builder) { return builder.dataSource(familyDataSource()) .properties(jpaProperties.getProperties()) // 換成你自己的實(shí)體類所在位置 .packages("com.javafamily.familydemo.dao.db") .persistenceUnit("familyPersistenceUnit") .build(); } @Primary // 事務(wù)管理器 @Bean(name = "transactionManagerFamily") public PlatformTransactionManager transactionManagerFamily(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactoryFamily(builder).getObject()); } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768復(fù)制代碼類型:[java]
package com.javafamily.familydemo.config;import org.springframework.boot.autoconfigure.orm.jpa.JpaProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jdbc.DataSourceBuilder;import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaTransactionManager;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.annotation.Resource;import javax.persistence.EntityManager;import javax.sql.DataSource;@Configuration@EnableTransactionManagement@EnableJpaRepositories( entityManagerFactoryRef = "entityManagerFactoryFamily2", transactionManagerRef = "transactionManagerFamily2", // 換成你自己的Repository所在位置 basePackages = {"com.javafamily.familydemo.dao.db2"})public class JPAFamily2Config { @Resource private JpaProperties jpaProperties; @Bean(name = "family2DataSource") @ConfigurationProperties(prefix = "spring.datasource.family2") public DataSource family2DataSource() { return DataSourceBuilder.create().build(); } @Bean(name = "entityManagerFamily2") public EntityManager entityManager(EntityManagerFactoryBuilder builder) { return entityManagerFactoryFamily2(builder).getObject().createEntityManager(); } @Bean(name = "entityManagerFactoryFamily2") public LocalContainerEntityManagerFactoryBean entityManagerFactoryFamily2(EntityManagerFactoryBuilder builder) { return builder.dataSource(family2DataSource()) .properties(jpaProperties.getProperties()) // 換成你自己的實(shí)體類所在位置 .packages("com.javafamily.familydemo.dao.db2") .persistenceUnit("family2PersistenceUnit") .build(); } @Bean(name = "transactionManagerFamily2") public PlatformTransactionManager transactionManagerFamily2(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(entityManagerFactoryFamily2(builder).getObject()); } }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758復(fù)制代碼類型:[java]
完成上述配置后,再編寫測(cè)試類對(duì)多數(shù)據(jù)源進(jìn)行測(cè)試。
package com.javafamily.familydemo;import com.javafamily.familydemo.dao.db.Pets;import com.javafamily.familydemo.dao.db.PetsRepository;import com.javafamily.familydemo.dao.db2.Doctor;import com.javafamily.familydemo.dao.db2.DoctorRepository;import org.junit.jupiter.api.Test;import org.junit.jupiter.api.extension.ExtendWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit.jupiter.SpringExtension;import javax.annotation.Resource;import java.util.Date;@ExtendWith(SpringExtension.class)@SpringBootTestpublic class KeyWordsTest { @Resource private PetsRepository petsRepository; @Resource private DoctorRepository doctorRepository; @Test public void Test() { Pets pets = petsRepository.findPetsByName("fish"); System.out.println(pets); } @Test public void jpaTest() { Pets pets = Pets.builder() .id(2L) .name("JavaFamily") .varieties("chai") .createTime(new Date()) .build(); Doctor doctor = Doctor.builder() .name("petsDoctor").title("director").build(); // 先構(gòu)造一個(gè)Pets對(duì)象pets,這個(gè)操作針對(duì)db petsRepository.save(pets); //在構(gòu)造一個(gè)Doctor對(duì)象doctor,這個(gè)操作針對(duì)db2 doctorRepository.save(doctor); } }1234567891011121314151617181920212223242526272829303132333435363738394041424344454647復(fù)制代碼類型:[java]
執(zhí)行代碼,查看數(shù)據(jù)庫會(huì)發(fā)現(xiàn)兩張表的數(shù)據(jù)都已經(jīng)插入成功。
當(dāng)有異常發(fā)生時(shí)按照正常的邏輯,兩條數(shù)據(jù)都應(yīng)該插入失敗。
我們先將數(shù)據(jù)庫中之前的數(shù)據(jù)全部清除,之后改寫PetsServiceImpl.java的save方法,并且添加分母為0的異常。
@Resource private PetsRepository petsRepository; @Resource private DoctorRepository doctorRepository; @Resource private Mapper dozerMapper; @Transactional public void savePets(PetsVO pets) { Pets petsPO = dozerMapper.map(pets, Pets.class); // 通過insert,保存一個(gè)對(duì)象 petsRepository.save(petsPO); doctorRepository.save(new Doctor(3,"Family2","doctor")); int num = 1/0; }1234567891011121314151617復(fù)制代碼類型:[java]
執(zhí)行代碼,在postman中添加請(qǐng)求:
得到報(bào)錯(cuò)后,查看數(shù)據(jù)庫。
第一個(gè)數(shù)據(jù)庫沒有數(shù)據(jù)傳入。
第二個(gè)數(shù)據(jù)庫有數(shù)據(jù)傳入。
這是因?yàn)閿?shù)據(jù)庫事物不能跨鏈接,數(shù)據(jù)源更不能跨庫。如果出現(xiàn)了上述操作那這個(gè)事務(wù)就變成了分布式事務(wù),需要一個(gè)統(tǒng)一協(xié)調(diào)的管理器。下面讓我們來實(shí)現(xiàn)JPA+atomikos實(shí)現(xiàn)分布式事務(wù)。
首先引入maven依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency>1234復(fù)制代碼類型:[java]
之后改寫.yml文件:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 datasource: family: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/Family?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 family2: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/Family2?useUnicode=true&characterEncoding=utf-8&useSSL=false username: root password: 123456 # jta表示分布式事物 jta: atomikos: datasource: # 數(shù)據(jù)池最大連接數(shù) max-pool-size: 30 # 超出設(shè)定時(shí)間拋出異常 borrow-connection-timeout: 100 connectionfactory: max-pool-size: 30 borrow-connection-timeout: 10012345678910111213141516171819202122232425262728復(fù)制代碼類型:[java]
注:將jdbc-url還原成url
在config文件下創(chuàng)建AtomikosJtaPlatform.java,這部分的代碼為固定代碼。
package com.javafamily.familydemo.config;import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;import javax.transaction.TransactionManager;import javax.transaction.UserTransaction;public class AtomikosJtaPlatform extends AbstractJtaPlatform { private static final long serialVersionUID = 1L; static TransactionManager transactionManager; static UserTransaction transaction; @Override protected TransactionManager locateTransactionManager() { return transactionManager; } @Override protected UserTransaction locateUserTransaction() { return transaction; } }12345678910111213141516171819202122232425復(fù)制代碼類型:[java]
再來進(jìn)行事物管理器配置。在config文件夾下創(chuàng)建JPAAtomikosTransactionConfig.java:
package com.javafamily.familydemo.config;import com.atomikos.icatch.jta.UserTransactionImp;import com.atomikos.icatch.jta.UserTransactionManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.vendor.Database;import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import org.springframework.transaction.jta.JtaTransactionManager;import javax.transaction.TransactionManager;import javax.transaction.UserTransaction;@Configuration@ComponentScan@EnableTransactionManagementpublic class JPAAtomikosTransactionConfig { @Bean public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() { return new PropertySourcesPlaceholderConfigurer(); } // JPA特性 @Bean public JpaVendorAdapter jpaVendorAdapter() { HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter(); hibernateJpaVendorAdapter.setShowSql(true); hibernateJpaVendorAdapter.setGenerateDdl(true); hibernateJpaVendorAdapter.setDatabase(Database.MYSQL); return hibernateJpaVendorAdapter; } @Bean(name = "userTransaction") public UserTransaction userTransaction() throws Throwable { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(5000); return userTransactionImp; } @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close") public TransactionManager atomikosTransactionManager() throws Throwable { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); AtomikosJtaPlatform.transactionManager = userTransactionManager; return userTransactionManager; } @Bean(name = "transactionManager") @DependsOn({"userTransaction", "atomikosTransactionManager"}) public PlatformTransactionManager transactionManager() throws Throwable { UserTransaction userTransaction = userTransaction(); AtomikosJtaPlatform.transaction = userTransaction; TransactionManager atomikosTransactionManager = atomikosTransactionManager(); return new JtaTransactionManager(userTransaction, atomikosTransactionManager); } }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364復(fù)制代碼類型:[java]
設(shè)置第一個(gè)數(shù)據(jù)庫Family的數(shù)據(jù)源和實(shí)體掃描管理:
package com.javafamily.familydemo.config;import com.mysql.cj.jdbc.MysqlXADataSource;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.context.annotation.Primary;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import javax.annotation.Resource;import javax.sql.DataSource;import java.sql.SQLException;import java.util.HashMap;@Configuration@DependsOn("transactionManager")@EnableJpaRepositories(basePackages = "com.javafamily.familydemo.dao.db", entityManagerFactoryRef = "familyEntityManager", transactionManagerRef = "transactionManager")public class JPAFamilyConfig { @Resource private JpaVendorAdapter jpaVendorAdapter; @Primary @Bean @ConfigurationProperties(prefix = "spring.datasource.family") public DataSourceProperties familyDataSourceProperties() { return new DataSourceProperties(); } @Primary @Bean @ConfigurationProperties(prefix = "spring.datasource.family") public DataSource familyDataSource() throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(familyDataSourceProperties().getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(familyDataSourceProperties().getPassword()); mysqlXaDataSource.setUser(familyDataSourceProperties().getUsername()); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("family"); xaDataSource.setBorrowConnectionTimeout(60); xaDataSource.setMaxPoolSize(20); return xaDataSource; } @Primary @Bean @DependsOn("transactionManager") public LocalContainerEntityManagerFactoryBean familyEntityManager() throws Throwable { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName()); properties.put("javax.persistence.transactionType", "JTA"); LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean(); entityManager.setJtaDataSource(familyDataSource()); entityManager.setJpaVendorAdapter(jpaVendorAdapter); //這里要修改成主數(shù)據(jù)源的掃描包 entityManager.setPackagesToScan("com.javafamily.familydemo.dao.db"); entityManager.setPersistenceUnitName("familyPersistenceUnit"); entityManager.setJpaPropertyMap(properties); return entityManager; } }12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970復(fù)制代碼類型:[java]
設(shè)置第二個(gè)數(shù)據(jù)庫Family的數(shù)據(jù)源和實(shí)體掃描管理:
package com.javafamily.familydemo.config;import com.mysql.cj.jdbc.MysqlXADataSource;import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import javax.annotation.Resource;import javax.sql.DataSource;import java.sql.SQLException;import java.util.HashMap;@Configuration@DependsOn("transactionManager")@EnableJpaRepositories(basePackages = "com.javafamily.familydemo.dao.db2", entityManagerFactoryRef = "family2EntityManager", transactionManagerRef = "transactionManager")public class JPAFamily2Config { @Resource private JpaVendorAdapter jpaVendorAdapter; @Bean @ConfigurationProperties(prefix = "spring.datasource.family2") //注意這里 public DataSourceProperties family2DataSourceProperties() { return new DataSourceProperties(); } // @Bean(name = "family2DataSource", initMethod = "init", destroyMethod = "close") @Bean @ConfigurationProperties(prefix = "spring.datasource.family2") public DataSource family2DataSource() throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(family2DataSourceProperties().getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(family2DataSourceProperties().getPassword()); mysqlXaDataSource.setUser(family2DataSourceProperties().getUsername()); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("family2"); xaDataSource.setBorrowConnectionTimeout(60); xaDataSource.setMaxPoolSize(20); return xaDataSource; } @Bean @DependsOn("transactionManager") public LocalContainerEntityManagerFactoryBean family2EntityManager() throws Throwable { HashMap<String, Object> properties = new HashMap<String, Object>(); properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName()); properties.put("javax.persistence.transactionType", "JTA"); LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean(); entityManager.setJtaDataSource(family2DataSource()); entityManager.setJpaVendorAdapter(jpaVendorAdapter); //這里要修改成主數(shù)據(jù)源的掃描包 entityManager.setPackagesToScan("com.javafamily.familydemo.dao.db2"); entityManager.setPersistenceUnitName("family2PersistenceUnit"); entityManager.setJpaPropertyMap(properties); return entityManager; } }123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869復(fù)制代碼類型:[java]
通過以上代碼我們會(huì)發(fā)現(xiàn),除了事物管理器只有一個(gè)以外,其他都是兩個(gè)。不同的數(shù)據(jù)源通過同一個(gè)事物管理器實(shí)現(xiàn)了分布式事務(wù)。
這時(shí)我們?cè)俅螆?zhí)行代碼,會(huì)發(fā)現(xiàn)報(bào)錯(cuò)之后兩個(gè)數(shù)據(jù)庫都沒有數(shù)據(jù)庫插入。
當(dāng)把錯(cuò)誤代碼去除后(intnum=1/0;)再次執(zhí)行,兩個(gè)數(shù)據(jù)庫的數(shù)據(jù)均已插入。
聯(lián)系客服