我有一个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)
谢谢!
答案 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中的查找或创建逻辑(多个异步调用)很可能必须同步。