#java #oracle #jpa #eclipselink #spring-transactions
#java #Oracle #jpa #eclipselink #spring-транзакции
Вопрос:
Я пытаюсь сохранить много записей в одной транзакции, используя JPA (EclipseLink). Запись имеет первичный ключ, определенный в DB.
У меня есть несколько дубликатов в моей ленте, поэтому я знаю, что будет нарушение ограничений. Я хочу перехватить исключение нарушения ограничений для одного элемента и продолжить сохранение других.
Вот мой код:
Сущность:
@Entity
@Table(name="TEST")
public class TestEntity {
@Id
private String name;
public TestEntity() {
}
public TestEntity(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Операция:
@Transactional
public void test() {
List<String> list = Arrays.asList("A", "B","C","D","D","E","F","G");//duplicated D
for(String name: list) {
try {
TestEntity entity = new TestEntity(name);
logger.info("" entity);
entityManager.persist(entity);
logger.info("Persist done");
entityManager.flush();
logger.info("Flush done");
} catch(RuntimeException e) {
logger.warn("Entry {} was skipped due to following exception {}.", name, e.getLocalizedMessage());
}
}
logger.info("Importing dictionary finished. ");
}
В результате я получаю исключение нарушения ограничений для второго D (это то, что я ожидал), но я получаю это исключение и для следующих элементов, и это то, чего я не понимаю.
Я получаю: Внутреннее исключение: java.sql.SQLIntegrityConstraintViolationException: ORA-00001: уникальное ограничение (BBHDEV4.SYS_C0023890) нарушено
Что вызывает эту проблему?
Комментарии:
1. Вы должны удалить
@Transactional
. Это гарантирует, что либо все будет сохранено, либо изменения не будут внесены.
Ответ №1:
Ваш диспетчер объектов поддерживает состояние, состоящее из всех управляемых объектов (т.Е.. контекст сохранения) и транзакционное состояние его объектов.
Таким образом, после сброса, вызывающего нарушение ограничения, общий объект по-прежнему управляется и находится в устаревшем состоянии и будет снова сброшен при следующем явном вызове flush (вы не должны вызывать flush явно) или в конце текущей транзакции. вы должны вручную удалить общую сущность из контекста сохранения ( detach()
) перед возобновлением цикла.
В любом случае, если все ваши операции являются частью одной транзакции и ошибка выходит за пределы транзакции, диспетчер транзакций автоматически откатит всю транзакцию.
Транзакция — это единица работы, если вы не хотите, чтобы ваши разные операции были независимыми, вы должны выполнять их в разных транзакциях. Обычно область диспетчера сущностей совпадает с областью транзакции (по крайней мере, когда она управляется контейнером ie. вводится с помощью @PersitenceContext), и вы решите обе проблемы сразу.
Ответ №2:
Проблема в том, что вторая D
все еще является частью той же транзакции / единицы работы, поэтому на следующей итерации hibernate пытается сохранить ее снова.
Чтобы исправить это (используя ванильный JPA), вы можете либо
- Создайте новую транзакцию для каждого элемента, который вы хотите обновить.
- Выполните обновление с помощью инструкции JPA update.
В режиме гибернации у вас есть третий вариант, который заключается в использовании сеанса без состояния, но я не знаю, имеет ли eclipselink понятие entitymanager без состояния
Комментарии:
1. Есть ли какой-нибудь способ удалить этот объект из текущей транзакции?
2. не из текущей транзакции, а из текущего состояния EM:
detach()
(вызываетсяevict
в сеансе гибернации)3. Что сказал Гэб 🙂 — Просто имейте в виду, что это не стандартный вызов JPA, а скорее часть внутреннего API EclipseLink.
4. Это стандартный JPA: docs.oracle.com/javaee/6/api/javax/persistence /…
5. Как только вы получите исключение сохранения, вы не сможете продолжать использовать свой EntityManager — его состояние повреждено, и транзакция должна быть помечена для отката. Почему бы просто не получить новый EM?