Запрос JPA не находит сущность после persist

У меня есть интерфейс AngularJS, который делает несколько запросов к различным ресурсам на Java back-end под управлением Wildfly 10.

Каждая конечная точка ресурса запрашивает базу данных (MySQL 5.6), чтобы найти пользователя, используя уникальный идентификатор пользователя из маркера доступа (это не первичный ключ, он имеет уникальный индекс).

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

public abstract class AbstractService {

    @PersistenceContext
    protected EntityManager em;

    ...

}

public abstract class AbstractResource extends AbstractService {

    @EJB
    UserService userService;

    @EJB
    UserRegistrationService userRegistrationService;

    public User getUser(AccessToken token) {

        User user = userService.findByKcId(token.getUserId());
        if (user == null) {
            user = userRegistrationService.findOrCreateUser(token);
        }

        return user;
    }

    ...
}

Я создал синглтон, чтобы убедиться, что пользователь создан, только если он действительно еще не существует.

@Singleton
@Startup
public class UserRegistrationService {

    @PersistenceContext
    EntityManager em;

    @EJB
    UserService userService;

    public User findOrCreateUser(AccessToken token) {
        String kcId = token.getUserId();

        User user = this.findByKcId(kcId);
        if (user == null) {
            user = new User();
            user.setKcId(kcId);   

            ...

            em.persist(user);
            em.flush();
            em.refresh(user);
        }

        return user;
    }

    private User findByKcId(String kcId) {

        CriteriaBuilder cb = em.getCriteriaBuilder();

        CriteriaQuery<User> criteria = cb.createQuery(User.class);
        Root<User> user = criteria.from(User.class);
        criteria.select(user).where(cb.equal(user.get(User_.kcId), kcId));

        List<User> users = em.createQuery(criteria)
                .setMaxResults(1)
                .getResultList();
        if (users.isEmpty()) {
            return null;
        }

        return users.get(0);
    }

    ...
}

Но по какой-то причине при первом входе пользователя все запросы (наша домашняя страница делает 3 асинхронных запроса) инициируют вставку в базу данных, в результате чего:

MySQLIntegrityConstraintViolationException: Duplicate entry ...

Несмотря на то, что первый запрос уже создал нового пользователя в базе данных. После первого входа все работает нормально.

Есть идеи?

ОБНОВЛЕНИЕ:

Я создал метод внутри UserRegistrationService для поиска пользователя и совместного использования того же EM с методом findOrCreateUser.

Также здесь вывод хэш-кода EntityManager() :

11:53:32,344 INFO  [stdout] (default task-21) UserService.findKcById: 11694883
11:53:32,416 INFO  [stdout] (default task-21) UserRegistrationService.findOrCreateUser: 212546987
11:53:32,416 INFO  [stdout] (default task-21) UserRegistrationService.findKcById: 212546987
11:53:32,423 INFO  [stdout] (default task-20) UserService.findKcById: 11694883
11:53:32,495 INFO  [stdout] (default task-20) UserRegistrationService.findOrCreateUser: 212546987
11:53:32,495 INFO  [stdout] (default task-20) UserRegistrationService.findKcById: 212546987
11:53:32,553 INFO  [stdout] (default task-26) UserService.findKcById: 11694883

ОБНОВЛЕНИЕ 2: LOG

Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '8f262ed0-3868-449e-aea8-b2af55209479' for key 'kc_id2_UNIQUE'
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:404)
at com.mysql.jdbc.Util.getInstance(Util.java:387)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:934)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3870)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3806)
at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2470)
at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2617)
at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2550)
at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1861)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2073)
at com.mysql.jdbc.PreparedStatement.executeUpdateInternal(PreparedStatement.java:2009)
at com.mysql.jdbc.PreparedStatement.executeLargeUpdate(PreparedStatement.java:5094)
at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1994)
at org.jboss.jca.adapters.jdbc.WrappedPreparedStatement.executeUpdate(WrappedPreparedStatement.java:537)

Спасибо!

2 ответа

  1. (наша домашняя страница делает 3 асинхронных запроса)

    Я предполагаю, что эти запросы вызываются не 1 одним byt все сразу (асинхронный в конце концов)

    Так просто говоря вы получили гоночные условия.
    Чтобы доказать, прав я или нет, для тестов, сделать findOrCreateметод synchronized
    поэтому delcare это следующим образом:

    public synchronized User findOrCreateUser(AccessToken token)  
    

    это должно устранить проблему (ad nas побочный эффект он будет сериализовать запросы, но это другая проблема)

  2. Возможно, у вас есть логическая ошибка здесь

        User user = userService.findByKcId(token.getUserId());
        if (user == null) {
            user = new User();
            ...
    
            em.persist(user);
            em.flush();
            em.refresh(user);
        }
    

    Возможно
    userService.findByKcId(token.getUserId());
    не возвращается пользователь в то время как он должен из-за например. ошибка в SELECTзапросе.

    Форма будущего расследования:

    • Покажите нам остальной код создания пользователя
    • Покажите нам точное ConstrainViolation
    • Покажите нам код для userService.findByKcId

    Но в любом случае IMHO это найти или создать логику в concurrenc env (несколько асинхронных вызовов), скорее всего, должны быть синхронизированы.