Статьи

Tapestry Magic: интеграция с гибернацией и поддержкой нескольких баз данных

В Tapestry5 уже есть модуль для интеграции с Hibernate. Этот модуль ограничен только одной базой данных. В этом посте я создам небольшой модуль, который может поддерживать несколько баз данных. Я не собираюсь предоставлять все возможности, которые уже предоставляет модуль Tapestry-Hibernate, но только для решения проблемы множественного доступа к базе данных.

Для поддержки нескольких баз данных мы будем использовать идентификатор фабрики сеанса или factoryId для идентификации SessionFactory. Это будет использоваться в качестве идентификатора сервиса для идентификации сервиса. Таким образом, мы можем использовать @InjectService («factoryId») или @Named («factoryId») @ Inject

Мы определяем SessionFactorySource, который будет отвечать за создание и управление SessionFactorys.

public interface SessionFactorySource {
public SessionFactory getSessionFactory(String factoryID);

public SessionFactory getSessionFactory(Class<?> entityClass);

public Session createSession(Class<?> entityClass);

public Session createSession(String factoryID);

public String getFactoryID(Class<?> entityClass);
}

Методы getSessionFactory () возвращают SessionFactory для определенного factoryId или класса сущности. Метод createSession () отвечает за создание нового сеанса. Метод getFactoryId () обеспечивает отображение класса сущности на идентификатор фабрики сеанса. Реализация этого сервиса принимает набор конфигураций для настройки различных SessionFactorys. Конфигурация

public class SessionFactoryConfiguration {
private final String[] packageNames;
private final String id;

public SessionFactoryConfiguration(final String[] packageNames, final String id) {
this.packageNames = packageNames;
this.id = id;
}

public String[] getPackageNames() {
return packageNames;
}

public final String getSymbol() {
return id;
}

public void configure(Configuration configuration){

}
}

В конфигурации мы можем указать имена пакетов, содержащие доменные объекты / сущности и идентификатор фабрики, который будет использоваться для идентификации конкретной SessionFactory. Существует метод configure (), который можно использовать для настройки SessionFactory.

Реализация SessionFactorySource как в

public class SessionFactorySourceImpl implements SessionFactorySource,
RegistryShutdownListener {

private final Map<String, SessionFactory> symbolMap = new HashMap<String, SessionFactory>();
private final Map<Class<?>, String> entityMap = new HashMap<Class<?>, String>();
private final ClassNameLocator classNameLocator;

public SessionFactorySourceImpl(final ClassNameLocator classNameLocator,
final List<SessionFactoryConfiguration> configurations) {
this.classNameLocator = classNameLocator;
for(final SessionFactoryConfiguration configuration : configurations){
setupSessionFactory(configuration);
}
}

private void setupSessionFactory(
final SessionFactoryConfiguration configuration) {
final Configuration hibernateConfig = new Configuration();
final List<Class<?>> entities = loadEntityClasses(configuration);

// Load entity classes
for(final Class<?> entityClass : entities){
hibernateConfig.addAnnotatedClass(entityClass);
entityMap.put(entityClass, configuration.getSymbol());
}

configuration.configure(hibernateConfig);

final SessionFactory sf = hibernateConfig.buildSessionFactory();
if(configuration.getSymbol() != null){
symbolMap.put(configuration.getSymbol(), sf);
}
}

private List<Class<?>> loadEntityClasses(
final SessionFactoryConfiguration configuration) {
final ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();

final List<Class<?>> entityClasses = new ArrayList<Class<?>>();

for(final String packageName : configuration.getPackageNames()){
for(final String className : classNameLocator
.locateClassNames(packageName)){
try{
Class<?> entityClass = null;
entityClass = classLoader.loadClass(className);
if(entityClass.getAnnotation(javax.persistence.Entity.class) != null ||
entityClass.getAnnotation(javax.persistence.MappedSuperclass.class) != null){
entityClasses.add(entityClass);
}
}catch(ClassNotFoundException e){
throw new RuntimeException(e);
}
}
}
return entityClasses;
}

public SessionFactory getSessionFactory(final String factoryID) {
SessionFactory sf = symbolMap.get(factoryID);
if(sf == null){
throw new RuntimeException("No session factory found for factoryID: "
+ factoryID);
}
return sf;
}

public SessionFactory getSessionFactory(final Class<?> factoryID) {
SessionFactory sf = getSessionFactory(entityMap.get(factoryID));
if(sf == null){
throw new RuntimeException("No session factory found for entity: "
+ factoryID);
}
return sf;
}

public void registryDidShutdown() {
for(final SessionFactory sessionFactory : symbolMap.values()){
sessionFactory.close();
}
}

public Session createSession(Class<?> entityClass) {
return createSession(getFactoryID(entityClass));
}

public Session createSession(String factoryID) {
final Session session = getSessionFactory(factoryID).openSession();
return session;
}

public String getFactoryID(Class<?> entityClass) {
return entityMap.get(entityClass);
}
}

