javax.persistence.RollbackException выбрасывается при добавлении отношения ManyToOne / OneToOne к сущности

1

Сценарий: у меня есть веб-приложение JSF 2.1, работающее на сервере tomcat, с hibernate, реализующим интерфейс jpa и базу данных mysql.
Пользователи webapp регистрируются как Entity в базе данных, и есть группа данных под названием "Комнаты", которые имеют свой собственный Entity, где пользователи имеют доступ как собственные или внешние менеджеры. Чтобы сохранить концепцию "внешнего управления" в базе данных, у меня есть надлежащее сущность для каждого руководства; внешнее управление сохраняется как переключение OneToMany в User Entity.

Здесь у вас есть зависимости Hibernate, поскольку они были добавлены в pom.xml проекта:

  <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-core</artifactId>
        <version>4.3.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-entitymanager</artifactId>
        <version>4.3.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-c3p0</artifactId>
        <version>4.3.5.Final</version>
    </dependency>
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.1.0.Final</version>
    </dependency>

Проблема. По какой-то нечетной причине добавление кода для внешнего ключа в управляемую комнату во внешнем управлении с отношением ManyToOne разбивает Hibernate: любое обновление списка внешнего свойства управления для пользователя, когда этот внешний ключ present, throws RollbackException - "Транзакция отмечена как откат только":

Caused by: it.katuiros.core.db.dao.DataAccessException: Data Access error while doing final commit.
at it.katuiros.core.db.dao.impl.GenericDaoImpl.closeDataAccess(GenericDaoImpl.java:222)
at it.katuiros.core.db.dao.impl.GenericDaoImpl.update(GenericDaoImpl.java:96)
at it.katuiros.appLogic.PersistenceTest.saveTestData(PersistenceTest.java:157)
at it.katuiros.beans.generics.TestBean.<init>(TestBean.java:44)
... 67 more

Caused by: javax.persistence.RollbackException: Transaction marked as rollbackOnly
at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)
at it.katuiros.core.db.dao.impl.GenericDaoImpl.closeDataAccess(GenericDaoImpl.java:217)
... 70 more

Как вы, наверное, знаете, грустная вещь о том, что RollbackException в Hibernate заключается в том, что они НЕОБХОДИМО!

КОД
Пользовательский объект:

@Entity
@Table(name = "USER")
@Access(AccessType.FIELD)
public class User extends DataModelEntity {
    /**
     * Serialization ID
     */
    private static final long serialVersionUID = -1154710247841840471L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Basic(optional = false)
    @Column(name = "ID")
    protected int id;

    @NotNull
    @Column(name = "USERNAME", unique = true)
    protected String username;

    @NotNull
    @Column(name = "PASSWORD")
    protected String password;

    @NotNull
    @Column(name = "EMAIL", unique = true)
    protected String email;

    @Column(name = "FIRST_NAME")
    protected String firstName;

    @Column(name = "LAST_NAME")
    protected String lastName;

    @Column(name = "PHONE")
    protected String telefono;

    @ManyToMany
    @LazyCollection(LazyCollectionOption.FALSE)
    @Basic(optional = false)
    @JoinColumn(name = "FAVORITE_ROOMS", referencedColumnName = "ID", updatable = true)
    protected Set<Room> favoriteRooms;

    @OneToMany
    @LazyCollection(LazyCollectionOption.FALSE)
    @Basic(optional = false)
    @JoinColumn(name = "REGISTERED_ROOMS", referencedColumnName = "ID", updatable = true)
    protected Set<Room> registeredRooms;

    @OneToMany
    @LazyCollection(LazyCollectionOption.FALSE)
    @Basic(optional = true)
    @JoinColumn(name = "EXTERNAL_MANAGEMENTS", referencedColumnName = "ID", updatable = true)
    protected Set<ExternalRoomManagementPermissions> externalManagements;

    @OneToOne
    @JoinColumn(name = "AVATAR", referencedColumnName = "ID", updatable = true)
    protected Image avatar;

    ... constructors and getters/setters ...
}

Внешняя организация управления

@Entity
@Table(name = "EXTERNAL_ROOM_MANAGEMENT_PERMISSIONS")
@Access(AccessType.FIELD)
public class ExternalRoomManagementPermissions extends DataModelEntity {

    /**
     * Serialization ID
     */
    private static final long serialVersionUID = 7122195658297760351L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Basic(optional = false)
    @Column(name = "ID")
    private int id;

    /* CRIMINAL CODE */
    @ManyToOne
    @NotNull
    @JoinColumn(name = "MANAGED_ROOM", referencedColumnName = "ID")
    private Room managedRoom;
    /* END OF CRIMINAL CODE */

