Это правильный шаблон для управления сессиями / транзакциями в Hibernate? - PullRequest
0 голосов
/ 21 октября 2011

мы разрабатываем приложение с использованием JSF 2.0 (primefaces) и Hibernate 3.6.1. Мы следуем подходу, позволяющему более высокому уровню приложения быть независимым от структуры DAL .... это означает, что мы неМы не используем подход сеанса к запросу, но мы сконфигурировали Hibernate для обработки сеанса для каждого потока.Мы реализовали класс, роль которого состоит в том, чтобы обрабатывать «атомарную операцию», заботиться об открытии сеанса и транзакции, когда они еще не выполняются, и просто подключать другую операцию БД к существующей транзакции, чтобы сделать их частьюосновной операции, позволяя им знать, выполняются ли они независимо или как часть более крупной операции.Вот код для нашего OperationManager:

public class OperationManager implements IOperationManager {

    Transaction tx = null;
    boolean isInternalTransaction = false;

    /* (non-Javadoc)
     * @see alekso.npe.dal.IOperationManager#beginOperation()
     */
    @Override
    public Session beginOperation(){            
        Session session = SessionFactoryUtil.getInstance().getCurrentSession();
        if (session.getTransaction().isActive()) {
            isInternalTransaction = false;  
            tx = session.getTransaction();
        }
        else {
            isInternalTransaction = true;
            tx = session.beginTransaction();
        }
        return session;
    }

    /* (non-Javadoc)
     * @see alekso.npe.dal.IOperationManager#commitOperation()
     */
    @Override
    public void commitOperation(){
        if (isInternalTransaction)
            tx.commit();
    }

    /* (non-Javadoc)
     * @see alekso.npe.dal.IOperationManager#rollbackOperation()
     */
    @Override
    public void rollbackOperation(){
        if (isInternalTransaction)
            tx.rollback();
    }
}

Класс SessionFactoryUtil - это «классическая» фабричная утилита для спящего режима, предлагаемая везде ... мы выбрали эту реализацию:

public class SessionFactoryUtil {

final static Logger log = Logger.getLogger(SessionFactoryUtil.class);

/** The single instance of hibernate SessionFactory */
private static org.hibernate.SessionFactory sessionFactory;

/**
 * disable contructor to guaranty a single instance
 */
private SessionFactoryUtil() {
}

static {
    // Annotation and XML
    // sessionFactory = new
    // AnnotationConfiguration().configure().buildSessionFactory();
    // XML only
    try {
        sessionFactory = new Configuration().configure().buildSessionFactory();
    } catch (Exception e) {
        log.error("Errore nella creazione del session factory", e);
        System.out.println("Errore nella creazione del session factory");
    }

}

public static SessionFactory getInstance() {
    try{
        return sessionFactory;
    } catch (Exception ex)
    {
        log.error("errore nella creazione della session di hibernate", ex);
        return null;
    }
}

/**
 * Opens a session and will not bind it to a session context
 * 
 * @return the session
 */
public Session openSession() {
    return sessionFactory.openSession();
}

/**
 * Returns a session from the session context. If there is no session in the
 * context it opens a session, stores it in the context and returns it. This
 * factory is intended to be used with a hibernate.cfg.xml including the
 * following property <property
 * name="current_session_context_class">thread</property> This would return
 * the current open session or if this does not exist, will create a new
 * session
 * 
 * @return the session
 */
public Session getCurrentSession() {
    return sessionFactory.getCurrentSession();
}

/**
 * closes the session factory
 */
public static void close() {
    if (sessionFactory != null)
        sessionFactory.close();
    sessionFactory = null;

}
}

Теперь, как мы используем диспетчер операций?Проще говоря, тот, кто должен отвечать за атомарную операцию в БД, вызывает метод в OperationManager.

