Как разрешить регистрацию транзакций в Java?

Я хочу добиться следующего:

Внутри транзакции я хочу создать несколько сообщений журнала. Эти сообщения журнала должны быть записаны только в том случае, если транзакция успешно зафиксирована. Если транзакция откатывается, сообщения журнала не должны регистрироваться.

Я не смог найти ничего, чтобы добиться этого (используя spring, hibernate, atomikos), поэтому я написал эту маленькую оболочку (я упустил пару удобных методов):

public class TransactionLogger {
    private Logger logger;
    private Map<Long, LinkedList<LogRecord>> threadBuffers =
        new HashMap<Long, LinkedList<LogRecord>>();

    public TransactionLogger(Logger logger) {
        this.logger = logger;
    }

    private void addRecord(LogRecord rec) {
        LinkedList<LogRecord> list =
            threadBuffers.get(Thread.currentThread().getId());
        if (list == null) {
            list = new LinkedList<LogRecord>();
            threadBuffers.put(Thread.currentThread().getId(), list);
        }
        list.add(rec);
    }

    private LinkedList<LogRecord> getRecords() {
        if (threadBuffers.containsKey(Thread.currentThread().getId())) {
            return threadBuffers.remove(Thread.currentThread().getId());
        } else {
            return new LinkedList<LogRecord>();
        }
    }

    public void commit() {
        for (LogRecord rec : getRecords()) {
            rec.setLoggerName(logger.getName());
            logger.log(rec);
        }
    }

    public void rollback() {
        getRecords();
    }

    /**
     * If the resulting log entry should contain the sourceMethodName
     * you should use logM(Level,String,String) instead,
     * otherwise TransactionLogger.commit() will get
     * inferred as sourceMethodName.
     */
    public void log(Level l, String sourceClassName, String msg) {
        LogRecord rec = new LogRecord(l, msg);
        rec.setSourceClassName(sourceClassName);
        addRecord(rec);
    }

    /**
     * Same as log(Level,String,String), but the sourceMethodName gets set.
     */
    public void logM(Level l, String sourceClassName, String msg) {
        StackTraceElement[] trace = Thread.currentThread().getStackTrace();
        LogRecord rec = new LogRecord(l, msg);
        rec.setSourceClassName(sourceClassName);
        if (trace != null && trace.length > 1) {
            rec.setSourceMethodName(trace[2].getMethodName());
        }
        addRecord(rec);
    }
}

Что вы думаете об этом подходе? Есть ли какие-либо серьезные или незначительные недостатки или проблемы с ним? Или еще лучше, есть ли готовые решения для этого?

ОБНОВЛЕНИЕ:

Поскольку я также использую JTA, у меня появилась новая идея.Решит ли реализация TransactionLogger в качестве получателя очереди сообщений с поддержкой транзакций проблему времени или она просто усложнит ситуацию?

ОБНОВЛЕНИЕ:

Я думаю, что запись в базу данных, а затем периодическая запись записей журнала из этой базы данных в файл в периодической задаче, как это предлагается в комментариях, является очень хорошим решением этой проблемы:

PROs:

  • Обычная реализация позволяет
  • интегрироваться с TransactionManager
  • Записи журнала в файлах журнала могут быть упорядочены по отметке времени

Минусы:

  • Файлы журнала не обновляются (в зависимости от периодического-интервала-задачи)
  • зависит от структуры базы данных
  • ведение журнала простых нетранзакционных событий становится зависимым от dbconnection
  • вероятно, больше общих накладных расходов на ведение журнала

Вот плюсы и минусы, которые я вижу в опубликованной оболочке:

PROs :

  • независимость от базы данных и фреймворка
  • простая реализация
  • файлы журналов всегда актуальны

Недостатки:

  • записи журнала в файлах журнала упорядочены не по отметке времени события, а по «завершению транзакции» "-timestamp (длительные транзакции приводят к очень запутанным лог-файлам.
  • rollb ack()и commit()должны вызываться "вручную", что может привести к ошибкам программирования (и возможному OutOfMemoryError, если вызов этих методов забыт)

Я думаю, что комбинация этих два, такие как буферизация записей журнала в подходе «обертка», хуже, чем использование одного из двух упомянутых подходов, из-за возможных несогласованных файлов журнала (забытые записи журнала из-за сбоя приложения).

Теперь я решил оставить свою "обертку". Следующие причины имеют решающее значение для этого решения (в порядке важности):

  1. Я предпочитаю файлы журналов, которые всегда актуальны, а не идеально упорядоченные записи журнала
  2. Длинные транзакции в моем случае очень редки
  3. Я могу чтобы сократить использование rollback()и commit()до нескольких методов.
  4. Это решение уже существует.

