I'm trying to change an annotation hibernate with only one datasource, to have as many as are saved in a database. In order to make an user have its assigned connection, and add new type of connections with only a server restart (avoiding .war recompile)
The server first loads a SecurityHibernateConfiguration Without any Problem:
@Configuration
@EnableTransactionManagement
public class SecurityHibernateConfiguration {
@Autowired
public Parameters parameters;
@Bean
DataSource datasourcesecurity() {
org.apache.commons.dbcp.BasicDataSource dataSource = new org.apache.commons.dbcp.BasicDataSource();
dataSource.setDriverClassName(parameters.getDriverClassName());
dataSource.setUrl(parameters.getUrlSecurity());
dataSource.setUsername(parameters.getUserNameSecurity());
dataSource.setPassword(parameters.getPasswordSecurity());
return dataSource;
}
@Bean
public SessionFactory securitySessionFactory() throws Exception {
Properties props = new Properties();
props.put("hibernate.dialect", parameters.getHibernateDialect());
props.put("hibernate.format_sql", parameters.getFormatSql());
AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean();
bean.setAnnotatedClasses(new Class[] {
Login.class,
LoginRol.class,
Aplicacio.class,
Rol.class,
RolObjecte.class,
Objecte.class,
RolObjecteAcl.class,
Acl.class,
Tema.class,
Connexio.class
});
bean.setHibernateProperties(props);
bean.setDataSource(datasourcesecurity());
bean.setSchemaUpdate(false);
bean.afterPropertiesSet();
SessionFactory factory = bean.getObject();
return factory;
}
@Bean
public HibernateTransactionManager securitytransactionManager() throws Exception {
return new HibernateTransactionManager(securitySessionFactory());
}
}
Then I created a Routing DataSource like this:
public class RoutingDataSource extends AbstractRoutingDataSource {
@Autowired
private SecurityManager securitymanager;
private Map<Long, DataSource> targetDataSources = new HashMap<Long, DataSource>();
@SuppressWarnings({ "unchecked", "rawtypes" })
public void setTargetDataSources(Map targetDataSources) {
this.targetDataSources = (Map<Long, DataSource>) targetDataSources;
}
@Override
protected DataSource determineTargetDataSource() {
Long lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.targetDataSources.get(lookupKey);
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
@Override
protected Long determineCurrentLookupKey() {
try {
String username = securitymanager.getUserName();
Login login = null;
if (!StringUtils.isEmpty(username)) {
login = securitymanager.getLogin(username);
}
return login == null ? 1L : login.getConnexio() == null ? 1L : login.getConnexio().getId();
} catch (Exception e) {
return 1L;
}
}
@Override
public void afterPropertiesSet() {
// do nothing
// overridden to avoid datasource validation error by Spring
}
}
Used in the HibernateConfiguration like this:
@Configuration
@EnableTransactionManagement
public class HibernateConfiguration {
@Autowired
private SecurityManager securitymanager;
@Autowired
private Parameters parameters;
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean lcemfb = new LocalContainerEntityManagerFactoryBean();
lcemfb.setDataSource(this.dataSource());
lcemfb.setPackagesToScan(new String[] { "cat.itec.pgm.persistence" });
lcemfb.setPersistenceUnitName("pgmdb");
HibernateJpaVendorAdapter va = new HibernateJpaVendorAdapter();
va.setShowSql(true);
lcemfb.setJpaVendorAdapter(va);
Properties ps = new Properties();
ps.put("hibernate.dialect", "org.hibernate.dialect.Oracle10gDialect");
ps.put("hibernate.format_sql", "true");
ps.put("hibernate.show_sql", "true");
lcemfb.setJpaProperties(ps);
lcemfb.afterPropertiesSet();
return lcemfb;
}
@Bean
public DataSource dataSource() {
RoutingDataSource rds = new RoutingDataSource();
Map<Long, DataSource> targetDataSources = new HashMap<Long, DataSource>();
List<Connexio> connexioLogins = new ArrayList<Connexio>();
try {
connexioLogins = securitymanager.getConnexioLogins();
} catch (Exception e) {
System.out.println("Cannot Load List Of Connections");
}
for (Connexio c : connexioLogins) {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(parameters.getDriverClassName());
ds.setUrl(generateUrlConnection(c));
ds.setUsername(c.getDbUsername());
ds.setPassword(c.getDbPassword());
targetDataSources.put(c.getId(), ds);
}
rds.setTargetDataSources(targetDataSources);
return rds;
}
@Bean
public PlatformTransactionManager transactionManager() {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(this.entityManagerFactoryBean().getObject());
return tm;
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
private String generateUrlConnection(Connexio c) {
StringBuilder sb = new StringBuilder();
sb.append("jdbc:oracle:thin:@");
sb.append(c.getServer());
sb.append(":");
sb.append(c.getPort());
sb.append(":");
sb.append(c.getSid());
return sb.toString();
}
}
The point is that when I start the server it throws a:
Caused by: org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63)
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:685)
at cat.itec.security.persistence.dao.login.impl.LoginDaoImpl.getConnexioLogins(LoginDaoImpl.java:37)
I don't know how if the error is to make the RoutingDataSource get every "Connexio", or it is not properly configured.
Any help or comment will be appreciated. (any other piece of code needed for better understanding will be posted as soon as I can).
Thanks in advance.
EDIT (Unuseful, See EDIT2):
Changing a bit the two conflictive Database points like this:
@Bean
public DataSource dataSource() {
RoutingDataSource rds = new RoutingDataSource();
Map<Long,DataSource> targetDataSources = new HashMap<Long,DataSource>();
Connexio c = new Connexio();
c.setDbPassword("XXXXXXXXX");
c.setDbUsername("XXX");
c.setId(1L);
c.setPort("XXXXXXX");
c.setServer("XXXXXXXX");
c.setSid("XXXX");
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(parameters.getDriverClassName());
ds.setUrl(generateUrlConnection(c));
ds.setUsername(c.getDbUsername());
ds.setPassword(c.getDbPassword());
targetDataSources.put(c.getId(), ds);
rds.setTargetDataSources(targetDataSources);
return rds;
}
And
@Override
protected Long determineCurrentLookupKey() {
return 1L;
}
Makes the app work like it was before this changes. So it seems a problem to acces DB in server startup. Any Idea?
EDIT2:
Changed the code added in first term to post the full working one as an example.
I found that the problem was in my Dao Layer. In server startup It is not possible to access the current session, so I've done something like:
try {
Session session = securitySessionFactory.getCurrentSession();
Criteria crit = session.createCriteria(clazz);
return (List<T>) crit.list();
} catch (Exception e) {
Session session = securitySessionFactory.openSession();
Transaction transaction = session.beginTransaction();
transaction.begin();
Criteria crit = session.createCriteria(clazz);
List<T> list = (List<T>) crit.list();
session.disconnect();
session.close();
return list;
}
With this I can fill the RoutingDataSources properly and make the number of datasources a little dynamic (with filling a new entry in the DB and a simple server restart).
Take into account that lazy mappings will be disabled, so it may be useful to evaluate what will be need to be set to FetchType.Eager (with FetchMode.Subselect if more than 1 bag is needed to be initialized with it)
I'm going to edit the question in order to leave this as an example for configuring routing "dynamic" datasources for Spring 3.1.3 with annotations.