持久化

时间:2016-11-03 23:18:22

标签: java hibernate jpa java-ee entitymanager

我有一个AngularJS前端,它对运行Wildfly 10的Java后端的不同资源发出多个请求。

每个资源端点查询数据库(MySQL 5.6)以使用访问令牌中的唯一用户ID查找用户(它不是具有唯一索引的主键)。

如果在数据库中找不到用户,我会从访问令牌信息创建它,请参阅下面的内容:

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个异步请求)都会触发数据库上的INSERT,从而导致:

MySQLIntegrityConstraintViolationException: Duplicate entry ...

即使第一个请求已经在数据库上创建了新用户。第一次登录后一切正常。

有什么想法吗?

更新

我在UserRegistrationService中创建了一个方法来搜索用户并使用findOrCreateUser方法共享相同的EM。

此处它是EntityManager hashCode()输出:

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:日志

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 个答案:

答案 0 :(得分:0)

  

(我们的主页提出3个异步请求)

我认为这些请求不会被一个一个一个地调用(毕竟是异步)

所以简单地说你有比赛条件。 要证明我是对还是错,对于测试,请findOrCreate方法synchronized 所以delcare如下:

public synchronized User findOrCreateUser(AccessToken token)  

它应该解决问题(广告会产生副作用,它会序列化请求,但这是一个不同的问题)

答案 1 :(得分:0)

也许你这里有逻辑错误

    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
  • 的代码

但无论如何,恕我直言,它是concurrenc中的查找或创建逻辑(多个异步调用)很可能必须同步。