usuarios usuario sesiones sesion registro parte inicio hacer formulario contraseña comprobar como codigo java spring hibernate spring-mvc jpa

java - sesiones - Cambiar el esquema de la base de datos durante el tiempo de ejecución en función del usuario que ha iniciado sesión



sesiones en java netbeans (2)

Suposiciones

Debido a que todavía no tengo la reputación de publicar un comentario debajo de su pregunta, mi respuesta se basa en las siguientes suposiciones:

  • Se puede acceder al nombre de esquema actual que se utilizará para el usuario actual a través de un proveedor Spring JSR-330 como private javax.inject.Provider<User> user; String schema = user.get().getSchema(); private javax.inject.Provider<User> user; String schema = user.get().getSchema(); . Idealmente, este es un proxy basado en ThreadLocal.

  • Para construir un DataSource que está completamente configurado de la manera que lo necesita, requiere las mismas propiedades. Cada vez. Lo único que es diferente es el nombre del esquema. (También sería posible obtener otros parámetros diferentes, pero esto sería demasiado para esta respuesta)

  • Cada esquema ya está configurado con el DDL necesario, por lo que no es necesario hibernar para crear tablas o algo más

  • Cada esquema de base de datos se ve completamente igual excepto por su nombre

  • Debe reutilizar un DataSource cada vez que el usuario correspondiente realice una solicitud a su aplicación. Pero no desea tener cada DataSource de cada usuario permanentemente en la memoria.

Mi idea de solución

Use una combinación de proxys ThreadLocal para obtener el nombre del esquema y un Singleton-DataSource que se comporte diferente en cada solicitud del usuario. Esta solución está inspirada en su sugerencia a AbstractRoutingDataSource , los comentarios de Meherzad y su propia experiencia.

Un DataSource dinámico

Sugiero facilitar AbstractDataSource of Spring e implementarlo como AbstractRoutingDataSource . En lugar de un enfoque estático de Map , utilizamos un Guava Cache para obtener un caché fácil de usar.

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(); } }

Ahora tiene un `DataSource'' que actúa diferente para cada usuario. Una vez que se crea un DataSource, se almacenará en caché durante 10 minutos. Eso es.

Haga que la aplicación conozca nuestro DataSource dinámico

El lugar para integrar nuestro recién creado DataSource es el singleton DataSource conocido en el contexto de primavera y utilizado en todos los beans, por ejemplo, EntityManagerFactory

Entonces necesitamos un equivalente a esto:

@Primary @Bean(name = "dataSource") @ConfigurationProperties(prefix="spring.datasource") public DataSource dataSource() { return DataSourceBuilder.create().build(); }

pero tiene que ser más dinámico que un DataSourceBuilder basado en propiedad simple:

@Primary @Bean(name = "dataSource") public UserSchemaAwareRoutingDataSource dataSource() { return new UserSchemaAwareRoutingDataSource(); }

Conclusión

Tenemos un DataSource dinámico transparente que utiliza el DataSource correcto cada vez.

Preguntas abiertas

  • ¿Qué hacer cuando ningún usuario está conectado? ¿No se permite el acceso a la base de datos?
  • ¿Quién establece los esquemas?

Renuncia

¡No he probado este código!

EDITAR: para implementar un Provider<CustomUserDetails> con Spring, necesita definir esto como prototipo. Puede utilizar el soporte de Springs de JSR-330 y Spring Securitys SecurityContextHolder:

@Bean @Scope("prototype") public CustomUserDetails customUserDetails() { return return (CustomUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); }

Ya no necesita un RequestInterceptor , el UserProvider o el código del controlador para actualizar al usuario.

¿Esto ayuda?

EDIT2 Solo para el registro: NO haga referencia directamente al bean CustomUserDetails . Como se trata de un prototipo, Spring intentará crear un proxy para la clase CustomUserDetails , que no es una buena idea en nuestro caso. Solo use Provider para acceder a este bean. O haz que sea una interfaz.

He leído muchas preguntas y respuestas sobre el enrutamiento de origen de datos dinámico y he implementado una solución usando AbstractRoutingDataSource y otra (ver a continuación). Está bien, pero requiere propiedades codificadas para todas las fuentes de datos. A medida que aumenta el número de usuarios que usan la aplicación, esta ya no es una forma adecuada de enrutar. También se requerirá agregar una entrada a las propiedades cada vez que se registre un nuevo usuario. La situación es la siguiente

  • 1 servidor de base de datos
  • muchos esquemas en ese servidor, cada usuario tiene su propio esquema.
  • Solo necesito cambiar el nombre del esquema durante el tiempo de ejecución
  • el nombre de esquema es retenible por el usuario que ha iniciado sesión

Estoy usando spring boot 1.4.0 junto con hibernate 5.1 y spring data jpa

No puedo encontrar una forma de cambiar el esquema de forma completamente dinámica. ¿Alguien sabe cómo hacerlo en primavera?

EDITAR:

Gracias a la respuesta de @Johannes Leimer, obtuve una implementación funcional.

Aquí está el código:

Proveedor de usuario :

@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"); } }


Dado que no especifica el DBMS, aquí hay una idea de alto nivel que puede ayudar.

(Aunque estoy utilizando Spring Data JDBC-ext como referencia, el mismo enfoque se puede adoptar fácilmente mediante el uso de AOP general)

Consulte http://docs.spring.io/spring-data/jdbc/docs/current/reference/html/orcl.connection.html , Sección 8.2

En Spring Data JDBC-ext, hay ConnectionPreparer que puede permitirle ejecutar SQL arbitrarios cuando adquiere una conexión desde DataSource. Simplemente puede ejecutar los comandos para cambiar el esquema (por ejemplo, ALTER SESSION SET CURRENT SCHEMA = ''schemaName'' en Oracle, using schemaName para Sybase, etc.).

p.ej

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; } }

En la configuración del contexto de la aplicación

<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>