В конструкторе мы перебираем все конфигурации и настраиваем SessionFactorys. Мы добавляем в него все классы, аннотированные @Entity и @MappedSuperclass в указанных пакетах. Мы храним карту SessionFactory и factoryIds. Мы также храним карту всех классов сущностей и связанных с ними SessionFactorys. Другие методы предназначены только для извлечения значений из этих карт.

Далее мы определяем службу для каждого потока для управления сеансами.

public interface HibernateSessionManager {

public Session getSession(Class<?> entityClass);

public Session getSession(String factoryID);

public Session getSession();
}

Методы используются для получения Session на основе factoryId или entityType. Метод без параметра извлекает сеанс для заводского идентификатора по умолчанию (указанного символом DEFAULT_FACTORY_ID).
Реализация как под

public class HibernateSessionManagerImpl implements HibernateSessionManager,
ThreadCleanupListener {

private SessionFactorySource sessionFactorySource;
private String defaultFactoryID;
private Map<String, Session> sessions = new HashMap<String, Session>();

public HibernateSessionManagerImpl(
@Symbol(TapestryHibernateMutipleConstants.DEFAULT_FACTORY_ID) String defaultFactoryID,
SessionFactorySource sessionFactorySource) {
this.sessionFactorySource = sessionFactorySource;
this.defaultFactoryID = defaultFactoryID;
}

public Session getSession(Class<?> entityClass) {
return getSession(sessionFactorySource.getFactoryID(entityClass));
}

public Session getSession(String factoryID) {
factoryID = nvl(factoryID);
Session session = sessions.get(factoryID);
if( session == null){
session = createSession(factoryID);
}
return session;
}

public Session getSession() {
return getSession(defaultFactoryID);
}

public void threadDidCleanup() {
for(final Session session: sessions.values()){
session.close();
}
sessions.clear();
}

private String nvl(String factoryID) {
return (factoryID == null || "".equals(factoryID)) ? defaultFactoryID : factoryID;
}

public Session createSession(String factoryID) {
factoryID = nvl(factoryID);
final Session session = sessionFactorySource.createSession(factoryID);
sessions.put(factoryID, session);
return session;
}

public Session createSession() {
return createSession(defaultFactoryID);
}

public void setSession(String factoryID, Session session) {
sessions.put(nvl(factoryID), session);
}

}

 

Теперь к новому сеансу можно обратиться, внедрив HibernateSessionManager и используя методы getSession (factoryId) или getSession (entityClass). Чтобы получить доступ к сеансу как сервису, нам понадобится Shadow Builder для SessionFactory, который получит доступ к его методу getService (factoryId) для создания сервиса Session.

//Interface
public interface SessionShadowBuilder {
Session build(HibernateSessionManager sm, String sessionFactoryId);
}

//Implementation
public class SessionShadowBuilderImpl implements SessionShadowBuilder {
private final ClassFactory classFactory;

public SessionShadowBuilderImpl(@Builtin ClassFactory classFactory) {
this.classFactory = classFactory;
}

@SuppressWarnings("unchecked")
public Session build(HibernateSessionManager sm, String sessionFactoryId) {
Class sourceClass = sm.getClass();
ClassFab cf = classFactory.newClass(Session.class);

cf.addField("_source", Modifier.PRIVATE | Modifier.FINAL, sourceClass);
cf.addConstructor(new Class[] { sourceClass }, null, "_source = $1;");

BodyBuilder body = new BodyBuilder();
body.begin();

body.addln("%s result = _source.getSession(\"%s\");", sourceClass.getName(), sessionFactoryId);

body.addln("if (result == null)");
body.begin();
body
.addln(
"throw new NullPointerException(%s.buildMessage(_source, \"getSession(%s)\"));",
getClass().getName(), sessionFactoryId);
body.end();
body.addln("return result;");
body.end();

MethodSignature sig = new MethodSignature(Session.class, "_delegate",
null, null);
cf.addMethod(Modifier.PRIVATE, sig, body.toString());

String toString = String.format("<Shadow: getSession(%s) of HibernateSessionManager>",
sessionFactoryId);

cf.proxyMethodsToDelegate(Session.class, "_delegate()", toString);

Class shadowClass = cf.createClass();
try {
Constructor cc = shadowClass.getConstructors()[0];
Object instance = cc.newInstance(sm);
return (Session)instance;
} catch (Exception ex) {
// Should not be reachable
throw new RuntimeException(ex);
}

}

public static final String buildMessage(Object source, String propertyName) {
return String
.format(
"Unable to delegate method invocation to property '%s' of %s, because the property is null.",
propertyName, source);
}
}

