Странная поддержка весенних транзакций для JPA + Hibernate + @ Транзакционная аннотация - PullRequest
8 голосов
/ 26 мая 2010

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

У меня есть простой пользовательский дао, который расширяет весенний класс JpaDaoSupport и содержит стандартный метод сохранения:

@Transactional
public User save(User user) {
    getJpaTemplate().persist(user);
    return user;
}

Если все работало нормально, пока я не добавил новый метод в тот же класс: пользователь getSuperUser (), этот метод должен возвращать пользователя с isAdmin == true, и если в БД нет суперпользователя, метод должен его создать. Вот как это выглядело:

 public User createSuperUser() {
    User admin = null;

    try {
        admin = (User) getJpaTemplate().execute(new JpaCallback() {
            public Object doInJpa(EntityManager em) throws PersistenceException {
                return em.createQuery("select u from UserImpl u where u.admin = true").getSingleResult();
            }
        });
    } catch (EmptyResultDataAccessException ex) {
        User admin = new User('login', 'password');
        admin.setAdmin(true);
        save(admin); // THIS IS THE POINT WHERE STRANGE THING COMING OUT
    }

    return admin;
}

Как вы видите, код выглядит странно, и я был очень смущен, когда узнал, что транзакция не была создана и зафиксирована при вызове метода save (admin) и новый пользователь фактически не был создан, несмотря на аннотацию @Transactional.

В результате мы имеем ситуацию: когда метод save () вызывает извне класса UserDAO - аннотация @Transactional считается и пользователь успешно создается, но если save () вызывает изнутри другого метода того же класса dao - аннотация @Transactional игнорироваться.

Вот как я изменил метод save (), чтобы заставить его всегда создавать транзакцию.

public User save(User user) {
    getJpaTemplate().execute(new JpaCallback() {
        public Object doInJpa(EntityManager em) throws PersistenceException {
            em.getTransaction().begin();
            em.persist(user);
            em.getTransaction().commit();
            return null;
        }
    });
    return user;
}

Как видите, я вручную запускаю begin и commit. Есть идеи?

Ответы [ 3 ]

9 голосов
/ 26 мая 2010
  1. @Transactional учитывается только для вызовов извне объекта. Для внутренних звонков это не так. Чтобы обойти это, просто добавьте @Transactional к вашей точке входа.
  2. Не используйте @Transactional для своего DAO - используйте его вместо классов обслуживания.
7 голосов
/ 26 мая 2010

Думаю, декларативные транзакции с аннотацией реализованы на основе прокси.

Если вы обращаетесь к своему DAO через динамический прокси-сервер, он проверяет, есть ли аннотация, и оборачивает его транзакцией.

Если вы вызываете свой класс изнутри класса, невозможно перехватить этот вызов.

Чтобы избежать проблемы, вы можете пометить метод createSuperuser также аннотацией.

0 голосов
/ 26 мая 2010

Ваша проблема связана с ограничениями Spring AOP. ответ Божо хорош, и вы должны рассмотреть возможность рефакторинга своего кода для поддержки его советов.

Но если вы хотите, чтобы ваша транзакция работала без изменения кода, это возможно!

Spring AOP является выбором по умолчанию в технологиях аспектов Spring. Но с некоторой конфигурацией и добавлением переплетения AspectJ это будет работать, так как AspectJ является гораздо более мощной технологией, которая позволяет создавать точки между двумя методами в одном классе.

...