Bonjour,
Je n'arrive pas à mettre en place les transactions dans un environnement JPA/Hibernate et annotations.
Voici le contexte :
J'ai choisi l'option TABLE pour générer la clé primaire de la table employee, cette clé primaire est bien générée, mais comme sa génération n'est pas prise dans la même transaction que celle de la création de la nouvelle occurrence de employee, j'ai une erreur duplicate primary key. Voici les détails de mon environnement de travail (il s'agit d'une reproduction sur un petit exemple et pas de l'application réelle, mais ce sera plus simple à comprendre) :
IDE : NetBeans 11.2
Type d'application : Enterprise Application nommée EmployeesJPA (ant) avec deux modules : EmployeesJPA-ejb et EmployeesJPA-war
ORM : JPA avec implémentation sous Hibernate 4.3
Base de données : MySql
EJB Entity : Employee
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
@Entity
@Table(name = "employee")
@XmlRootElement
@NamedQueries({
    @NamedQuery(name = "Employee.findAll", query = "SELECT e FROM Employee e"),
    @NamedQuery(name = "Employee.findByEmployeeId", query = "SELECT e FROM Employee e WHERE e.employeeId = :employeeId"),
    @NamedQuery(name = "Employee.findByName", query = "SELECT e FROM Employee e WHERE e.name = :name"),
    @NamedQuery(name = "Employee.findByManagerId", query = "SELECT e FROM Employee e WHERE e.managerId = :managerId"),
    @NamedQuery(name = "Employee.findByHiredate", query = "SELECT e FROM Employee e WHERE e.hiredate = :hiredate"),
    @NamedQuery(name = "Employee.findBySalary", query = "SELECT e FROM Employee e WHERE e.salary = :salary")})
public class Employee implements Serializable {
 
    private static final long serialVersionUID = 1L;
    @Id
    @TableGenerator(name="EmployeePk", table="pkeys", pkColumnName="pkey_id",
            valueColumnName="pkey_val", pkColumnValue="EMPLOYEE", allocationSize=1)
    @GeneratedValue(strategy=GenerationType.TABLE, generator="EmployeePk")    
    @Basic(optional = false)
    @NotNull
    @Column(name = "employee_id")
    private Integer employeeId;
    . . .
EJB Session EmployeeFacade
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
 
@Stateless
@LocalBean
@TransactionManagement(TransactionManagementType.CONTAINER)
public class EmployeeFacade {
    @PersistenceContext(unitName = "EmployeesJPA-ejbPU")
    private EntityManager em;
    @EJB
    JobFacade jobFacade;
    @EJB
    DepartmentFacade departmentFacade;
 
    @TransactionAttribute(TransactionAttributeType.REQUIRED)
    public void addEmployee(String name, Date hiredate, double salary, int jobId, int departmentId, int managerId) throws Exception {
        Employee employee = new Employee();
        try{
            Department department = departmentFacade.getDepartmentById(departmentId);
            Job job = jobFacade.getJobById(jobId);
            employee.setDepartment(department);
            employee.setJob(job);
            employee.setHiredate(hiredate);
            employee.setManagerId(managerId);
            employee.setName(name);
            em.persist(employee);
        }catch(Exception ex){
            throw ex;
        }
    }
}
Servlet SrvlEmployee
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        String error = null;
        try {
            Date hiredate = Utilitaire.StrToDate("2020-02-01", "yyyy-MM-dd");
            BigDecimal salary = new BigDecimal(2000.00);
            employeeFacade.addEmployee("Smith", hiredate, salary, 2, 3, 4);
        } catch (Exception e) {
            error = Utilitaire.getExceptionCause(e);
        } finally {
            request.setAttribute("errorR", error);
            RequestDispatcher dsp = request.getRequestDispatcher("/index.jsp");
            dsp.forward(request, response);
        }
    }
Table pkeys avant insertion :
Nom : pkeyBefore.PNG
Affichages : 263
Taille : 7,2 Ko
Table pkeys après insertion :
Nom : pkeyAfter.PNG
Affichages : 219
Taille : 6,1 Ko
L'id de la dernière occurrence de employee est 14.
La trace des requêtes :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
 
Hibernate: select department0_.department_id as departme1_0_0_, department0_.dname as dname2_0_0_, department0_.location as location3_0_0_ from department department0_ where department0_.department_id=?
Hibernate: select job0_.job_id as job_id1_2_0_, job0_.jobname as jobname2_2_0_ from job job0_ where job0_.job_id=?
Hibernate: select pkey_val from pkeys where pkey_id = 'EMPLOYEE' for update
Hibernate: update pkeys set pkey_val = ? where pkey_val = ? and pkey_id = 'EMPLOYEE'
Hibernate: insert into employee (department_id, hiredate, job_id, manager_id, name, salary, employee_id) values (?, ?, ?, ?, ?, ?, ?)
Le message d'erreur :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
 
SQL Error: 1062, SQLState: 23000
Duplicate entry '14' for key 'PRIMARY'
Précisions :
- le même code sous EclipseLink fonctionne parfaitement !
- j'ai essayé le REQUIRES_NEW à la place de REQUIRED, cela n'a rien changé.
Voilà, si quelqu'un a une idée, je serais bien preneur !
Merci d'avance pour votre aide.