Этот сервис создает прокси для Session, который делегирует каждый вызов сеансу, созданному с использованием HibernateSessionFactory # getSession (factoryId).

Наконец, мы вносим вклад в наш класс модуля

public class TapestryHibernateMultipleModule {

public static void bind(ServiceBinder binder) {
binder.bind(SessionFactorySource.class, SessionFactorySourceImpl.class);
binder.bind(SessionShadowBuilder.class, SessionShadowBuilderImpl.class);
}

public void contributeFactoryDefaults(
MappedConfiguration<String, String> defaults) {
defaults.add(TapestryHibernateMutipleConstants.DEFAULT_FACTORY_ID,
"default");
}

@Scope(ScopeConstants.PERTHREAD)
public HibernateSessionManager buildHibernateSessionManager(
@Symbol(TapestryHibernateMutipleConstants.DEFAULT_FACTORY_ID) String defaultFactoryID,
SessionFactorySource sessionFactorySource,
PerthreadManager threadManager) {
HibernateSessionManagerImpl sm = new HibernateSessionManagerImpl(
defaultFactoryID, sessionFactorySource);
threadManager.addThreadCleanupListener(sm);
return sm;
}

@ServiceId("default")
public Session buildDefaultSession(
@Symbol(TapestryHibernateMutipleConstants.DEFAULT_FACTORY_ID) String defaultFactoryID,
SessionShadowBuilder sessionShadowBuilder,
HibernateSessionManager sessionManager){
return sessionShadowBuilder.build(sessionManager, defaultFactoryID);
}

}

Для использования этого модуля мы должны добавить HibernateSessionConfiguration в классе Module

@Contribute(SessionFactorySource.class)
public void providerSessionFactorySource(
Configuration<SessionFactoryConfiguration> configuration) {
configuration.add(
new SessionFactoryConfiguration(
new String[] { "com.googlecode.tawus.hibernate.models" },
"default") {
@Override
public void configure(
org.hibernate.cfg.Configuration configuration) {
Properties prop = new Properties();
prop.put("hibernate.dialect",
"org.hibernate.dialect.HSQLDialect");
prop.put("hibernate.connection.driver_class",
"org.hsqldb.jdbcDriver");
prop
.put("hibernate.connection.url",
"jdbc:hsqldb:mem:testdb");
prop.put("hibernate.connection.username", "sa");
prop.put("hibernate.connection.password", "");
prop.put("hibernate.connection.pool_size", "1");
prop.put("hibernate.connection.autocommit", "false");
prop.put("hibernate.hbm2ddl.auto", "create-drop");
prop.put("hibernate.show_sql", "true");
prop.put("hibernate.current_session_context_class", "thread");
configuration.addProperties(prop);
}
});

configuration.add(
new SessionFactoryConfiguration(
new String[] { "com.googlecode.tawus.hibernate.models2" },
"second") {
@Override
public void configure(
org.hibernate.cfg.Configuration configuration) {
Properties prop = new Properties();
prop.put("hibernate.dialect",
"org.hibernate.dialect.HSQLDialect");
prop.put("hibernate.connection.driver_class",
"org.hsqldb.jdbcDriver");
prop
.put("hibernate.connection.url",
"jdbc:hsqldb:hsql://localhost/firstdb");
prop.put("hibernate.connection.username", "sa");
prop.put("hibernate.connection.password", "");
prop.put("hibernate.connection.pool_size", "1");
prop.put("hibernate.connection.autocommit", "false");
prop.put("hibernate.hbm2ddl.auto", "create-drop");
prop.put("hibernate.show_sql", "true");
prop.put("hibernate.current_session_context_class", "thread");
configuration.addProperties(prop);
}
});

}

@ServiceId("second")
public Session buildFinacleSession(
SessionShadowBuilder sessionShadowBuilder,
HibernateSessionManager sessionManager){
return sessionShadowBuilder.build(sessionManager, "second");
}

Теперь мы можем получить сессии на странице как

public class TestPage {
@InjectService("default")
private Session session;

@InjectService("second")
private Session otherSession;

}

Если у нас есть только одна сессия, мы можем продолжать использовать @Inject Session.

От http://tawus.wordpress.com/2011/04/28/tapestry-magic-9-integrating-with-hibernate-and-multiple-database-support/