Faz*_*zia 5 java spring hibernate multi-tenant
我有一个具有基础数据库 (Oracle) 的应用程序。它从基本数据库中的表中获取另一个租户数据库连接字符串。这些租户可以是 Oracle、Postgres 或 MSSQL。
当应用程序启动时,方言被设置为org.hibernate.dialect.SQLServerDialect基本数据库的休眠状态。但是当我尝试在 MSSQL 数据库的租户中插入数据时,它在插入数据时抛出错误。com.microsoft.sqlserver.jdbc.SQLServerException: DEFAULT 或 NULL 不允许作为显式标识值
这是因为它正在为 Oracle 数据库设置 MSSQL 方言。
[WARN ] 2020-01-21 09:16:22.504 [https-jsse-nio-22500-exec-5] [o.h.e.j.s.SqlExceptionHelper] -- SQL Error: 339, SQLState: S0001
[ERROR] 2020-01-21 09:16:22.504 [https-jsse-nio-22500-exec-5] [o.h.e.j.s.SqlExceptionHelper] -- DEFAULT or NULL are not allowed as explicit identity values.
[ERROR] 2020-01-21 09:16:22.535 [https-jsse-nio-22500-exec-5] [o.a.c.c.C.[.[.[.[dispatcherServlet]] -- Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: could not execute statement; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not execute statement] with root cause
com.microsoft.sqlserver.jdbc.SQLServerException: DEFAULT or NULL are not allowed as explicit identity values.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:217)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1655)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:440)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:385)
at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7505)
at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:2445)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:191)
at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:166)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:328)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:57)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3106)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3699)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:335)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:292)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:198)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:128)
at org.hibernate.event.internal.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:192)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:135)
at org.hibernate.event.internal.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:62)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108)
at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:702)
at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:688)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
Run Code Online (Sandbox Code Playgroud)
我有一个TenantIdentifierResolver实现CurrentTenantIdentifierResolver
@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
@Autowired
PropertyConfig propertyConfig;
@Override
public String resolveCurrentTenantIdentifier() {
String tenantId = TenantContext.getCurrentTenant();
if (tenantId != null) {
return tenantId;
}
return propertyConfig.getDefaultTenant();
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
MultiTenantConnectionProviderImpl扩展的组件类AbstractDataSourceBasedMultiTenantConnectionProviderImpl
@Component
public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
@Autowired
private DataSource defaultDS;
@Autowired
PropertyConfig propertyConfig;
@Autowired
TenantDataSourceService tenantDBService;
private Map<String, DataSource> map = new HashMap<>();
boolean init = false;
@PostConstruct
public void load() {
map.put(propertyConfig.getDefaultTenant(), defaultDS);
ConcurrentMap<String,DataSource> tenantList = tenantDBService.getGlobalTenantDataSource(); //gets tenant datasources from service
map.putAll(tenantList);
}
@Override
protected DataSource selectAnyDataSource() {
return map.get(propertyConfig.getDefaultTenant());
}
@Override
protected DataSource selectDataSource(String tenantIdentifier) {
return map.get(tenantIdentifier) != null ? map.get(tenantIdentifier) : map.get(propertyConfig.getDefaultTenant());
}
}
Run Code Online (Sandbox Code Playgroud)
和一个配置类 HibernateConfig
@Configuration
public class HibernateConfig {
@Autowired
private JpaProperties jpaProperties;
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean
LocalContainerEntityManagerFactoryBean entityManagerFactory(
DataSource dataSource,
MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl,
TenantIdentifierResolver currentTenantIdentifierResolverImpl
) {
Map<String, Object> jpaPropertiesMap = new HashMap<>(jpaProperties.getProperties());
jpaPropertiesMap.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
jpaPropertiesMap.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
jpaPropertiesMap.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
//jpaPropertiesMap.put(Environment.DIALECT_RESOLVERS, "com.esq.cms.CashOrderMgmtService.multitenant.CustomDialectResolver");
jpaPropertiesMap.put("hibernate.jdbc.batch_size", 500);
jpaPropertiesMap.put("hibernate.order_inserts", true);
jpaPropertiesMap.put("hibernate.order_updates", true);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.esq.cms.*");
em.setJpaVendorAdapter(this.jpaVendorAdapter());
em.setJpaPropertyMap(jpaPropertiesMap);
return em;
}
}
Run Code Online (Sandbox Code Playgroud)
有许多使用属性文件设置方言的示例,但它们具有固定类型和数量的数据库。就我而言,它可以是任何数据库类型。我也试过为休眠解析器添加一个自定义类,但它仍然无法正常工作。我可能会遗漏一些东西。因此,我应该怎么做才能通过休眠本身根据数据库启用方言。任何帮助都将得到认可。谢谢
当您处理不同类型的数据库(DATABASE例如:Oracle、MySQL 等)时,请尝试采用多租户策略。SCHEMADISCRIMINATOR
根据Hibernate 文档:在多租户系统中分离数据可以采取的方法:
MultiTenancyStrategy.DATABASE) :每个租户的数据都保存在物理上独立的数据库实例中。JDBC 连接将专门指向每个单独的数据库,以便连接池将针对每个单租户。连接池是根据链接到特定用户的“租户标识符”来选择的。
MultiTenancyStrategy.SCHEMA) :每个租户的数据都保存在单个数据库实例上的不同数据库模式中。
MultiTenancyStrategy.DISCRIMINATOR):所有数据仅保存在单个数据库模式中。每个租户的数据通过使用鉴别器进行分区。所有租户使用单个 JDBC 连接池。对于每个 SQL 语句,应用程序需要根据“租户标识符”鉴别器来管理数据库上查询的执行。
根据要求决定您想要采用哪种策略。
我提供了我自己的多租户示例代码(使用 spring-boot),我使用两个不同的数据库完成了该代码,一个使用MySQL,另一个使用Postgres。这是我提供的工作示例。
Github 存储库:工作多租户代码
注意:在数据库中执行任何操作之前先创建表。
我已经使用不同的数据库配置了属性文件(application.properties)中的所有租户。
server.servlet.context-path=/sample
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.show-sql=true
## Tenant 1 database ##
multitenant.datasources.tenant1.url=jdbc:postgresql://localhost:5432/tenant1
multitenant.datasources.tenant1.username=postgres
multitenant.datasources.tenant1.password=Anish@123
multitenant.datasources.tenant1.driverClassName=org.postgresql.Driver
## Tenant 2 database ##
multitenant.datasources.tenant2.url=jdbc:mysql://localhost:3306/tenant2
multitenant.datasources.tenant2.username=root
multitenant.datasources.tenant2.password=Anish@123
multitenant.datasources.tenant2.driverClassName=com.mysql.cj.jdbc.Driver
Run Code Online (Sandbox Code Playgroud)
MultiTenantProperties:此类绑定并验证为多个租户设置的属性,并将它们保存为租户与所需数据库信息的映射。
@Component
@ConfigurationProperties(value = "multitenant")
public class MultiTenantProperties {
private Map<String, Map<String, String>> datasources = new LinkedHashMap<>();
public Map<String, Map<String, String>> getDatasources() {
return datasources;
}
public void setDatasources(Map<String, Map<String, String>> datasources) {
this.datasources = datasources;
}
}
Run Code Online (Sandbox Code Playgroud)
ThreadLocalTenantStorage:此类保留来自当前线程执行 CRUD 操作的传入请求的租户名称。
public class ThreadLocalTenantStorage {
private static ThreadLocal<String> currentTenant = new ThreadLocal<>();
public static void setTenantName(String tenantName) {
currentTenant.set(tenantName);
}
public static String getTenantName() {
return currentTenant.get();
}
public static void clear() {
currentTenant.remove();
}
}
Run Code Online (Sandbox Code Playgroud)
MultiTenantInterceptor:此类拦截传入请求,并为要选择的数据库设置当前租户的 ThreadLocalTenantStorage。请求完成后,租户将从班级中删除ThreadLocalTenantStorage。
public class MultiTenantInterceptor extends HandlerInterceptorAdapter {
private static final String TENANT_HEADER_NAME = "TENANT-NAME";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String tenantName = request.getHeader(TENANT_HEADER_NAME);
ThreadLocalTenantStorage.setTenantName(tenantName);
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
ThreadLocalTenantStorage.clear();
}
}
Run Code Online (Sandbox Code Playgroud)
TenantIdentifierResolver:该类负责返回来自ThreadLocalTenantStorage的当前租户以选择数据源。
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver {
private static String DEFAULT_TENANT_NAME = "tenant1";
@Override
public String resolveCurrentTenantIdentifier() {
String currentTenantName = ThreadLocalTenantStorage.getTenantName();
return (currentTenantName != null) ? currentTenantName : DEFAULT_TENANT_NAME;
}
@Override
public boolean validateExistingCurrentSessions() {
return true;
}
}
Run Code Online (Sandbox Code Playgroud)
WebConfiguration:这是注册MultiTenantInterceptor要用作拦截器的类的配置。
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MultiTenantInterceptor());
}
}
Run Code Online (Sandbox Code Playgroud)
DataSourceMultiTenantConnectionProvider:该类根据租户名称选择数据源。
public class DataSourceMultiTenantConnectionProvider extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 1L;
@Autowired
private Map<String, DataSource> multipleDataSources;
@Override
protected DataSource selectAnyDataSource() {
return multipleDataSources.values().iterator().next();
}
@Override
protected DataSource selectDataSource(String tenantName) {
return multipleDataSources.get(tenantName);
}
}
Run Code Online (Sandbox Code Playgroud)
MultiTenantJPAConfiguration:此类为数据库事务配置自定义bean,并为多租户注册租户数据源。
@Configuration
@EnableJpaRepositories(basePackages = { "com.example.multitenancy.dao" }, transactionManagerRef = "multiTenantTxManager")
@EnableConfigurationProperties({ MultiTenantProperties.class, JpaProperties.class })
@EnableTransactionManagement
public class MultiTenantJPAConfiguration {
@Autowired
private JpaProperties jpaProperties;
@Autowired
private MultiTenantProperties multiTenantProperties;
@Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
return new DataSourceMultiTenantConnectionProvider();
}
@Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
return new TenantIdentifierResolver();
}
@Bean(name = "multipleDataSources")
public Map<String, DataSource> repositoryDataSources() {
Map<String, DataSource> datasources = new HashMap<>();
multiTenantProperties.getDatasources().forEach((key, value) -> datasources.put(key, createDataSource(value)));
return datasources;
}
private DataSource createDataSource(Map<String, String> source) {
return DataSourceBuilder.create().url(source.get("url")).driverClassName(source.get("driverClassName"))
.username(source.get("username")).password(source.get("password")).build();
}
@Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return entityManagerFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager multiTenantTxManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> hibernateProperties = new LinkedHashMap<>();
hibernateProperties.putAll(this.jpaProperties.getProperties());
hibernateProperties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
hibernateProperties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProperties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPackagesToScan("com.example.multitenancy.entity");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setJpaPropertyMap(hibernateProperties);
return entityManagerFactoryBean;
}
}
Run Code Online (Sandbox Code Playgroud)
用于测试的示例实体类:
@Entity
@Table(name = "user_details", schema = "public")
public class User {
@Id
@Column(name = "id")
private Long id;
@Column(name = "full_name", length = 30)
private String name;
public User() {
super();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Run Code Online (Sandbox Code Playgroud)
用于测试的示例存储库:
public interface UserRepository extends JpaRepository<User, Long>{
}
Run Code Online (Sandbox Code Playgroud)
示例控制器:
@RestController
@Transactional
public class SampleController {
@Autowired
private UserRepository userRepository;
@GetMapping(value = "/{id}")
public ResponseEntity<User> getUser(@PathVariable("id") String id) {
Optional<User> user = userRepository.findById(Long.valueOf(id));
User userDemo = user.get();
return ResponseEntity.ok(userDemo);
}
@PostMapping(value = "/create/user")
public ResponseEntity<String> createUser(@RequestBody User user) {
userRepository.save(user);
return ResponseEntity.ok("User is saved");
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
522 次 |
| 最近记录: |