早些时候,我们探索了使用 Spring Data JPA 和 Hibernate 加入不相关实体并将结果映射到 POJO 的各种方法。在本文中,我们将在 Spring Boot 应用程序中配置多个数据库、实体管理器、事务管理器和 Hikari 连接池。
这就是我们的项目结构的样子。 模型和存储库包是特定于数据库的。这样我们就可以在特定于数据库的配置类中指定相应的包,用于扫描实体和存储库。多个数据库的实体和存储库不应放在同一个包中。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.javachinna</groupId>
<artifactId>multiple-datasources</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>multiple-datasources</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>11</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
假设我们有 2 个不同的数据库,即 PRIME 和 PRODUCT。 PRIME 是用于用户管理的主数据库,而 PRODUCT 用于产品管理。现在让我们在 Spring Boot 应用程序中配置这两个数据库。此外,我们将配置 Hikari CP,因为它是 Spring Boot 2.x 使用的默认连接池
### Prime Database Details
app.datasource.prime.url=jdbc:mysql://localhost:3306/prime?createDatabaseIfNotExist=true
app.datasource.prime.username=root
app.datasource.prime.password=secret
app.datasource.prime.driver-class-name=com.mysql.cj.jdbc.Driver
### Prime Database Connection Pool Details
app.datasource.prime.hikari.idle-timeout=10000
app.datasource.prime.hikari.maximum-pool-size=10
app.datasource.prime.hikari.minimum-idle=5
app.datasource.prime.hikari.pool-name=PrimeHikariPool
### Product Database Details
app.datasource.product.url=jdbc:mysql://localhost:3306/product?createDatabaseIfNotExist=true
app.datasource.product.username=root
app.datasource.product.password=secret
app.datasource.product.driver-class-name=com.mysql.cj.jdbc.Driver
### Product Database Connection Pool Details
app.datasource.product.hikari.idle-timeout=10000
app.datasource.product.hikari.maximum-pool-size=10
app.datasource.product.hikari.minimum-idle=5
app.datasource.product.hikari.pool-name=ProductHikariPool
# Hibernate props
hibernate.hbm2ddl.auto=create
hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect
@Primary
注释表示当多个候选者有资格自动装配单值依赖项时,应该优先考虑一个 bean。如果候选中恰好存在一个“主要”bean,它将是自动装配的值。简单来说,它用于将 bean 标记为默认 bean。当定义了多个相同类型的 bean 并且我们想在不指定其名称的情况下注入 bean 时,这很有用。
package com.javachinna.config;
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@EnableJpaRepositories(basePackages = "com.javachinna.repo.prime", entityManagerFactoryRef = "primeEntityManager", transactionManagerRef = "primeTransactionManager")
public class PrimeDataSourceConfiguration {
@Autowired
Environment env;
@Bean
@Primary
@ConfigurationProperties(prefix = "app.datasource.prime")
public DataSourceProperties primeDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
@Primary
public DataSource primeDataSource() {
return primeDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean primeEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primeDataSource());
em.setPackagesToScan("com.javachinna.model.prime");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
@Bean
@Primary
public PlatformTransactionManager primeTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(primeEntityManager().getObject());
return transactionManager;
}
}
请注意,我们分别指定了 com.javachinna.repo.prime
和 com.javachinna.model.prime
作为扫描 JPA 存储库和 PRIME 数据库实体的包名称。
package com.javachinna.config;
import java.util.HashMap;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import com.zaxxer.hikari.HikariDataSource;
@Configuration
@EnableJpaRepositories(basePackages = "com.javachinna.repo.product", entityManagerFactoryRef = "productEntityManager", transactionManagerRef = "productTransactionManager")
public class ProductDataSourceConfiguration {
@Autowired
Environment env;
@Bean
@ConfigurationProperties(prefix = "app.datasource.product")
public DataSourceProperties productDataSourceProperties() {
return new DataSourceProperties();
}
@Bean
public DataSource productDataSource() {
return productDataSourceProperties().initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
@Bean
public LocalContainerEntityManagerFactoryBean productEntityManager() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(productDataSource());
em.setPackagesToScan("com.javachinna.model.product");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("hibernate.dialect"));
em.setJpaPropertyMap(properties);
return em;
}
@Bean
public PlatformTransactionManager productTransactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(productEntityManager().getObject());
return transactionManager;
}
}
请注意,我们已分别指定 com.javachinna.repo.product
和 com.javachinna.model.product
作为用于扫描 JPA 存储库和 PRODUCT 数据库实体的包名称。
这个 POJO 类用于从 prime
数据库中的本机查询结果集中映射用户信息。
package com.javachinna.model.prime;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserInfo {
private String id;
private String name;
}
#####角色.java
此实体映射到 prime
数据库中的 role
表
package com.javachinna.model.prime;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* The persistent class for the role database table.
*
*/
@Entity
@Getter
@Setter
@NoArgsConstructor
public class Role implements Serializable {
private static final long serialVersionUID = 1L;
public static final String USER = "USER";
public static final String ADMIN = "ADMIN";
public static final String ROLE_USER = "ROLE_USER";
public static final String ROLE_ADMIN = "ROLE_ADMIN";
@Id
@Column(name = "ROLE_ID")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int roleId;
private String name;
// bi-directional many-to-many association to User
@ManyToMany(mappedBy = "roles")
private Set<User> users;
public Role(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
return Objects.equals(name, ((Role) obj).getName());
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("Role [name=").append(name).append("]").append("[id=").append(roleId).append("]");
return builder.toString();
}
}
该实体映射到 prime
数据库中的 user
表
package com.javachinna.model.prime;
import java.util.Set;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.SqlResultSetMapping;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@NoArgsConstructor
@SqlResultSetMapping(name = "UserInfoMapping", classes = @ConstructorResult(targetClass = UserInfo.class, columns = {@ColumnResult(name = "user_id", type = String.class),
@ColumnResult(name = "username", type = String.class)}))
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "USER_ID")
private Long id;
private String username;
private String password;
// bi-directional many-to-many association to Role
@ManyToMany
@JoinTable(name = "user_role", joinColumns = {@JoinColumn(name = "USER_ID")}, inverseJoinColumns = {@JoinColumn(name = "ROLE_ID")})
private Set<Role> roles;
/**
* @param username
* @param password
*/
public User(String username, String password, Set<Role> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
}
这个 POJO 类用于从 product
数据库中的本机 SQL 查询结果集中映射产品信息。
package com.javachinna.model.product;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProductInfo {
private String id;
private String name;
private String price;
}
该实体映射到 product
数据库中的 product
表
package com.javachinna.model.product;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SqlResultSetMapping;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Entity
@NoArgsConstructor
@SqlResultSetMapping(name = "ProductInfoMapping", classes = @ConstructorResult(targetClass = ProductInfo.class, columns = {@ColumnResult(name = "id", type = String.class),
@ColumnResult(name = "name", type = String.class), @ColumnResult(name = "price", type = String.class)}))
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String price;
/**
* @param name
* @param price
*/
public Product(String name, String price) {
this.name = name;
this.price = price;
}
}
我们将分别为 User 和 Role 表创建 User 和 Role 存储库,以演示对一个数据库中多个表的 CRUD 操作。
package com.javachinna.repo.prime;
import org.springframework.data.jpa.repository.JpaRepository;
import com.javachinna.model.prime.Role;
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
package com.javachinna.repo.prime;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.javachinna.model.prime.User;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String userName);
}
这只是一个 DAO 类,用于演示如何使用 @Qualifier
注释在不指定名称 primeEntityManager
的情况下注入主实体管理器。
package com.javachinna.repo.prime;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.hibernate.procedure.ProcedureOutputs;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.javachinna.model.prime.UserInfo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Repository
@Transactional
public class UserInfoRepository {
@PersistenceContext
private EntityManager entityManager;
@SuppressWarnings("unchecked")
public List<UserInfo> getUerInfo() {
List<UserInfo> list = new ArrayList<>();
Query query = entityManager.createNativeQuery("select user_id, username from user", "UserInfoMapping");
try {
// Execute query
list = query.getResultList();
} catch (Exception e) {
log.error("Error while querying the db", e);
} finally {
try {
query.unwrap(ProcedureOutputs.class).release();
} catch (Exception e) {
}
}
return list;
}
}
请注意,我们使用 @Transactional
注释了这个类,而没有指定事务管理器名称。这意味着默认情况下将使用主事务管理器。
这是用于辅助数据库中的产品 CRUD 操作的 JPA 存储库类。
@Transactional
表示执行更新查询需要一个事务。这里我们不需要显式指定事务管理器,因为我们已经在 ProductDataSourceConfiguration
类中使用 @EnableJpaRepositories
注释指定了 productTransactionManager
。
@Modifying
注释表明查询方法应该被视为修改查询,因为它改变了它需要执行的方式。只有在通过 @Query
注释定义的查询方法上使用时才考虑此注释。
package com.javachinna.repo.product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.javachinna.model.product.Product;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Transactional
@Modifying
@Query("update Product set name=:name where id=:id")
void updateProduct(String name, Long id);
}
这只是一个 DAO 类,用于演示如何使用 @Qualifier
注释自动连接辅助实体管理器并用于查询辅助数据库。
package com.javachinna.repo.product;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.hibernate.procedure.ProcedureOutputs;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import com.javachinna.model.product.ProductInfo;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Repository
public class ProductInfoRepository {
@Autowired
@Qualifier("productEntityManager")
private EntityManager entityManager;
@SuppressWarnings("unchecked")
public List<ProductInfo> getProductInfo() {
List<ProductInfo> list = new ArrayList<>();
Query query = entityManager.createNativeQuery("select id, name, price from product", "ProductInfoMapping");
try {
// Execute query
list = query.getResultList();
} catch (Exception e) {
log.error("Error while querying the db", e);
} finally {
try {
query.unwrap(ProcedureOutputs.class).release();
} catch (Exception e) {
}
}
return list;
}
@Transactional("productTransactionManager")
public void updateProductInfo(String name, Long id) {
Query query = entityManager.createQuery("update Product set name=:name where id=:id");
try {
// Execute query
query.setParameter("name", name);
query.setParameter("id", id);
query.executeUpdate();
} catch (Exception e) {
log.error("Error while querying the db", e);
} finally {
try {
query.unwrap(ProcedureOutputs.class).release();
} catch (Exception e) {
}
}
};
}
请注意,我们用 @Transactional("productTransactionManager")
注释对 updateProductInfo()
方法进行了注释,这意味着 productTransactionManager
将在此处使用。
package com.javachinna.multipledatasources;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.javachinna")
public class MultipleDatasourcesApplication {
public static void main(String[] args) {
SpringApplication.run(MultipleDatasourcesApplication.class, args);
}
}
@TestInstance(Lifecycle.PER_CLASS)
注释用于将测试实例生命周期模式设置为 PER_CLASS
,这意味着测试实例状态在给定测试类中的测试方法之间以及非静态 [[$35$] 之间共享]] 和 @AfterAll
方法在测试类中。如果我们不使用这个注解,那么我们就不能在非静态方法上使用 @BeforeAll
注解。如果我们将 init
方法设为静态,那么我们就不能在其中使用自动装配的存储库。
package com.javachinna.multipledatasources;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import java.util.Set;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.javachinna.model.prime.Role;
import com.javachinna.model.prime.User;
import com.javachinna.model.prime.UserInfo;
import com.javachinna.repo.prime.RoleRepository;
import com.javachinna.repo.prime.UserInfoRepository;
import com.javachinna.repo.prime.UserRepository;
@SpringBootTest
@TestInstance(Lifecycle.PER_CLASS)
class PrimeDataSourceTests {
@Autowired
private RoleRepository roleRepository;
@Autowired
private UserRepository userRepository;
@Autowired
private UserInfoRepository userInfoRepository;
@BeforeAll
public void init() {
Role userRole = roleRepository.save(new Role(Role.USER));
userRepository.save(new User("test", "secret", Set.of(userRole)));
}
@Test
public void getUserTest() {
List<User> list = userRepository.findAll();
assertThat(list).isNotEmpty();
}
@Test
public void getUserInfoTest() {
List<UserInfo> list = userInfoRepository.getUerInfo();
assertThat(list).isNotEmpty();
assertThat(list.get(0).getName()).isEqualTo("test");
}
}
package com.javachinna.multipledatasources;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import java.util.Optional;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.TestInstance.Lifecycle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.javachinna.model.product.Product;
import com.javachinna.model.product.ProductInfo;
import com.javachinna.repo.product.ProductInfoRepository;
import com.javachinna.repo.product.ProductRepository;
@SpringBootTest
@TestInstance(Lifecycle.PER_CLASS)
class ProductDataSourceTests {
@Autowired
private ProductRepository productRepository;
@Autowired
private ProductInfoRepository productInfoRepository;
@BeforeAll
public void init() {
Product product = new Product("phone", "secret");
product = productRepository.save(product);
}
@Test
public void getProductTest() {
List<Product> list = productRepository.findAll();
assertThat(list).isNotEmpty();
}
@Test
public void getProductInfoTest() {
List<ProductInfo> list = productInfoRepository.getProductInfo();
assertThat(list).isNotEmpty();
assertThat(list.get(0).getName()).isEqualTo("phone");
}
@Test
public void updateProductTest() {
productRepository.updateProduct("smartphone", 1L);
Optional<Product> product = productRepository.findById(1L);
assertThat(product.get().getName()).isEqualTo("smartphone");
}
@Test
public void updateProductInfoTest() {
productInfoRepository.updateProductInfo("cellphone", 1L);
Optional<Product> product = productRepository.findById(1L);
assertThat(product.get().getName()).isEqualTo("cellphone");
}
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://www.javachinna.com/spring-boot-multiple-data-sources/
内容来源于网络,如有侵权,请联系作者删除!