[SPRING] 로그인 한 사용자를 기반으로 런타임 중에 데이터베이스 스키마 변경
SPRING로그인 한 사용자를 기반으로 런타임 중에 데이터베이스 스키마 변경
동적 데이터 소스 라우팅에 대한 많은 질문과 답변을 읽었으며 AbstractRoutingDataSource와 다른 데이터 소스를 사용하여 솔루션을 구현했습니다 (아래 참조). 괜찮지 만 모든 데이터 소스에 하드 코드 된 속성이 필요합니다. 애플리케이션을 사용하는 사용자 수가 증가함에 따라 더 이상 라우팅 할 수있는 방법이 아닙니다. 또한 새로운 사용자가 등록 할 때마다 등록 정보에 항목을 추가해야합니다. 상황은 다음과 같습니다.
나는 최대 절전 모드 5.1과 스프링 데이터 jpa와 함께 스프링 부트 1.4.0을 사용하고있다.
스키마를 완전히 동적으로 변경하는 방법을 찾을 수 없습니다. 누군가 봄에 그것을하는 방법을 알고 있습니까?
편집하다:
@Johannes Leimer의 답변 덕분에, 나는 실용적인 구현을 얻었습니다.
코드는 다음과 같습니다.
사용자 제공자 :
@Component
public class UserDetailsProvider {
@Bean
@Scope("prototype")
public CustomUserDetails customUserDetails() {
return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
}
}
UserSchema 인식 라우팅 데이터 소스 :
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");
}
}
해결법
-
==============================
1.내가 아직 귀하의 질문 아래에 의견을 게시하는 평판이 없기 때문에, 내 대답은 다음과 같은 가정을 기반으로합니다 :
내가 아직 귀하의 질문 아래에 의견을 게시하는 평판이 없기 때문에, 내 대답은 다음과 같은 가정을 기반으로합니다 :
ThreadLocal 프록시 조합을 사용하여 스키마 이름을 가져오고, Singleton-DataSource는 모든 사용자 요청에서 다르게 동작합니다. 이 솔루션은 AbstractRoutingDataSource, Meherzad의 의견 및 자신의 경험에 대한 힌트에서 영감을 얻었습니다.
Spring의 AbstractDataSource를 용이하게하고 AbstractRoutingDataSource와 같이 구현하는 것이 좋습니다. 정적 Map-like 접근 방식 대신 Guava 캐시를 사용하여 사용하기 쉬운 캐시를 얻습니다.
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가 생성되면 10 분 동안 캐시됩니다. 그게 전부 야.
새로 생성 된 DataSource를 통합 할 수있는 곳은 스프링 컨텍스트에 알려진 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가 있습니다.
나는이 코드를 테스트하지 않았다!
편집하다: Spring에서 Provider
를 구현하려면 이것을 프로토 타입으로 정의해야합니다. JSR-330 및 Spring Securitys SecurityContextHolder의 스프링 지원을 활용할 수 있습니다. @Bean @Scope("prototype") public CustomUserDetails customUserDetails() { return return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); }
RequestInterceptor, UserProvider 또는 컨트롤러 코드가 없어 사용자를 더 이상 업데이트 할 필요가 없습니다.
이게 도움이 되나요?
EDIT2 기록을 위해 : CustomUserDetails 빈을 직접 참조하지 마십시오. 이것이 프로토 타입이기 때문에, Spring은 CustomUserDetails 클래스를위한 프록시를 만들려고 시도 할 것입니다. 우리의 경우에는 좋지 않습니다. 따라서 Providers를 사용하여이 빈에 액세스하십시오. 또는 인터페이스로 만드십시오.
-
==============================
2.DBMS를 지정하지 않았다면 여기에 도움이 될 수있는 높은 수준의 아이디어가 있습니다.
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에는 DataPource에서 Connection을 얻을 때 임의의 SQL을 실행할 수있는 ConnectionPreparer가 있습니다. 스키마를 전환하는 명령을 간단히 실행할 수 있습니다 (예 : Oracle의 경우 ALTER SESSION SET CURRENT SCHEMA = 'schemaName', Sybase의 경우 schemaName 등을 사용).
e.
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>
from https://stackoverflow.com/questions/39357367/change-database-schema-during-runtime-based-on-logged-in-user by cc-by-sa and MIT license
'SPRING' 카테고리의 다른 글
[SPRING] 스프링 보안을 이용한 싱글 사인온 통합 (0) | 2019.03.02 |
---|---|
[SPRING] 요청 매개 변수를위한 커스텀 Spring 애노테이션 (0) | 2019.03.02 |
[SPRING] Spring MVC REST는 JAX-RS와 호환되지 않는다. 상관이 있나? [닫은] (0) | 2019.03.02 |
[SPRING] Spring 보안에서 registerGlobal (), configure (), configureGlobal (), configureGlobalSecurity 간의 차이점 (0) | 2019.03.02 |
[SPRING] Spring MVC에서 HTTP 상태 코드로 응답하는 방법 @RestController @ResponseBody 클래스가 객체를 반환합니까? (0) | 2019.03.02 |