EntityManager não altera relacionamento (EJB 3.1 / JPA 2)

Criei um projeto Java EE (Enterprise Application), com um módulo WEB e outro EJB.

Estou alterando um registro no banco de dados que faz referência a outro.
Quando altero a chave estrangeira, a alteração não é refletida no banco de dados.

Recursos:

Provedor JPA: Eclipse Persistence Services - 2.0.1.v20100213-r6600
EJB: 3.1
DB: MySQL 5
Java: jdk1.6.0_18
Java EE: 6
JSF: 2.0
Contêiner: Glassfish-3.0.1
SO: Linux (Fedora 10)

Tenho duas tabelas:

[code]CREATE TABLE IF NOT EXISTS task (

id bigint(20) NOT NULL auto_increment,

title varchar(255) NOT NULL,

task_status_id bigint(20) NOT NULL,

PRIMARY KEY (id),

KEY FK_task_task_status_id (task_status_id)

);[/code]

[code]CREATE TABLE IF NOT EXISTS task_status (

id bigint(20) NOT NULL auto_increment,

name varchar(255) NOT NULL,

PRIMARY KEY (id)

);[/code]

Representação do banco de dados:

Tabela: task

id | title | task_status_id
1 | Tarefa 01 | 1

Tabela: task_status

id | name
1 | Aprovado
2 | Reprovado

Código exemplo:

@ManagedBean
public class TaskController {

    @EJB
    private TaskBeanLocal taskBean;

    public void edit() {
        Task task = taskBean.getTask(1L);
        task.setTitle("Titulo alterado");
        task.getTaskStatus().setId(2L);

        taskBean.update(task);
    }
}

No código acima estou alterando o título e o status da tarefa.
O resultado é o seguinte:

[quote]
FINE: UPDATE task SET title = ? WHERE (id = ?)
bind => [Titulo alterado, 1][/quote]

Ou seja, apenas o título é alterado.

Verifiquei dentro do “taskBean.update” e o id do objeto “TaskStatus” está com o valor correto (2), como segue:

[quote]EJB Chamado: TaskBean.update
– Task
---- id: 1
---- title: Titulo alterado
---- TaskStatus
------ iid: 2[/quote]

Apenas consigo alterar o ID do Status da seguinte forma:

        Task task = taskBean.getTask(1L);
        task.setTitle("Titulo novamente  alterado");

        TaskStatus taskStatus = new TaskStatus();
        taskStatus.setId(2L);

        task.setTaskStatus(taskStatus);

        taskBean.update(task);

O resultado é o seguinte:

[quote]FINE: UPDATE task SET title = ?, task_status_id = ? WHERE (id = ?)
bind => [Titulo novamente alterado, 2, 1][/quote]

Seguem os outros códigos:

Entidade Task

@Entity
@Table(name="task")
@NamedQueries({
    @NamedQuery(name = "Task.findAll", query = "SELECT t FROM Task t")
})
public class Task implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @Column(name="id")
    private Long id;

    @Column(name="title", nullable=false)
    private String title;

    @Column(name="description", nullable=false)
    private String description;
    
    @OneToOne
    @JoinColumn(name="task_status_id", nullable=false)
    private TaskStatus taskStatus = new TaskStatus();
    

    /** Getters and Setters */

    @Override
    public int hashCode() {
        int hash = 0;
        hash += (id != null ? id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Task other = (Task) obj;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public String toString() {
        return "core.task.Task[id=" + id + "]";
    }
}

Entidade TaskStatus

@Entity
@Table(name="task_status")
@NamedQueries({
    @NamedQuery(name = "TaskStatus.findAll", query = "SELECT t FROM TaskStatus t")
})
public class TaskStatus implements Serializable {
    @Id
    @GeneratedValue(strategy=GenerationType.SEQUENCE)
    @Column(name="id")
    private Long id;

    @Column(name="name", nullable=false)
    private String name;

    @OneToOne(mappedBy="taskStatus")
    private Task task;

    /** Getters and Setters */

    @Override
    public boolean equals(Object obj) {

        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final TaskStatus other = (TaskStatus) obj;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 11 * hash + (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    @Override
    public String toString() {
        return "TaskStatus{" + "id=" + id + "name=" + name + "task=" + task + '}';
    }
}

EJB TaskBean

@Stateless
public class TaskBean implements TaskBeanRemote, TaskBeanLocal {

    @PersistenceContext(unitName="CorePU")
    private EntityManager em;

    @Override
    public void update(Task task) {
        System.out.println("EJB Chamado: TaskBean.update");

        System.out.println("Task " + task.hashCode());
        System.out.println("  id: " + task.getId());
        System.out.println("  title: " + task.getTitle());
        System.out.println("  TaskStatus " + task.getTaskStatus().hashCode());
        System.out.println("    id: " + task.getTaskStatus().getId());

        em.merge(task);
    }

    @Override
    public Task getTask(Long id) {
        Task task = em.find(Task.class, id);

        return task;
    }
}

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="CorePU" transaction-type="JTA">
    <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
    <jta-data-source>jdbc/CorePU</jta-data-source>
    <properties>
      <property name="eclipselink.ddl-generation" value="create-tables"/>
      <property name="eclipselink.logging.level" value="FINE"/>
    </properties>
  </persistence-unit>
</persistence>

Em resumo, quero alterar o status de uma tarefa para o ID 2 (Reprovado), mas apenas o título da tarefa é alterado. Só consigo alterar o status quando crio um novo objeto. Essa ultima solução não é adequada, pois utilizo o JSF (versão 2.0) para popular os atributos dos meus objetos.

Alguém pode me ajudar solucionar esse problema?
Porque o primeiro código não funciona?

Agradeço desde já.

Henrique

Ola Henrique

Esse codigo aqui:
task.getTaskStatus().setId(2L);

Funciona bem no Hibernate. Pela spec o objeto a ser passado precisa ser managed no relacionamento. Talvez o eclipselink trabalhe esse ponto de maneira diferente. Experimente entao fazer:

task.setTaskStatus(entityManager.getReference(TaskStatus.class, 2L));

Olá Paulo Silveira,

Rodei o código com o Hibernate (versão 3.5.4) e funcionou.
Bom, estarei adotando o Hibernate no projeto.

Obrigado pela ajuda!

Ps: Se alguém souber porque o eclipselink não funciona dessa maneira, favor postar solução para quem precise utilizar esse provedor JPA.

Henrique