    ... constructors and getters/setters ...
}

Я не буду публиковать код Entity Room, так как это не так важно.

Этот объект доступа к данным, который используется для обновления объекта

public abstract class GenericDaoImpl<T> implements GenericDao<T> {

protected EntityManager em;

private Class<T> type;

public GenericDaoImpl() {
    Type t = getClass().getGenericSuperclass();
    ParameterizedType pt = (ParameterizedType) t;
    type = (Class) pt.getActualTypeArguments()[0];
    this.em=null;
}

protected void finalize() throws Throwable{
    this.closeDataAccess();
    super.finalize();
}

/* DATA ACCESS LOGICS */
/* BASIC CRUD OPERATIONS */ 

@Override
public void create(final T t)  throws DataAccessException  {
    try{
        this.startDataAccess();
        this.em.persist(t);
    }catch(Exception e){
        throw new DataAccessException("Data Access error while persisting Object.", e);
    }finally{
        this.closeDataAccess();
    }
}

@Override
public void delete(final Object id)  throws DataAccessException {
    try{
        this.startDataAccess();
        this.em.remove(this.em.getReference(type, id));
    }catch(Exception e){
        throw new DataAccessException("Data Access error while removing Object.", e);
    }finally{
        this.closeDataAccess();
    }
}

@Override
public T find(final Object id)  throws DataAccessException {
    T result = null;
    try{
        this.startDataAccess();
        result = (T) this.em.find(type, id);
    }catch(Exception e){
        throw new DataAccessException("Data Access error while finding Object.", e);
    }finally{
        this.closeDataAccess();
    }
    return result;
}

@Override
public T update(final T t) throws DataAccessException {
    T result = null;
    try{
        this.startDataAccess();
        result = (T) this.em.merge(t);
    }catch(Exception e){
        throw new DataAccessException("Data Access error while updating Object.", e);
    }finally{
        this.closeDataAccess();
    }
    return result;
}

/* MORE OPERATIONS */

/**
 * Method that returns the number of entries from a table that meet some
 * criteria (where clause params)
 *
 * @param params
 *            sql parameters
 * @return the number of records meeting the criteria
 */
@Override
public long countAll(final Map<String, Object> params) throws DataAccessException {
    long result = -1;

    try{
        this.startDataAccess();
        final StringBuffer queryString = new StringBuffer(
                "SELECT count(o) from ");

        queryString.append(type.getSimpleName()).append(" o ");
        queryString.append(this.getQueryClauses(params, null));

        final Query query = this.em.createQuery(queryString.toString());

        result = (Long) query.getSingleResult();
    }catch(Exception e){
        throw new DataAccessException("Data Access error while counting Object.", e);
    }finally{
        this.closeDataAccess();
    }

    return result;
}

/**
 * This method returns the whole list of all T entities available on the datasource.
 * 
 * @return the whole list of all T entities.
 */
@Override
public List<T> list()  throws DataAccessException {
    List<T> resultList = null;

    try{
        this.startDataAccess();
        final StringBuffer queryString = new StringBuffer("SELECT o from ");

        queryString.append(type.getSimpleName()).append(" o ");

        final Query query = this.em.createQuery(queryString.toString());

        resultList =(List<T>) query.getResultList();
    }catch(Exception e){
        throw new DataAccessException("Data Access error while retrieving list of Objects.", e);
    }finally{
        this.closeDataAccess();
    }

    return resultList;
}

/* MISCELLANEOUS UTILITY METHODS */

protected String getQueryClauses(final Map<String, Object> params,
        final Map<String, Object> orderParams) {
    final StringBuffer queryString = new StringBuffer();
    if ((params != null) && !params.isEmpty()) {
        queryString.append(" where ");
        for (final Iterator<Map.Entry<String, Object>> it = params
                .entrySet().iterator(); it.hasNext();) {
            final Map.Entry<String, Object> entry = it.next();
            if (entry.getValue() instanceof Boolean) {
                queryString.append(entry.getKey()).append(" is ")
                        .append(entry.getValue()).append(" ");
            } else {
                if (entry.getValue() instanceof Number) {
                    queryString.append(entry.getKey()).append(" = ")
                            .append(entry.getValue());
                } else {
                    // string equality
                    queryString.append(entry.getKey()).append(" = '")
                            .append(entry.getValue()).append("'");
                }
            }
            if (it.hasNext()) {
                queryString.append(" and ");
            }
        }
    }
    if ((orderParams != null) && !orderParams.isEmpty()) {
        queryString.append(" order by ");
        for (final Iterator<Map.Entry<String, Object>> it = orderParams
                .entrySet().iterator(); it.hasNext();) {
            final Map.Entry<String, Object> entry = it.next();
            queryString.append(entry.getKey()).append(" ");
            if (entry.getValue() != null) {
                queryString.append(entry.getValue());
            }
            if (it.hasNext()) {
                queryString.append(", ");
            }
        }
    }
    return queryString.toString();
}

protected void startDataAccess() throws DataAccessException{
    this.em = ContextEMF.createEntityManager();
    if (this.em==null) throw new DataAccessException("No Entity Manager was created! Check if ContextEMF is correctly working.");
    this.em.getTransaction().begin();
}

protected void closeDataAccess() throws DataAccessException{
    if (this.em!=null){
        try{
            if (this.em.isOpen()){
                this.em.getTransaction().commit();
                this.em.clear();
                this.em.close();
            }
        }catch(Exception e){
            throw new DataAccessException("Data Access error while doing final commit.", e);
        }           
        this.em=null;
    }
}

}

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

