2015-12-18 1 views
1

Je veux faire un traitement multi-threadable avec JTA.JTA Transaction et thread

Environnement:

  • AS 6.3 JBOSS
  • Java 7
  • Oracle
  • 11,2 g
  • CDI

Traitement: Je veux produire un zip avec toutes les données de base de données. Ce fichier zip peut être grand, donc je veux démarrer un thread pour produire le flux en même temps que jboss l'envoie au client.

Mon entrée REST:

@Stateless 
@Path("/exportProcess") 
public class ExportProcessusResource { 

    @Inject 
    private IExport export; 

    @GET 
    @Path("/{processCode: [^/]+}") 
    @Produces(MediaType.APPLICATION_OCTET_STREAM) 
    public Response export(@PathParam("processCode") final String pProcessCode) { 
     return Response.ok(export.export(pProcessCode)) 
           .header("Content-Disposition", "attachment; filename=" + pCodeProcessus + ".zip") 
           .build(); 
    } 
} 

Mon modèle:

@Entity 
@Table(name = "T_PROCESS") 
@NamedQueries({ 
    @NamedQuery(name = "Process.GetByCode", query = "SELECT p FROM Process p WHERE p.code=:code") 
}) 
public class Process { 

    @Column(name = "CODE", length = 50, nullable = false) 
    private String code; 

    @OneToMany(mappedBy = "process", targetEntity = Step.class) 
    private Collection<Step> steps; 

    //Getters/Setters 
} 

@Entity 
@Table(name = "T_STEP") 
public class STEP { 
    @Id 
    @Column(name = "ID_STEP") 
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "SEQ_ID_STEP") 
    @SequenceGenerator(name = "SEQ_ID_STEP", sequenceName = "SEQ_ID_STEP") 
    private int id; 

    @ManyToOne(targetEntity = Process.class) 
    @JoinColumn(name = "CODE_PROCESS", referencedColumnName = "CODE", nullable = false) 
    private Process process; 

    //Getters/Setters 
} 

Mon OAC:

public interface IProcessDao { 

    Processus getByCode(final String pCode); 
} 

public class ProcessDao implements IProcessDao { 

    @Override 
    public Processus getByCode(final String pCode) { 
     Processus lResult = null; 
     try { 
      final TypedQuery<Processus> lRequest = pEm.createNamedQuery("Process.GetByCode", Process.class); 
      lRequest.setParameter("code", pCode); 
      lResult = lRequest.getSingleResult(); 
     } catch (final NoResultException e) { 
      // Return null 
      lResult = null; 
     } 
     return lResult; 
    } 
} 

Mon contrôleur:

public interface IExport { 

    /** 
    * Generate export 
    * 
    * @param pProcessCode Process code 
    * @return Datas 
    */ 
    InputStream export(final String pProcessCode); 
} 


public class Export implements IExport { 

    @PersistenceContext(unitName="authorizations") 
    private EntityManager entityManagerAuthorizations; 

    @Inject 
    private ExportThreadHelper exportThreadHelper; 

    @Override 
    public InputStream export(final String pProcessCode) { 
     //Check if user has the profile. Use database "AUTHORIZATIONS" 
     checkProfil(entityManagerAuthorizations, Profiles.ADMIN); 

     final PipedInputStream lInputStream = new PipedInputStream(); 
     OutputStream lOutputStream = null; 
     try { 
      lOutputStream = new FileOutputStream("d:/test.zip");// new 
                   // PipedOutputStream(lInputStream); 
     } catch (final IOException e) { 
      throw new RuntimeException("Cannot start zip generation", e); 
     } 

     final ZipOutputStream lZipOutputStream = new ZipOutputStream(lOutputStream); 

     final Runnable lRunnable = new Runnable() { 
      @Override 
      public void run() { 
       try { 
        exportThreadHelper.export(pProcessCode, lZipOutputStream); 
       } catch (final Exception e) { 
        logger.error(e); 
       } finally { 
        IOUtils.closeQuietly(lZipOutputStream); 
       } 
      } 
     }; 
     //To execute in same thread : 
     //lRunnable.run(); 

     //To execute in another thread 
     final Thread lThread = new Thread(lRunnable); 
     lThread.start(); 
     try { 
      lThread.join(); 
     } catch (final InterruptedException e1) { 
      throw new RuntimeException(e1); 
     } 


     try { 
      return new FileInputStream("d:/test.zip"); 
     } catch (final FileNotFoundException e) { 
      logger.error(e); 
     } 
     return lInputStream; 
    } 
} 


public class ExportThreadHelper { 

    private class ProcessToExport { 
     //... 
    } 

    @PersistenceContext 
    @Named("Application") 
    private EntityManager entityManagerThreadable; 

    @Inject 
    private IProcessDao processDao; 

    public void export(final String pProcesssCode, final ZipOutputStream pZipOutputStream) 
        throws MyWayBusinessException { 
     try { 

      final ProcessToExport lProcessToExport = new ProcessToExport(); 

      transaction(entityManagerThreadable, new Callable<Void>() { 
       @Override 
       public Void execute() { 
        final Process lProcess = processDao.getByCode(pProcesssCode); 
        for (final Step lStep : lProcess.getSteps()) { 
         //Many things 
        } 
        return null; 
       } 
      }); 

      //MANY OTHER TREATMENTS 

     } catch (final Exception e) { 
      logger.error(e); 
      throw new RuntimeException("Cannot generate export", e); 
     } 
    } 