public class BL1{
    public void highMethod() {      
        IOperationManager om = new OperationManager();
        BL2 bl2 = new BL2();
        BL3 bl3 = new BL3();
        try {           
            om.beginOperation();            

            bl2.midMethod();
            bl3.midMethod();

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
}

public class BL2{
    public void midMethod() {       
        IOperationManager om = new OperationManager();
        DAL1 dal1 = new DAL1();
        DAL2 dal2 = new DAL2();
        try {           
            om.beginOperation();            

            dal1.lowMethod1();
            dal2.lowMethod1();

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
}

public class BL3{
    public void midMethod() {       
        IOperationManager om = new OperationManager();
        DAL1 dal1 = new DAL1();
        DAL2 dal2 = new DAL2();
        try {           
            om.beginOperation();            

            dal1.lowMethod2();
            dal2.lowMethod2();

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
}

public class DAL1{
    public void lowMethod1() {      
        IOperationManager om = new OperationManager();
        Session session = nullM
        try {                                   
            session = om.beginOperation();

            // do some work on session
            session.saveOrUpdate(...);
            session.load(..);
            session.somethingElse(....);

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
    public void lowMethod2() {      
        IOperationManager om = new OperationManager();
        Session session = nullM
        try {                                   
            session = om.beginOperation();

            // do some work on session
            session.saveOrUpdate(...);
            session.load(..);
            session.somethingElse(....);

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
}

public class DAL2{
    public void lowMethod1() {      
        IOperationManager om = new OperationManager();
        Session session = nullM
        try {                                   
            session = om.beginOperation();

            // do some work on session
            session.saveOrUpdate(...);
            session.load(..);
            session.somethingElse(....);

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
    public void lowMethod2() {      
        IOperationManager om = new OperationManager();
        Session session = nullM
        try {                                   
            session = om.beginOperation();

            // do some work on session
            session.saveOrUpdate(...);
            session.load(..);
            session.somethingElse(....);

            om.commitOperation();           
        } catch (Exception ex) {
            omrollbackOperation();      
        }       
    }
}    
}

Если я вызываю BL1.highMethod, каждый из них ниже (все это метод, названный извнутри) будет находиться под транскрипцией, которую он начал: om.beginOperation () в вызываемом методе просто возвращает сессию, начатую BL1.highMethod, а om.commitOperation или om.rollBackOperation вызванного метода просто ничего не делаютоставляя в экземпляре om, созданном в BL1.highMethod, ответственность за фиксацию / откат и закрытие сеанса.Но если мы вызовем напрямую BL2.midMethod, ответственность за управление сессией и транзакцией будет своей.То же самое происходит, если мы вызываем напрямую DAL1.lowMethod1.Шаблон таков: если я (как метод) не вызывал beginOperation, все, что я использую для выполнения своей работы, будет обрабатывать сеанс / транзакцию самостоятельно;если я начну работу, я буду отвечать за управление сессией / транскрипцией.Надеюсь, я ясно дал понять.

Теперь мне показалось, что это очень разумный подход ... пока что-то пойдет не так.Что идет не так, очень сложно сказать.Просто приложение начинает вести себя странно: иногда приложение создает блокировку на БД во время обновления (база данных говорит о блокирующей строке, но, глядя на последовательность операторов sql, сброшенных на БД, это не имеет смысла);иногда нет блокировки и не регистрируется ошибка, но приложение, похоже, читает «старые данные» ... как если бы операции, которую мы делали пару секунд назад, не было ... и она появилась там только через 30 секунд(или 10, или 60 ... это зависит).Я имею в виду очень странное поведение.Мы видим только некоторую ошибку в журнале, когда мы попадаем в блокировку строки на БД, что приводит к длительной действующей транзакции, в противном случае ничего не кажется неправильным.Чтобы усложнить задачу, иногда работает та же самая точная последовательность действий пользователя над приложением (над одной и той же записью, одним и тем же пользователем и т. Д.), Иногда нет.И это происходит из-за того, что мы реорганизуем код, представляющий шаблон, который я показал выше, используя класс OperationManager.

У кого-то есть представление о том, что мы сделали неправильно, почему этот шаблон не работает должным образом?Я прошу прощения за длину вопроса, размещенного ...

Ответы [ 2 ]

0 голосов
/ 21 октября 2011

На самом деле я решил свои проблемы, набрав вопрос выше! В последней части кода, класс и метод, который вызвал OperationManager, я объявил и istantiate объект OperationManager прямо внутри метода. Таким образом, флаг isInternalTransaction связан с самим методом. В моем реальном коде в классе я объявил OperationManager на уровне класса: это означает, что вызовы различных методов одного и того же экземпляра класса нарушили шаблон, потому что метод isInternalTransaction был изменен извне метода владельца! Так что это единственная ошибка, которую я имел: использование созданного мной сервиса для шаблона, а не сам шаблон. Теперь отлично работает даже без весны.

Это хорошо для меня, не только потому, что мне не нужно изучать весну только из-за этого, но и потому, что я предпочитаю глубже копаться в проблеме / шаблоне и понимать, почему и как это работает .. не просто взять что-то, что работает, а считать это волшебством;)

0 голосов
/ 21 октября 2011
  1. То, что SessionFactoryUtil не является чем-то «классическим», кроме клуджа. Единственная причина, по которой он так широко распространен, заключается в том, что он используется как часть учебника в первой главе справочного руководства по Hibernate . Это всего лишь учебник код, и хотя в справочном руководстве этого никогда не говорится, я скажу: этот код никогда не должен использоваться в производстве.
  2. Вы попадаете в ту же ловушку, что и многие люди, пытаясь переписать код, который уже был написан, проверен и повторно использован тысячами разработчиков. См. этот ответ на аналогичный вопрос .
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...