Skip to content

Dynamic MappedStatement with dynamic databaseId #3429

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2009-2024 the original author or authors.
* Copyright 2009-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -59,7 +59,8 @@ public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");

if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
if (!this.configuration.getSupportDynamicRoutingDataSource()
&& !databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}

Expand Down Expand Up @@ -117,9 +118,18 @@ public void parseStatementNode() {
String resultSets = context.getStringAttribute("resultSets");
boolean dirtySelect = context.getBooleanAttribute("affectData", Boolean.FALSE);

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap,
parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
if (databaseId != null && databaseId.contains(";")) {
String[] databaseIds = databaseId.split(";");
for (String dbId : databaseIds) {
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache,
resultOrdered, keyGenerator, keyProperty, keyColumn, dbId, langDriver, resultSets, dirtySelect);
}
} else {
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout,
parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache,
resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets, dirtySelect);
}
}

private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) {
Expand Down
94 changes: 77 additions & 17 deletions src/main/java/org/apache/ibatis/session/Configuration.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.apache.ibatis.session;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
Expand All @@ -23,6 +24,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -69,6 +71,7 @@
import org.apache.ibatis.logging.slf4j.Slf4jImpl;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMap;
Expand Down Expand Up @@ -108,6 +111,7 @@ public class Configuration {
protected boolean safeResultHandlerEnabled = true;
protected boolean mapUnderscoreToCamelCase;
protected boolean aggressiveLazyLoading;
protected boolean multipleResultSetsEnabled = true;
protected boolean useGeneratedKeys;
protected boolean useColumnLabel = true;
protected boolean cacheEnabled = true;
Expand All @@ -117,6 +121,7 @@ public class Configuration {
protected boolean shrinkWhitespacesInSql;
protected boolean nullableOnForEach;
protected boolean argNameBasedConstructorAutoMapping;
protected boolean supportDynamicRoutingDataSource;

protected String logPrefix;
protected Class<? extends Log> logImpl;
Expand All @@ -133,6 +138,7 @@ public class Configuration {
protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

protected DatabaseIdProvider databaseIdProvider;
protected Properties variables = new Properties();
protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
protected ObjectFactory objectFactory = new DefaultObjectFactory();
Expand Down Expand Up @@ -349,6 +355,29 @@ public void setDatabaseId(String databaseId) {
this.databaseId = databaseId;
}

public boolean getSupportDynamicRoutingDataSource() {
return this.supportDynamicRoutingDataSource;
}

public void setSupportDynamicRoutingDataSource(Boolean supportDynamicRoutingDataSource) {
this.supportDynamicRoutingDataSource = supportDynamicRoutingDataSource;
}

public DatabaseIdProvider getDatabaseIdProvider() {
return databaseIdProvider;
}

public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
this.databaseIdProvider = databaseIdProvider;
}

public String getCurrentDatabaseId() throws SQLException {
if (supportDynamicRoutingDataSource && databaseIdProvider != null) {
return databaseIdProvider.getDatabaseId(environment.getDataSource());
}
return this.getDatabaseId();
}

public Class<?> getConfigurationFactory() {
return configurationFactory;
}
Expand Down Expand Up @@ -455,20 +484,12 @@ public void setAggressiveLazyLoading(boolean aggressiveLazyLoading) {
this.aggressiveLazyLoading = aggressiveLazyLoading;
}

/**
* @deprecated You can safely remove the call to this method as this option had no effect.
*/
@Deprecated
public boolean isMultipleResultSetsEnabled() {
return true;
return multipleResultSetsEnabled;
}

/**
* @deprecated You can safely remove the call to this method as this option had no effect.
*/
@Deprecated
public void setMultipleResultSetsEnabled(boolean multipleResultSetsEnabled) {
// nop
this.multipleResultSetsEnabled = multipleResultSetsEnabled;
}

public Set<String> getLazyLoadTriggerMethods() {
Expand Down Expand Up @@ -831,7 +852,12 @@ public boolean hasParameterMap(String id) {
}

public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
String id = ms.getId();
if (this.getSupportDynamicRoutingDataSource() && Objects.nonNull(ms.getDatabaseId())
&& ms.getDatabaseId().trim().length() > 0) {
id = id + "#" + ms.getDatabaseId();
}
mappedStatements.put(id, ms);
}

public Collection<String> getMappedStatementNames() {
Expand Down Expand Up @@ -920,7 +946,29 @@ public MappedStatement getMappedStatement(String id, boolean validateIncompleteS
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
MappedStatement statement = null;
try {
statement = mappedStatements.get(this.getMappedStatementId(id));
} catch (IllegalArgumentException e) {
if (this.getSupportDynamicRoutingDataSource()) {
statement = mappedStatements.get(id);
} else {
throw e;
}
}

return statement;
}

protected String getMappedStatementId(String id) {
try {
String databaseId = this.getCurrentDatabaseId();
if (this.getSupportDynamicRoutingDataSource() && Objects.nonNull(databaseId)) {
return id + "#" + databaseId;
}
} catch (SQLException ignore) {
}
return id;
}

public Map<String, XNode> getSqlFragments() {
Expand Down Expand Up @@ -959,7 +1007,8 @@ public boolean hasStatement(String statementName, boolean validateIncompleteStat
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.containsKey(statementName);
return mappedStatements.containsKey(this.getMappedStatementId(statementName))
|| this.getSupportDynamicRoutingDataSource() && mappedStatements.containsKey(statementName);
}

public void addCacheRef(String namespace, String referencedNamespace) {
Expand Down Expand Up @@ -1113,7 +1162,6 @@ protected static class StrictMap<V> extends ConcurrentHashMap<String, V> {
private static final long serialVersionUID = -4950446264854982944L;
private final String name;
private BiFunction<V, V, String> conflictMessageProducer;
private static final Object AMBIGUITY_INSTANCE = new Object();

public StrictMap(String name, int initialCapacity, float loadFactor) {
super(initialCapacity, loadFactor);
Expand Down Expand Up @@ -1163,7 +1211,7 @@ public V put(String key, V value) {
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
super.put(shortKey, (V) AMBIGUITY_INSTANCE);
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
Expand All @@ -1184,13 +1232,25 @@ public V get(Object key) {
if (value == null) {
throw new IllegalArgumentException(name + " does not contain value for " + key);
}
if (AMBIGUITY_INSTANCE == value) {
throw new IllegalArgumentException(key + " is ambiguous in " + name
if (value instanceof Ambiguity) {
throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
+ " (try using the full name including the namespace, or rename one of the entries)");
}
return value;
}

protected static class Ambiguity {
private final String subject;

public Ambiguity(String subject) {
this.subject = subject;
}

public String getSubject() {
return subject;
}
}

private String getShortName(String key) {
final String[] keyParts = key.split("\\.");
return keyParts[keyParts.length - 1];
Expand Down