this.externalManagement1 = new ExternalRoomManagementPermissions(this.room4);
this.dbAccessor.saveData(this.externalManagement1);
this.externallyManagedRoomsForUser1.add(this.externalManagement1);
this.user1.setRegisteredRooms(this.registeredRoomsForUser1);
this.user2.setRegisteredRooms(this.registeredRoomsForUser2);
userDao.update(this.user1);
userDao.update(this.user2);
this.user1.setExternalManagements(this.externallyManagedRoomsForUser1);

userDao.update(this.user1);  // <---- EXPLODING LINE

Последние две строки являются критическим шагом: если внешняя управляющая организация имеет этот "уголовный код" о отношении ManyToOne к управляемой комнате, userDao.update(...) будет генерировать исключение; если "уголовный код" опущен, userDao.update(...) НЕ выбрасывает исключение.

(Комната уже была сохранена в базе данных до того, как я использую ее в этом последнем сегменте кода)

Sumptum: У меня есть отношения "один-к-одному" на ExternalRoomManagementPermissions, в котором есть один номер в качестве дочернего элемента, и один-на-один на пользователе, у которого есть коллекция ExternalRoomManagementPermissions. Поскольку я объяснил наличие первого отношения (внешнего управления в комнате) в коде - "Уголовный кодекс" - заставляет пользователя не обновляться с помощью коллекции ExternalRoomManagementPermissions (второе отношение).

Вопрос: К сожалению, мне нужно, чтобы ManyToOne отношение к управляемой комнате, знаете ли вы, почему это нарушает постоянство?

Post-Scriptum: я попытался изменить отношение ManyToOne к OneToOne, но это не повлияло.

  • 0
    Где вы декларируете эту транзакцию? Ваша трассировка стека в основном говорит о том, что вы не можете зафиксировать результат транзакции либо потому, что было сгенерировано исключение, либо транзакция объявлена как необязательная. А почему ты обновляешься дважды? Просто поместите один вызов обновления в конце для user1.
  • 0
    Я тоже выложу код Дао
Показать ещё 16 комментариев
Теги:
hibernate
jpa
persistence
rollback

1 ответ

3
Лучший ответ

ОК!

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

   "org.hibernate.InstantiationException: No default constructor for entity: : it.katuiros.model.entities.ExternalRoomManagementPermissions"

Как бы то ни было, исключение было запущено внутри слоя Hibernate и проглочено в том же месте, поэтому исключение RollbackException само по себе не было причиной. Я, вероятно, опубликую сообщение об ошибке на форуме/группах Hibernate.

Добавив пустой конструктор по умолчанию к ExternalRoomManagementPermissions, мне удалось заставить код работать:

public ExternalRoomManagementPermissions(){
    super();
}

Большое спасибо Тиму Холлоуэю от кодера, Маби и Джеймса Мэсси от stackoverflow за помощь!

  • 1
    Поздравляю с решением этого. Не знаю, поддерживает ли это ваша IDE, но мне очень повезло с применением метода «разбить на каждое исключение» непосредственно перед запуском критической части (приложения JSF выдают допустимые исключения, поэтому не оставляйте это включенным). Откат - это 99,9% признак того, что метод не завершился нормально, и было сгенерировано исключение.
  • 0
    Да, я использую затмение. В любом случае, проглатывание исключений происходит из-за плохого управления исключениями «попробуй / поймай», что очень плохо в стандартизированной и всемирно известной библиотеке, такой как Hibernate.
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню