EclipseLink JPA 持久化时重复条目

EclipseLink JPA Duplicate entry when persisting

我在尝试保留带注释的对象时遇到这个奇怪的错误。

这是我的代码:

if (hashTags != null && !hashTags.isEmpty()) {
    tweet.setHashTags(new LinkedList<HashTag>());
    for (HashTag hashTag : hashTags) {
        HashTag hashTagEntity = em
            .find(HashTag.class, hashTag.getTag());
        if (hashTagEntity == null) {
            LogWrapper.logger.debug("H: " + hashTag.getTag());
            em.persist(hashTag);
            tweet.getHashTags().add(hashTag);
        } else {
            tweet.getHashTags().add(hashTagEntity);
        }
    }
}

我知道,如果您尝试保留一个现有对象,您会得到该异常,但我确实在调用 persist 方法之前检查了这一点。此外,我找不到导致异常的主键,既不在我的日志文件中,也不在我的 MySQL 数据库中,这让我感到困惑,我如何获得一个异常,指示我的数据库中不存在的主键值第一名.

编辑

这些是我的注释类,其中我只显示属性和注释:

@Entity
public class Tweet {

    @Id
    @Column(name = "ID")
    private long id;
    private Usuario usuario;
    private String texto;
    @Temporal(TemporalType.TIMESTAMP)
    private Date fecha;
    private int numRetweets;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "TWEET_USUARIO_MENCIONADO", joinColumns = { @JoinColumn(name = "TWEET_ID", referencedColumnName = "ID") }, inverseJoinColumns = { @JoinColumn(name = "USUARIO_ID", referencedColumnName = "ID") })
    private LinkedList<Usuario> usuariosReferenciados;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "TWEET_HASHTAG", joinColumns = { @JoinColumn(name = "TWEET_ID", referencedColumnName = "ID") }, inverseJoinColumns = { @JoinColumn(name = "HASHTAG_ID", referencedColumnName = "ID") })
    private LinkedList<HashTag> hashTags;
    ...

@Entity
@Access(AccessType.FIELD)
public class Usuario {

    @Id
    @Column(name = "ID")
    private long id;
    private String nombreEnPantalla;
    private int numTweets;
    @Temporal(TemporalType.TIMESTAMP)
    private Date fechaRegistro;
    @OneToMany(mappedBy = "usuario", fetch = FetchType.EAGER)
    private LinkedList<Tweet> tweets;
    private int numFollowers;
    private double tweetsPerDay;
    ...

@Entity
public class HashTag {

    @Id
    @Column(name = "ID")
    private String tag;
    ...

这是我遇到问题的整个方法代码:

    public Tweet crearTweet(long idTweet, Date fechaTweet, int numRetweets,
            String textoTweet, long idUsuario, Date fechaRegistroUsuario,
            int followersUsuario, String nombreEnPantallaUsuario,
            int numTweetsUsuario, List<Usuario> usuariosMencionados,
            List<HashTag> hashTags) {
        EntityManager em = emf.createEntityManager();
        em.getTransaction().begin();
        Tweet tweet = new Tweet();
        tweet.setFecha(fechaTweet);
        tweet.setId(idTweet);
        tweet.setNumRetweets(numRetweets);
        tweet.setTexto(textoTweet);
        em.persist(tweet);
        Usuario user = em.find(Usuario.class, idUsuario);
        if (user == null) {
            user = new Usuario();
            user.setFechaRegistro(fechaRegistroUsuario);
            user.setId(idUsuario);
            user.setNombreEnPantalla(nombreEnPantallaUsuario);
            user.setNumFollowers(followersUsuario);
            user.setNumTweets(numTweetsUsuario);
            em.persist(user);
        }
        tweet.setUsuario(user);
        if (usuariosMencionados != null && !usuariosMencionados.isEmpty()) {
            tweet.setUsuariosReferenciados(new LinkedList<Usuario>());
            for (Usuario usuarioMencionado : usuariosMencionados) {
                Usuario usuarioEntity = em.find(Usuario.class,
                        usuarioMencionado.getId());
                if (usuarioEntity == null) {
                    LogWrapper.logger.debug("U: " + usuarioMencionado.getId()
                            + " " + usuarioMencionado.getNombreEnPantalla());
                    em.persist(usuarioMencionado);
                    tweet.getUsuariosReferenciados().add(usuarioMencionado);
                } else {
                    tweet.getUsuariosReferenciados().add(usuarioEntity);
                }
            }
        }
        if (hashTags != null && !hashTags.isEmpty()) {
            tweet.setHashTags(new LinkedList<HashTag>());
            for (HashTag hashTag : hashTags) {
                HashTag hashTagEntity = em
                        .find(HashTag.class, hashTag.getTag());
                if (hashTagEntity == null) {
                    LogWrapper.logger.debug("H: " + hashTag.getTag());
                    em.persist(hashTag);
                    tweet.getHashTags().add(hashTag);
                } else {
                    tweet.getHashTags().add(hashTagEntity);
                }
            }
        }
        em.getTransaction().commit();
        em.close();
        return tweet;
    } 

这是整个堆栈跟踪:

[EL Warning]: 2015-03-17 01:50:48.411--UnitOfWork(841203)--Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '??' for key 'PRIMARY'
Error Code: 1062
Call: INSERT INTO HASHTAG (ID) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(um.es.TFG.modelo.HashTag@7a77e4)
Exception in thread "main" javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '??' for key 'PRIMARY'
Error Code: 1062
Call: INSERT INTO HASHTAG (ID) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(um.es.TFG.modelo.HashTag@7a77e4)
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:159)
    at um.es.TFG.dao.jpa.JPATweetDAO.crearTweet(JPATweetDAO.java:76)
    at um.es.TFG.core.TweetProcessorMiner.startProcess(TweetProcessorMiner.java:105)
    at um.es.TFG.main.Main.main(Main.java:24)
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.6.0.v20150309-bf26070): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '??' for key 'PRIMARY'
Error Code: 1062
Call: INSERT INTO HASHTAG (ID) VALUES (?)
    bind => [1 parameter bound]