    @Override 
    @TransactionAttribute(TransactionAttributeType.REQUIRED) 
    protected <T> T transaction(final EntityManager pEntityManager, final Callable<T> pCallable) { 
     //I've tried with and without the annotation and with and without the "UserTransaction" 
     try { 
      final UserTransaction tx = com.arjuna.ats.jta.UserTransaction.userTransaction(); 
      try { 
       tx.begin(); 
       final T lResultat = pCallable.execute(); 
       tx.commit(); 
       return lResultat; 
      } catch (final Throwable e) { 
       tx.rollback(); 
       throw e; 
      } 
     } catch (final Throwable e) { 
      throw new RuntimeException(e); 
     } 
    } 
} 

Mes 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="APPLICATION" transaction-type="JTA"> 
     <jta-data-source>java:/jdbc/app</jta-data-source> 
     <class>Processus</class> 
     <class>Step</class> 
     <properties> 
      <!-- Scan for annotated classes and Hibernate mapping XML files --> 
      <property name="hibernate.archive.autodetection" value="class, hbm" /> 
     </properties> 
    </persistence-unit> 
    <persistence-unit name="AUTHORIZATION" transaction-type="JTA"> 
     <jta-data-source>java:/jdbc/AUTHORIZATION</jta-data-source> 
     <!-- many things... --> 
    </persistence-unit> 
</persistence> 

(j'ai nettoyé le code pour ne garder que des choses importantes). Et, si j'utilise la version monothread (lRunnable.run()) j'ai le fichier zip, mais si je lance la version multithread (thread.start()) (que j'ai bloqué ici pour assurer mes tests que la connexion n'est pas à proximité thread parent, mais après je vais retirer Thread.join()) J'ai cette exception:

erreur [... ExportThreadHelper] (discussion-115) a échoué à paresseusement initialize une collection de rôle: .steps, n'a pas pu initialiser proxy - no Session: org.hibernate.LazyInitializationException: impossible d'initialiser paresseusement une collection de rôle: .steps, n'a pas pu initialiser proxy - no Session at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException (AbstractPersistentCollection.java:569) [hibernate-core-4.2.14.SP1-redhat-1.jar: 4.2.14.SP1-redhat-1] à org .hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded (AbstractPersistentCollection.java:188) [hibernate-core-4.2.14.SP1-redhat-1.jar: 4.2.14.SP1-redhat-1] à org.hibernate .collection.internal.AbstractPersistentCollection.initialize (AbstractPersistentCollection.java:548) [hibernate-core-4.2.14.SP1-redhat-1.jar: 4.2.14.SP1-redhat-1] à org.hibernate.collection .internal.AbstractPersistentCollection.read (AbstractPersistentCollection.java:126) [hibernate-core-4.2.14.SP1-redhat-1.jar: 4.2.14.SP1-redhat-1] à org.hibernate.collection.internal.PersistentBag.iterator (PersistentBag.java:266) [hibernate-core-4.2.14.SP1-redhat-1.jar: 4.2.14.SP1-redhat-1] à ExportThreadHelper $ 1.execute (ExportThreadHelper.java:101) [metier-2.3.0-SNAPSHOT.jar:] à ExportThreadHelper $ 1.execute (ExportThreadHelper.java:1) [metier-2.3.0-SNAPSHOT.pot:] à ExportThreadHelper.transaction (ExportThreadHelper.java:148) [metier-2.3.0-SNAPSHOT.jar:] à ExportThreadHelper.export (ExportThreadHelper.java:97) [metier-2.3.0-SNAPSHOT. jar:] at ExportMetier $ 1.run (ExportMetier.java:62) [metier-2.3.0-SNAPSHOT.jar:] at java.lang.Thread.run (Thread.java:722) [rt.jar: 1.7 .0_04]

Avez-vous vu un problème dans mon code?

+0

fichier ayant-vous nommé 'ExportThreadHelper.java' dans le projet, vous pouvez publier ce fichier. Lignes spécialement près de 90-150? – Amogh

+0

En regardant d'abord à l'exception, il semble que vous essayez d'accéder 'Collection ' depuis l'objet 'Process' hors de la session d'hibernation. Soit faire la session de mise en veille prolongée fermer ou de définir 'fetch =' FetchType.EAGER' à la collection privée étapes, ' – Amogh

+0

Merci de votre attention - ExportThreadHelper est dans la partie « contrôleur » - Je veux garder la chargement paresseux car il y a trop de données et je ne l'utilise pas dans tous les cas. – Chklang

Répondre

0

Vous devez modifier la requête comme ceci:

@NamedQueries({ 
    @NamedQuery(name = "Process.GetByCode", query = "SELECT p FROM Process p LEFT JOIN FETCH p.steps WHERE p.code=:code") 
}) 
+0

Je peux aussi ajouter "fetch = EAGER" sur le terrain, mais je ne le veux pas car sinon nous obtenons tout le temps toutes les données sur chaque cas. Je veux garder le chargement paresseux et utiliser correctement les transactions. – Chklang

+0

Pourquoi ne pas avoir deux requêtes à la place? –

+0

Je ne veux pas être dépendant du contexte. C'est une grande application, qui fonctionne sur REST. Et cette nouvelle ressource REST doit créer une "exportation" dans le fichier zip des réponses. Donc si vous appelez "/ rest/myResource" vous obtenez un JSON, et si vous appelez "/ rest/export" vous aurez un "fichier zip" avec "/rest/myResource.json" qui contient la réponse de la première requête . C'est un exemple simple mais le problème est ici: je veux appeler toutes les autres méthodes commerciales dans un thread pour générer le fichier zip. – Chklang