Кстати: Я хотел бы улучшить свой английский. Так что, если вы заметили какие-то ошибки в моей статье, буду рад, если вы на них укажете.

ОБНОВЛЕНИЕ:

Simplefied Я использую это так:

/*
 * This objects contains one or more TransactionLogger(s) and exposes rollback()
 * and commit() through rollbackLogs() and commitLogs().
 */
@Autowired
private ITransactionalServer server;

public void someMethod(String someParam) {
    boolean rollback = false;
    try {
        /*
         * This method is for example annotated with @Transactional.
         */
        return server.someTransactionalMethod(someParam);
    } catch (Exception ex) {
        logError(ex);
        rollback = true;
    } finally {
        if (rollback) {
            server.rollbackLogs();
        } else {
            server.commitLogs();
        }
    }
}

Тем не менее, это не идеально, но прямо сейчас мне это кажется "достаточно хорошим решением". Следующим шагом будет использование аспектов для украшения моих транзакционных методов.

ОБНОВЛЕНИЕ:

Я добавляю это в свой вопрос, потому что мне было неловко принимать свой собственный ответ, хотя кто-то другой помог мне в пути.

Сейчас я использую АОП-подход в основном со следующим регистратором. (В моем реальном приложении у меня есть несколько таких регистраторов, и все эти регистраторы управляются настраиваемым менеджером синглтона.):

public class AopLogger extends Logger {

    public static AopLogger getLogger(String name) {
        LogManager manager = LogManager.getLogManager();
        Object l = manager.getLogger(name);
        if (l == null) {
            manager.addLogger(new AopLogger(name));
        }
        l = manager.getLogger(name);
        return (AopLogger)l;
    }

    private Map<Long, LinkedList<LogRecord>> threadBuffers = new HashMap<Long, LinkedList<LogRecord>>();

    public AopLogger(String name) {
        super(name, null);
    }

    public void beginTransaction() {
        LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
        if (list == null) {
            list = new LinkedList<LogRecord>();
            threadBuffers.put(Thread.currentThread().getId(), list);
        }
    }

    private void addRecord(LogRecord rec) {
        LinkedList<LogRecord> list = threadBuffers.get(Thread.currentThread().getId());
        if (list != null) {
            list.add(rec);
        } else {
            super.log(record);
        }
    }

    private LinkedList<LogRecord> getRecords() {
        if (threadBuffers.containsKey(Thread.currentThread().getId())) {
            return threadBuffers.remove(Thread.currentThread().getId());
        } else {
            return new LinkedList<LogRecord>();
        }
    }

    public void commit() {
        for (LogRecord rec : getRecords()) {
            rec.setMillis(System.currentTimeMillis());
            super.log(rec);
        }
    }

    public void rollback() {
        getRecords();
    }

    public void log(LogRecord record) {
        addRecord(record);
    }
}

и этот аспект:

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

@Service
@Aspect
@Order(10)
public class AopLogManager implements Ordered {

    @Autowired
    private AopLogger logger;
    private Logger errorLogger = Logger.getLogger("ExceptionLogger");

    @Around("@annotation(org.springframework.transaction.annotation.Transactional)")
    public Object logTransaction(ProceedingJoinPoint pjp) throws Throwable {
        logger.beginTransaction();
        Exception ex = null;
        try {
            return pjp.proceed();
        } catch (Exception e) {
            ex = e;
            throw e;
        } finally {
            if (ex != null) {
                logger.rollback();
                errorLogger.severe(ex.getMessage());
            } else {
                logger.commit();
            }
        }
    }

    private int mOrder;

    @Override
    public int getOrder() {
        return mOrder;
    }

    public void setOrder(int order) {
        mOrder = order;
    }
}

В моем applicationContext.xml у меня есть следующие строки:

<aop:aspectj-autoproxy />
<tx:annotation-driven transaction-manager="springTransactionManager" order="5"/>

Это работает нормально до сих пор.

PRO:

  • независимость от базы данных и фреймворка
  • простая реализация
  • файлы журналов всегда актуальны
  • rollback()и commit()вызываются автоматически после каждая транзакция

ПРОТИВ:

  • (Записи журнала в файлах журнала упорядочены не по отметке времени события, а по отметке времени завершения транзакции.Я думаю, что это не является большим недостатком, так как действия БД действительно происходят во время фиксации транзакции, и записи журнала одной транзакции все еще упорядочены правильно.)
11
задан AlexS 3 December 2013 в 10:45
поделиться