Query: InsertObjectQuery(um.es.TFG.modelo.HashTag@7a77e4)
    at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:331)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:902)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:964)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:633)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:560)
    at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2055)
    at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:306)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:242)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:228)
    at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:377)
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:165)
    at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:180)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:489)
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommit(InsertObjectQuery.java:80)
    at org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:90)
    at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:301)
    at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:58)
    at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:904)
    at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:803)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:108)
    at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:85)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:2896)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1857)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1839)
    at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1790)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitNewObjectsForClassWithChangeSet(CommitManager.java:227)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsForClassWithChangeSet(CommitManager.java:194)
    at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:139)
    at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4260)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1441)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1531)
    at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:278)
    at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1169)
    at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:134)
    ... 3 more
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry '??' for key 'PRIMARY'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source)
    at java.lang.reflect.Constructor.newInstance(Unknown Source)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:377)
    at com.mysql.jdbc.Util.getInstance(Util.java:360)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:971)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3887)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3823)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2435)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2582)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2530)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1907)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2141)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2077)
    at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2062)
    at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:892)
    ... 35 more

编辑 2

如果我在每次坚持操作后执行 em.flush() 并在我的代码中注释以下行,错误将停止弹出:

tweet.getUsuariosReferenciados().add(usuarioMencionado);
tweet.getUsuariosReferenciados().add(usuarioEntity);
tweet.getHashTags().add(hashTag);
tweet.getHashTags().add(hashTagEntity);

我正在评论以上几行,试图尽可能少地执行操作来定位问题,我已经尝试过不评论这些行,但如果我这样做,我仍然会收到错误消息,但正如我所说,让他们评论并每次坚持后刷新,我的代码 运行 很好,它只是不将实体的另外两个列表(HashTags 和提到的用户)与我的主要实体(Tweet)相关联。 所以我现在知道,由于我在一个事务中做的很多事情都没有刷新,所以我收到了错误,但我不知道为什么它说重复键。

深入查看EclipseLink 生成的日志后,我发现我的主要问题是由于不兼容的字符编码。我的源正在生成以 UTF-8 编码的文本,而我的数据库使用的是 latin1 编码。因此,在我的一个表中插入一个主键无法在 latin1 中正确表示的新行后,它被插入为 ???每个询问符号替换一个字符并且键的长度与原始单词的长度匹配,这就是为什么我得到重复输入异常的原因,因为在插入例如一些由 3 个奇怪字符组成的奇怪单词之后???当我试图插入另一个由 3 个不同的奇怪字符组成的不同的奇怪单词时,我会得到重复条目异常。我尝试将我的数据库中的表配置为使用 UTF-8,但我仍然遇到问题,因为 MySQL 中的 utf8 仅使用 3 个字节来表示一个字符,而我的源具有以 4 个字节编码的 UTF-8。所以我为我的表使用了 utf8mb4,但这不足以解决问题,我需要用 character_set_server=utf8mb4 配置 MySQL 服务器,重新启动服务器,这样 Connector/J 就可以了正确地自动检测 UTF-8 设置。