動的データソースルーティングに関する多くの質問と回答を読み、AbstractRoutingDataSource
と別のソリューションを使用してソリューションを実装しました(以下を参照)。それは問題ありませんが、すべてのデータソースにハードコードされたプロパティが必要です。アプリケーションを使用するユーザーの数が増えるにつれて、これはルーティングの適切な方法ではなくなります。また、新しいユーザーが登録するたびにプロパティにエントリを追加する必要があります。状況は次のとおりです
spring boot 1.4.0
とhibernate 5.1
とともにspring data jpa
を使用しています
スキーマを完全に動的に変更する方法が見つかりません。誰かが春にそれを行う方法を知っていますか?
編集:
@Johannes Leimerの回答のおかげで、実用的な実装ができました。
コードは次のとおりです。
ユーザープロバイダー:
@Component
public class UserDetailsProvider {
@Bean
@Scope("prototype")
public CustomUserDetails customUserDetails() {
return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
UserSchemaAwareRoutingDatasource:
public class UserSchemaAwareRoutingDataSource extends AbstractDataSource {
@Inject
Provider<CustomUserDetails> customUserDetails;
@Inject
Environment env;
private LoadingCache<String, DataSource> dataSources = createCache();
@Override
public Connection getConnection() throws SQLException {
try {
return determineTargetDataSource().getConnection();
} catch (ExecutionException e){
e.printStackTrace();
return null;
}
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
System.out.println("getConnection" + username);
System.out.println("getConnection2" + password);
try {
return determineTargetDataSource().getConnection(username, password);
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
private DataSource determineTargetDataSource() throws SQLException, ExecutionException {
try {
String schema = customUserDetails.get().getUserDatabase();
return dataSources.get(schema);
} catch (NullPointerException e) {
e.printStackTrace();
return dataSources.get("fooooo");
}
}
私はあなたの質問の下にコメントを投稿する評判がまだないので、私の答えは次の仮定に基づいています:
現在のユーザーに使用される現在のスキーマ名には、private javax.inject.Provider<User> user; String schema = user.get().getSchema();
などのSpring JSR-330プロバイダーからアクセスできます。これは、理想的にはThreadLocalベースのプロキシです。
必要な方法で完全に構成されたDataSource
を構築するには、同じプロパティが必要です。毎回。異なるのはスキーマ名だけです。 (他の異なるパラメーターも簡単に取得できますが、これはこの回答には多すぎます)
各スキーマには必要なDDLがすでに設定されているため、休止状態でテーブルなどを作成する必要はありません。
各データベーススキーマは、名前を除いてまったく同じに見えます。
対応するユーザーがアプリケーションにリクエストを行うたびに、DataSourceを再利用する必要があります。ただし、すべてのユーザーのすべてのDataSourceを永続的にメモリに保持する必要はありません。
ThreadLocalプロキシの組み合わせを使用して、スキーマ名と、ユーザーリクエストごとに異なる動作をするSingleton-DataSourceを取得します。このソリューションは、AbstractRoutingDataSource
、Meherzadのコメント、および自身の経験に対するヒントに触発されています。
DataSource
SpringのAbstractDataSource
を容易にし、AbstractRoutingDataSource
のように実装することをお勧めします。静的なMap
のようなアプローチの代わりに、使いやすいキャッシュを得るために Guava Cache を使用します。
public class UserSchemaAwareRoutingDataSource extends AbstractDataSource {
private @Inject javax.inject.Provider<User> user;
private @Inject Environment env;
private LoadingCache<String, DataSource> dataSources = createCache();
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return determineTargetDataSource().getConnection(username, password);
}
private DataSource determineTargetDataSource() {
String schema = user.get().getSchema();
return dataSources.get(schema);
}
private LoadingCache<String, DataSource> createCache() {
return CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build(
new CacheLoader<String, DataSource>() {
public DataSource load(String key) throws AnyException {
return buildDataSourceForSchema(key);
}
});
}
private DataSource buildDataSourceForSchema(String schema) {
// e.g. of property: "jdbc:postgresql://localhost:5432/mydatabase?currentSchema="
String url = env.getRequiredProperty("spring.datasource.url") + schema;
return DataSourceBuilder.create()
.driverClassName(env.getRequiredProperty("spring.datasource.driverClassName"))
[...]
.url(url)
.build();
}
}
これで、ユーザーごとに異なる動作をする「DataSource」ができました。 DataSourceが作成されると、10分間キャッシュされます。それでおしまい。
新しく作成したDataSourceを統合する場所は、Springコンテキストで認識され、すべてのBeanで使用されるDataSourceシングルトンです。 EntityManagerFactory
したがって、これと同等のものが必要です。
@Primary
@Bean(name = "dataSource")
@ConfigurationProperties(prefix="spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
ただし、プレーンなプロパティベースのDataSourceBuilderよりも動的である必要があります。
@Primary
@Bean(name = "dataSource")
public UserSchemaAwareRoutingDataSource dataSource() {
return new UserSchemaAwareRoutingDataSource();
}
常に正しいDataSourceを使用する透過的な動的DataSourceがあります。
私はこのコードをテストしていません!
EDIT:SpringでProvider<CustomUserDetails>
を実装するには、これをプロトタイプとして定義する必要があります。 JSR-330およびSpring Securitys SecurityContextHolderのスプリングサポートを利用できます。
@Bean @Scope("prototype")
public CustomUserDetails customUserDetails() {
return return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
ユーザーを更新するために、RequestInterceptor
、UserProvider
、またはコントローラーコードは必要ありません。
これは役立ちますか?
EDIT2レコードについてのみ:CustomUserDetails
Beanを直接参照しないでください。これはプロトタイプであるため、SpringはクラスCustomUserDetails
のプロキシを作成しようとしますが、これはこのケースではお勧めできません。したがって、Provider
sを使用してこのBeanにアクセスします。または、インターフェースにします。
DBMSを指定しない場合、ここに役立つ高レベルのアイデアがあります。
(参考としてSpring Data JDBC-extを使用していますが、一般的なAOPを使用することで同じアプローチを簡単に採用できます)
http://docs.spring.io/spring-data/jdbc/docs/current/reference/html/orcl.connection.html 、セクション8.2を参照してください
Spring Data JDBC-extには、DataSourceからConnectionを取得するときに任意のSQLを実行できるConnectionPreparerがあります。コマンドを実行するだけでスキーマを切り替えることができます(例:ALTER SESSION SET CURRENT SCHEMA = 'schemaName'
Oracleでは、using schemaName
Sybaseなど)。
例えば.
package foo;
import org.springframework.data.jdbc.support.ConnectionPreparer;
import Java.sql.CallableStatement;
import Java.sql.Connection;
import Java.sql.SQLException;
public class SwitchSchemaConnectionPreparer implements ConnectionPreparer {
public Connection prepare(Connection conn) throws SQLException {
String schemaName = whateverWayToGetTheScehmaToSwitch();
CallableStatement cs = conn.prepareCall("ALTER SESSION SET CURRENT SCHEMA " + scehmaName);
cs.execute();
cs.close();
return conn;
}
}
アプリコンテキストの構成
<aop:config>
<aop:advisor
pointcut="execution(Java.sql.Connection javax.sql.DataSource.getConnection(..))"
advice-ref="switchSchemaInterceptor"/>
</aop:config>
<bean id="switchSchemaInterceptor"
class="org.springframework.data.jdbc.aop.ConnectionInterceptor">
<property name="connectionPreparer">
<bean class="foo.SwitchSchemaConnectionPreparer"/>
</property>
</bean>