JPA. Проблемы с транзакциями при переходе с Hibernate на Eclipselink - PullRequest
4 голосов
/ 12 октября 2011

Стандартная среда Java EE: JPA + EJB вызывается из bean-компонентов JSF.Сервер: glassfish 3.1.1

Код, который разработан, протестирован и развернут с Hibernate в качестве поставщика JPA, отказывается сохранять сущности с Eclipselink - поставщиком JPA по умолчанию в glassfish:

Вот код Iвызов из JSF bean:

@Singleton
@Startup
public class SecurityService {
  @PersistenceContext(unitName = "unit01")
  private EntityManager em;

  @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
  public String createNewUser(String login)  {
    UserImpl newUser = new UserImpl();
    newUser.setLogin(login);
    em.persist(newUser);
    //em.flush() ------> works fine when uncommented 

    DirectoryImpl userHome = new DirectoryImpl();
    userHome.setOwner(newUser);
    em.persist(userHome); // Throws exception due to FK violation
    //
    //

Как прокомментировано, код работает, только если я очищаю EntityManager после сохранения нового пользователя, что, очевидно, неправильно.При тестировании других методов я обнаружил, что некоторые другие изменения сбрасываются в БД только при выключении сервера.

Все вышеизложенное происходит в новой установке GF, в дизайне чистой Java EE без каких-либо Spring или каких-либо платформ.

Вот мой файл persistence.xml:

<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
  <persistence-unit name="unit01" transaction-type="JTA">
    <jta-data-source>jdbc/Unit01DS</jta-data-source>
    <properties>
      <property name="eclipselink.logging.level" value="FINE" />
    </properties>
  </persistence-unit>
</persistence>

Вот как я создаю источник данных:

asadmin create-jdbc-connection-pool --datasourceclassname com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource --restype javax.sql.ConnectionPoolDataSource --property "User=u1:Password=xxx:URL=jdbc\:mysql\://localhost/dbname" Unit01DS
asadmin create-jdbc-resource --connectionpoolid Unit01DS jdbc/Unit01DS

Вот и все, другие конфигурационные файлы / опции не используются.Так почему мой код отлично работает в Hibernate и ведет себя совершенно по-другому в Eclipselink?Есть идеи?

ОБНОВЛЕНИЕ

Дальнейшие исследования показали, что проблема лежит где-то в отображениях сущностей.В моем случае обе упомянутые сущности (UserImpl и DirectoryImpl) наследуются от одного корневого класса, как показано ниже:

@Entity
@Table(name = "ob10object")
@Inheritance(strategy = InheritanceType.JOINED)
public class RootObjectImpl implements RootObject {
  private Long id;

  @Id
  @Column(name = "ob10id", nullable = false)
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  public Long getId() {
    return id;
  }
  //  

UserImpl является прямым подклассом корневой сущности и не имеет ссылокдругим объектам

@Entity
@Table(name = "as10user")
@DiscriminatorValue(value = "AS10")
public class UserImpl extends RootObjectImpl implements User {
  private String login;
  //
  //

Несколько более сложный случай: DirectoryImpl:

@Entity
@Table(name = "st20directory")
@DiscriminatorValue(value = "ST20")
public class DirectoryImpl extends AbstractStorageNode implements Directory {
  // Inside there are also no references to other entities 

Где AbstractStorageNode также расширяет корневой объект:

@Entity
@Table(name = "st10storage_node")
public abstract class AbstractStorageNode extends RootObjectImpl implements StorageNode {
  private Set<AbstractStorageNode> childNodes;
  private StorageNode parentNode;
  private User owner;


  @OneToMany(mappedBy = "parentNode")
  public Set<AbstractStorageNode> getChildNodes() {
    return childNodes;
  }

  @ManyToOne(targetEntity = AbstractStorageNode.class)
  @JoinColumn(name="st10parent", nullable = true)
  public StorageNode getParentNode() {
    return parentNode;
  }

  @ManyToOne(targetEntity = UserImpl.class)
  @JoinColumn(name = "as10id") // this is FK to `UserImpl` table.
  public User getOwner() {
    return owner;
  }

  // Setters omitted

Сейчасвот сгенерированный sql:

// Creating user:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v1', 'v2',  'AS10')
SELECT LAST_INSERT_ID()
// Creating Directory:
INSERT INTO ob10object (field1, field2, ob10discriminator) VALUES ('v11', 'v22',  'ST20')
SELECT LAST_INSERT_ID()
INSERT INTO st10storage_node (st10name, as10id, st10parent, ob10id) VALUES ('home', 10, null, 11)

Теперь я вижу, что происходит: eclipselink не вставляет данные в таблицу User (as10user), вызывая нарушение FK в последнем запросе.Но все же я понятия не имею, почему это происходит.

ОБНОВЛЕНИЕ 2

Вот исключение:

Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`dbname`.`st10storage_node`, CONSTRAINT `F_ST10_AS10` FOREIGN KEY (`as10id`) REFERENCES `as10user` (`ob10id`))
Error Code: 1452

Ответы [ 4 ]

2 голосов
/ 12 октября 2011

Итак, без сброса вы получаете ошибку ограничения? Пожалуйста, включите исключение, трассировку стека и журнал SQL, а также укажите, как вы отображаете отношения и как вы определяете идентификаторы.

Есть ли у вас двунаправленные отношения? EclipseLink может пытаться разрешить двунаправленные отношения, вызывающие вашу ошибку, вам может потребоваться удалить ненулевое ограничение, чтобы разрешить вставку в вашу таблицу, или определить зависимость ограничения с помощью настройщика, или исправить отображение двунаправленных отношений.

Обновление

Ошибка возникает из-за того, что у вас есть ограничение, определенное для дочерней пользовательской таблицы, а не для родительской корневой таблицы, где определен Id. По умолчанию EclipseLink откладывает запись во вторичные таблицы, на которые нет ссылок, чтобы оптимизировать транзакцию и избежать блокировок базы данных.

Технически EclipseLink должен находить ссылку на Пользователя и не делать этого, если вы используете последнюю версию, возможно, она исправлена, в противном случае зарегистрируйте ошибку и проголосуйте за нее. Вы можете решить эту проблему, используя DescriptorCustomizer и задав свойство setHasMultipleTableConstraintDependecy (true) в дескрипторе Root.

Кроме того, у вас, похоже, есть все ваши классы, использующие одну и ту же корневую таблицу, которая, кажется, определяет только идентификатор, это, вероятно, не очень хорошая идея. Возможно, вам лучше сделать Root @MappedSuperclass и иметь таблицу только для конкретных подклассов.

0 голосов
/ 14 октября 2011

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

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @Id
    @Basic(optional = false)
    private String login;
}

Обратите внимание на аннотации @ Id и @ Basic .Я не вижу, чтобы вы использовали какие-либо аннотации к атрибутам UserImpl.

Я предлагаю вам пересмотреть аннотации ваших сущностей.

0 голосов
/ 12 октября 2011

Полагаю, что потребуется больше подробностей (например, опубликовать код вашей сущности), так как в моем случае довольно похожий код выполняется без проблем.Просто посмотрите и дайте знак, если вы заметите какие-либо различия.

Это Glassfish 3.1.1 с EclipseLink.

Код EJB:

@Stateless
public class MyEJB {

    @PersistenceContext
    EntityManager em;

    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    public void doSmth() {
        UserData ud = new UserData();
        em.persist(ud);

        Website w = new Website();
        w.setUser(ud);

        em.persist(w);

        System.out.println("!!!!!" + w);
    }   
}

Сущность № 1:

@Entity
public class UserData implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    public Long getId() {
        return id;
    }

    @Override
    public String toString() {
        return "com.entities.UserData[ id=" + id + " ]";
    }
}

№ сущности 2:

@Entity
public class Website implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @OneToOne(optional=false)
    private UserData user;

    public Long getId() {
        return id;
    }

    public void setUser(UserData u){
        user = u;
    }

    public UserData getUser() {
        return user;
    }

    @Override
    public String toString() {
        return "com.entities.Website[ id=" + id + ", " + user +" ]";
    }
}

persistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
   <persistence-unit name="TransactionsPU" transaction-type="JTA">
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
         <jta-data-source>jdbc/__default</jta-data-source>
         <exclude-unlisted-classes>false</exclude-unlisted-classes>
         <properties>
            <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
         </properties>
   </persistence-unit>
</persistence>

HTH.

0 голосов
/ 12 октября 2011

Я думаю, вы обнаружите, что оба подхода действительно соответствуют спецификации JPA 2.http://download.oracle.com/auth/otn-pub/jcp/persistence-2.0-fr-eval-oth-JSpec/persistence-2_0-final-spec.pdf?e=1318430009&h=d3d726bc7bec224bd8f803aba90d1f13

См. Раздел 3.2.2

3.2.2 Сохранение экземпляра объекта

Новый экземпляр объекта становится управляемым и постоянным, вызывая метод persistна нем или путем каскадного сохранения операции.Семантика операции постоянства, применяемая к объекту X, выглядит следующим образом:

• Если X является новым объектом, он становится управляемым.Объект X будет введен в базу данных во время или до принятия транзакции или в результате операции сброса ......

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

Флэш-вызов может показаться плохим, но он должен работать и в Hibernate.Он записывает данные в БД, но не удаляет их для EntityManager, поэтому сущность остается присоединенной.

Надеюсь, это поможет.

...