Spring + Hibernate: вставка нескольких записей и @Transactional

У меня есть приложение, построенное на Spring + Hibernate. Я пытаюсь вставить данные в базу данных из списка записей в файле. Основная идея состоит в том, чтобы вставить все записи с действительными данными и зарегистрировать отчет об ошибках для всех записей, которые не были вставлены.

Класс обслуживания, которым является @Transactional, вызывает другой класс обслуживания для чтения файла и вставки в БД. Сейчас я вставляю записи одну за другой. Возможно, я изменю это на Пакетную обработку. Вставка выполняется в блоке try catch.

Псевдокод:

@Transactional
class MainService {     
     public void insertFromFile(File inputFile) {
          subServuce(toIterator(inputFile)); // toIterator(..) is just for understanding. 
     } 
}

class SubService {
    public void insert(Iterator input) {
        while (input.hasNext()) {
             try {
                 DBService.save(input.next());
             } catch (Exception e) {
                 // Log the error and continue with next records
             }
        }
    }
}

@Transactional
class DBService {
     public void save(Data input) {
          generalDao.save(input);
     }
}

Пытался:

class SubService {
    public void insert(Iterator input) {
        while (input.hasNext()) {
             try {
                 save(input.next());
             } catch (Exception e) {
                 // Log the error and continue with next records
             }
        }
    }

    // @Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor = Exception.class)
    @Transactional(propagation = Propagation.NOT_SUPPORTED, noRollbackFor = Exception.class)
    public void save(Data input) {
        DBService.save(input);
    }
}

Несмотря на то, что исключение обработано, выполняется откат всей транзакции, и я получаю эту ошибку Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly

Я попытался указать @Transactional(noRollBack = Exception.class) на SubService.insert(..), но это не сработало.

Следующие мои вопросы:

  1. Как мне сказать Spring не откатывать назад и продолжить с другими записями.
  2. Хорошая практика - не откатывать назад при вставке нескольких записей? Разве это не противоречит правилу атомарности?
  3. Как это обрабатывается при пакетной обработке? Если возникает ошибка, вставляются все или нет?

    Решение:

С помощью ответа @Madhusudana Reddy Sunnapu смог решить эту проблему.

Создал слой между SubService и DBService с помощью Propagation.REQUIRES_NEW. Проблема заключалась в том, что даже несмотря на то, что SubService.save (..) аннотируется @Transactional, поскольку он принадлежит одному и тому же Bean, прокси не будет рассматривать его как другой сеанс.

SubServiceDao {
    // @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void save(Data input) {
        DBService.save(input);
    }
}

class SubService {
    public void insert(Iterator input) {
        while (input.hasNext()) {
             try {
                 SubServiceDao.save(input.next());
             } catch (Exception e) {
                 // Log the error and continue with next records
             }
        }
    }
}

person dora    schedule 23.01.2016    source источник
comment
‹У меня такое же требование (вставить список сущностей). Я хочу выполнить откат для всех исключений, кроме исключений, связанных с DuplicateKey. Не могли бы вы проверить проблему stackoverflow.com/questions/54343137/   -  person Alagammal P    schedule 13.02.2019


Ответы (2)


Хотя вы перехватываете исключение, Spring все равно сможет обнаружить, что что-то пошло не так с entityManager, и в таких случаях помечает транзакцию только как откат.

Это связано с тем, что entityManager Spring injects - это прокси, который перехватывает все вызовы перед делегированием реальным entityManager и сообщает ему, что исключение было сгенерировано реальным entityManager, даже если вы его поймаете.

Я вижу один вариант: вы можете начать новую транзакцию DBService.save(...) методом @Transactional(propagation=Propagation.REQUIRES_NEW). Таким образом, если какое-либо исключение возникает из-за недопустимых данных, это влияет на вставку только этой строки, а оставшаяся часть все равно будет продолжена.

person Madhusudana Reddy Sunnapu    schedule 23.01.2016
comment
Привет! Сам DBService аннотируется @Transactional. Я обновил свой вопрос, показывая класс DBService. В любом случае, я переместил часть, вызывающую DBService, в другой метод и пометил ее как Propagation.REQUIRES_NEW / NOT_SUPPORTED. Ни один не работал. - person dora; 24.01.2016
comment
Вы пытались добавить Propagation.REQUIRES_NEW в метод generalDao.save(input) в классе dao. Потому что вы должны добавить эту аннотацию к методу, который взаимодействует с entityManager. Я считаю, что generalDao.save(input) - это тот, который взаимодействует с entitymanager. Остальные другие могут работать в той же транзакции. - person Madhusudana Reddy Sunnapu; 24.01.2016
comment
generalDao здесь code.google.com/p/hibernate-generic-dao и я не могу изменить его реализацию для моей конкретной цели. Так что тут я особо ничего не могу сделать. - person dora; 24.01.2016
comment
Понятно. Хотя вы аннотировали метод save(input) в классе SubService с помощью REQUIRES_NEW, проблема, которую я вижу, заключается в том, что вы вызываете этот метод save непосредственно в цикле while. Если мы вызываем метод напрямую, он не будет проксироваться Spring AOP (без новой транзакции), и поэтому он будет работать в той же транзакции, что и вызывающий. В качестве быстрого обходного пути вы можете вызвать метод сохранения с помощью - ((SubService) AopContext.currentProxy()).save(input) или, лучше, переместить метод save(input) в новый компонент и вызвать его с помощью этого нового компонента. - person Madhusudana Reddy Sunnapu; 24.01.2016
comment
Извините за поздний ответ. Первоначальный ответ, который вы дали, помог мне в решении проблемы. Я добавил еще один DAO между SubService и DBService и переместил метод persist в этот класс. Я обновил свой ответ решением. - person dora; 27.01.2016
comment
@MadhusudanaReddySunnapu, я хочу вставить список клиентов, и при вставке, если мы получим повторяющиеся ключевые ошибки, я хочу igonore и вставить другие, если есть какие-либо другие ошибки, я хочу откатить всех клиентов. Не могли бы вы посоветовать ему добиться этого stackoverflow.com/questions/54343137/ - person Alagammal P; 30.01.2019

Из сеанса javadoc:

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

Таким образом, вся транзакция откатывается, и ничего не остается вставленным.

Выполните проверку (и необходимую блокировку при необходимости) перед вставкой и повторите всю транзакцию в случае сбоя.

person Dragan Bozanovic    schedule 23.01.2016