Spring @Transactional - isolation, propagation

447

Quelqu'un peut-il expliquer à quoi servent les paramètres d' isolation et de propagation dans l' @Transactionalannotation via un exemple réel?

Fondamentalement, quand et pourquoi je devrais choisir de changer leurs valeurs par défaut.

Mat B.
la source

Réponses:

442

Bonne question, mais pas insignifiante.

Propagation

Définit la relation entre les transactions. Options courantes:

  • Required: Le code s'exécutera toujours dans une transaction. Crée une nouvelle transaction ou en réutilise une si disponible.
  • Requires_new: Le code s'exécutera toujours dans une nouvelle transaction. Suspend la transaction en cours si elle existe.

Isolement

Définit le contrat de données entre les transactions.

  • Read Uncommitted: Permet des lectures sales.
  • Read Committed: Ne permet pas les lectures sales.
  • Repeatable Read: Si une ligne est lue deux fois dans la même transaction, le résultat sera toujours le même.
  • Serializable: Effectue toutes les transactions dans une séquence.

Les différents niveaux ont des caractéristiques de performances différentes dans une application multithread. Je pense que si vous comprenez le dirty readsconcept, vous pourrez sélectionner une bonne option.


Exemple de cas où une lecture incorrecte peut se produire:

  thread 1   thread 2      
      |         |
    write(x)    |
      |         |
      |        read(x)
      |         |
    rollback    |
      v         v 
           value (x) is now dirty (incorrect)

Ainsi, une valeur par défaut saine (si cela peut être revendiqué) pourrait être Read Committed, qui vous permet uniquement de lire des valeurs qui ont déjà été validées par d'autres transactions en cours, en combinaison avec un niveau de propagation de Required. Ensuite, vous pouvez travailler à partir de là si votre application a d'autres besoins.


Un exemple pratique où une nouvelle transaction sera toujours créée lors de l'entrée dans la provideServiceroutine et terminée lors de la sortie:

public class FooService {
    private Repository repo1;
    private Repository repo2;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void provideService() {
        repo1.retrieveFoo();
        repo2.retrieveFoo();
    }
}

Si nous avions utilisé à la place Required, la transaction resterait ouverte si la transaction était déjà ouverte lors de l'entrée dans la routine. Notez également que le résultat d'un rollbackpourrait être différent car plusieurs exécutions pourraient participer à la même transaction.


Nous pouvons facilement vérifier le comportement avec un test et voir comment les résultats diffèrent avec les niveaux de propagation:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:/fooService.xml")
public class FooServiceTests {

    private @Autowired TransactionManager transactionManager;
    private @Autowired FooService fooService;

    @Test
    public void testProvideService() {
        TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
        fooService.provideService();
        transactionManager.rollback(status);
        // assert repository values are unchanged ... 
}

Avec un niveau de propagation de

  • Requires new: nous nous attendions à ce qu'il fooService.provideService()n'ait PAS été annulé car il a créé sa propre sous-transaction.

  • Required: nous nous attendrions à ce que tout soit annulé et que le magasin de sauvegarde soit inchangé.

Johan Sjöberg
la source
Quel est le lien entre ce dernier lien et ce dont vous parlez? Selon les documents liés, c'est la session qui semble indiquer ce qu'est la transaction actuelle, pas la fabrique de sessions.
Donal Fellows
@Donal, oh désolé, ce n'était pas clair. Mon point était, depuis l' sessionFactory.getCurrentTransaction()ajout, il n'est plus nécessaire d'exécuter HibernateTemplatepour gérer les transactions. Je l'ai supprimé :)
Johan Sjöberg
Ma question portait sur la direction réelle du lien. :-)
Donal Fellows
comment obtenir les modifications apportées dans la transaction actuelle
Prasanna Kumar HA
304

PROPAGATION_REQUIRED = 0 ; Si DataSourceTransactionObject T1 est déjà démarré pour la méthode M1.Si un autre objet Transaction M2 est requis, aucun nouvel objet Transaction n'est créé. Le même objet T1 est utilisé pour M2

PROPAGATION_MANDATORY = 2 ; doit s'exécuter dans une transaction. Si aucune transaction existante n'est en cours, une exception sera levée

PROPAGATION_REQUIRES_NEW = 3 ; Si DataSourceTransactionObject T1 est déjà démarré pour la méthode M1 et qu'il est en cours (exécution de la méthode M1) .Si une autre méthode M2 ​​commence à s'exécuter, T1 est suspendu pendant la durée de la méthode M2 ​​avec un nouveau DataSourceTransactionObject T2 pour M2.M2 exécuté dans son propre contexte de transaction

PROPAGATION_NOT_SUPPORTED = 4 ; Si DataSourceTransactionObject T1 est déjà démarré pour la méthode M1.Si une autre méthode M2 ​​est exécutée simultanément, alors M2 ne doit pas s'exécuter dans le contexte de la transaction. T1 est suspendu jusqu'à ce que M2 soit terminé.

PROPAGATION_NEVER = 5 ; Aucune des méthodes ne s'exécute dans le contexte de transaction.

Un niveau d'isolement: il s'agit de savoir dans quelle mesure une transaction peut être affectée par les activités d'autres transactions simultanées. Il prend en charge la cohérence en laissant les données sur plusieurs tables dans un état cohérent. Il s'agit de verrouiller des lignes et / ou des tables dans une base de données.

Le problème des transactions multiples

Scénario 1 .Si la transaction T1 lit les données de la table A1 qui ont été écrites par une autre transaction simultanée T2.Si T2 est annulé, les données obtenues par T1 ne sont pas valides.Eg a = 2 sont les données d'origine .Si T1 lit a = 1 qui a été écrit par T2.Si T2 rollback alors a = 1 sera rollback à a = 2 dans DB.Mais, maintenant, T1 a a = 1 mais dans la table DB il est changé en a = 2.

Scénario2 .Si la transaction T1 lit les données de la table A1.Si une autre transaction simultanée (T2) met à jour les données de la table A1.Ensuite, les données que T1 a lues sont différentes de la table A1.Parce que T2 a mis à jour les données de la table A1.Eg si T1 lire a = 1 et T2 mis à jour a = 2. Puis a! = b.

Scénario 3 .Si la transaction T1 lit les données de la table A1 avec un certain nombre de lignes. Si une autre transaction simultanée (T2) insère plus de lignes dans la table A1.Le nombre de lignes lues par T1 est différent des lignes de la table A1

Le scénario 1 est appelé lectures sales.

Le scénario 2 est appelé lectures non répétables.

Le scénario 3 est appelé lectures fantômes.

Ainsi, le niveau d'isolement est l'étendue à laquelle le scénario 1, le scénario 2, le scénario 3 peuvent être empêchés. Vous pouvez obtenir un niveau d'isolement complet en implémentant le verrouillage, ce qui empêche les lectures et les écritures simultanées de se produire, mais cela affecte les performances. Le niveau d'isolement dépend de l'application à l'application de l'isolement requis.

ISOLATION_READ_UNCOMMITTED : Permet de lire les modifications qui n'ont pas encore été validées. Il souffre du scénario 1, du scénario 2, du scénario 3

ISOLATION_READ_COMMITTED : autorise les lectures à partir de transactions simultanées qui ont été validées. Il peut souffrir des scénarios 2 et 3. Parce que d'autres transactions peuvent mettre à jour les données.

ISOLATION_REPEATABLE_READ : plusieurs lectures du même champ produiront les mêmes résultats jusqu'à ce qu'il soit modifié par lui-même.Il peut souffrir du scénario 3.Parce que d'autres transactions peuvent insérer les données

ISOLATION_SERIALIZABLE : le scénario 1, le scénario 2, le scénario 3 ne se produisent jamais. Il s'agit d'une isolation complète. Il implique un verrouillage complet. Il affecte les performances en raison du verrouillage.

Vous pouvez tester en utilisant

public class TransactionBehaviour {
   // set is either using xml Or annotation
    DataSourceTransactionManager manager=new DataSourceTransactionManager();
    SimpleTransactionStatus status=new SimpleTransactionStatus();
   ;


    public void beginTransaction()
    {
        DefaultTransactionDefinition Def = new DefaultTransactionDefinition();
        // overwrite default PROPAGATION_REQUIRED and ISOLATION_DEFAULT
        // set is either using xml Or annotation
        manager.setPropagationBehavior(XX);
        manager.setIsolationLevelName(XX);

        status = manager.getTransaction(Def);

    }

    public void commitTransaction()
    {


            if(status.isCompleted()){
                manager.commit(status);
        } 
    }

    public void rollbackTransaction()
    {

            if(!status.isCompleted()){
                manager.rollback(status);
        }
    }
    Main method{
        beginTransaction()
        M1();
        If error(){
            rollbackTransaction()
        }
         commitTransaction();
    }

}

Vous pouvez déboguer et voir le résultat avec différentes valeurs d'isolement et de propagation.

abishkar bhattarai
la source
comment obtenir les modifications apportées dans la transaction actuelle
Prasanna Kumar HA
2
Quelle est l'interaction entre le niveau d'isolement et la propagation ? Si la méthode 1 démarre une transaction avec un niveau d'isolement, disons READ_COMMITTED, et appelle plus tard la méthode 2 avec le niveau REPEATABLE_READ, la méthode 2 doit sûrement être exécutée dans sa propre transaction, quel que soit le comportement de propagation qu'elle spécifie (par exemple, seulement REQUIS).
Cornel Masson
C'est vraiment tard dans l'émission, mais quand PROPAGATION_REQUIRES_NEW, qu'arrive-t-il à T1 (qui est utilisé par M1) si un autre nouvel appel arrivait à M1? (disons M1.1)
Tim Z.
115

Une explication suffisante de chaque paramètre est donnée par d'autres réponses; Cependant, vous avez demandé un exemple concret, voici celui qui clarifie le but des différentes options de propagation :

Supposons que vous soyez responsable de la mise en œuvre d'un service d'inscription dans lequel un e-mail de confirmation est envoyé à l'utilisateur. Vous obtenez deux objets de service, l'un pour l' inscription de l'utilisateur et l'autre pour l' envoi d' e-mails, ce dernier étant appelé dans le premier. Par exemple quelque chose comme ça:

/* Sign Up service */
@Service
@Transactional(Propagation=REQUIRED)
class SignUpService{
 ...
 void SignUp(User user){
    ...
    emailService.sendMail(User);
 }
}

/* E-Mail Service */
@Service
@Transactional(Propagation=REQUIRES_NEW)
class EmailService{
 ...
 void sendMail(User user){
  try{
     ... // Trying to send the e-mail
  }catch( Exception)
 }
}

Vous avez peut-être remarqué que le deuxième service est de type propagation OBLIGATOIRE_NEW et en plus il y a de fortes chances qu'il lève une exception (serveur SMTP arrêté, e-mail invalide ou autres raisons) .Vous ne voulez probablement pas que l'ensemble du processus soit annulé, comme supprimer les informations utilisateur de la base de données ou d'autres choses; par conséquent, vous appelez le deuxième service dans une transaction distincte.

Revenons à notre exemple, cette fois, vous êtes préoccupé par la sécurité de la base de données, vous définissez donc vos classes DAO de cette façon:

/* User DAO */
@Transactional(Propagation=MANDATORY)
class UserDAO{
 // some CRUD methods
}

Cela signifie que chaque fois qu'un objet DAO, et donc un accès potentiel à db, est créé, nous devons nous assurer que l'appel a été effectué à l'intérieur de l'un de nos services, ce qui implique qu'une transaction en direct doit exister; sinon, une exception se produit. Par conséquent, la propagation est de type OBLIGATOIRE .

ye9ane
la source
26
Exemple parfait pour REQUIRES_NEW.
Ravi Thapliyal
5
Bonne explication! Au fait, quelle est la valeur par défaut pour la propagation? Ce serait encore mieux si vous pouviez également donner un exemple comme celui-ci pour l'isolement. Merci beaucoup.
Prakash K
5
@PrakashK La valeur par défaut est REQUISE. ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… )
ihebiheb
59

Le niveau d'isolement définit la manière dont les modifications apportées à certains référentiels de données par une transaction affectent d'autres transactions simultanées simultanées, ainsi que la manière et le moment où ces données modifiées deviennent disponibles pour d'autres transactions. Lorsque nous définissons une transaction à l'aide du framework Spring, nous pouvons également configurer à quel niveau d'isolement cette même transaction sera exécutée.

@Transactional(isolation=Isolation.READ_COMMITTED)
public void someTransactionalMethod(Object obj) {

}

Le niveau d'isolement READ_UNCOMMITTED indique qu'une transaction peut lire des données qui ne sont pas encore validées par d'autres transactions.

Le niveau d'isolement READ_COMMITTED indique qu'une transaction ne peut pas lire des données qui ne sont pas encore validées par d'autres transactions.

Le niveau d'isolement REPEATABLE_READ indique que si une transaction lit plusieurs fois un enregistrement de la base de données, le résultat de toutes ces opérations de lecture doit toujours être le même.

Le niveau d'isolement SERIALISABLE est le plus restrictif de tous les niveaux d'isolement. Les transactions sont exécutées avec verrouillage à tous les niveaux (verrouillage en lecture, en plage et en écriture) de sorte qu'elles apparaissent comme si elles avaient été exécutées de manière sérialisée.

La propagation est la capacité de décider comment les méthodes commerciales doivent être encapsulées dans les transactions logiques ou physiques.

Le comportement Spring REQUIRED signifie que la même transaction sera utilisée s'il existe une transaction déjà ouverte dans le contexte d'exécution actuel de la méthode du bean.

Le comportement REQUIRES_NEW signifie qu'une nouvelle transaction physique sera toujours créée par le conteneur.

Le comportement NESTED oblige les transactions Spring imbriquées à utiliser la même transaction physique mais définit des points de sauvegarde entre les invocations imbriquées afin que les transactions internes puissent également être annulées indépendamment des transactions externes.

Le comportement OBLIGATOIRE indique qu'une transaction ouverte existante doit déjà exister. Sinon, une exception sera levée par le conteneur.

Le comportement NEVER indique qu'une transaction ouverte existante ne doit pas déjà exister. Si une transaction existe, une exception sera levée par le conteneur.

Le comportement NOT_SUPPORTED s'exécutera en dehors de la portée de toute transaction. Si une transaction ouverte existe déjà, elle sera suspendue.

Le comportement SUPPORTS s'exécutera dans le cadre d'une transaction si une transaction ouverte existe déjà. S'il n'y a pas de transaction déjà ouverte, la méthode s'exécutera de toute façon mais de manière non transactionnelle.

reos
la source
4
Si vous pouviez ajouter quand utiliser lequel, ce serait beaucoup plus avantageux.
Kumar Manish
Donnez quelques exemples, ce serait très utile pour les débutants
nitinsridar
23

Une transaction représente une unité de travail avec une base de données.

Dans l' TransactionDefinitioninterface Spring qui définit les propriétés de transaction conformes à Spring. @Transactionalannotation décrit les attributs de transaction sur une méthode ou une classe.

@Autowired
private TestDAO testDAO;

@Transactional(propagation=TransactionDefinition.PROPAGATION_REQUIRED,isolation=TransactionDefinition.ISOLATION_READ_UNCOMMITTED)
public void someTransactionalMethod(User user) {

  // Interact with testDAO

}

Propagation (Reproduction): est utilisée pour la relation inter transaction. (analogue à la communication java inter thread)

+-------+---------------------------+------------------------------------------------------------------------------------------------------+
| value |        Propagation        |                                             Description                                              |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+
|    -1 | TIMEOUT_DEFAULT           | Use the default timeout of the underlying transaction system, or none if timeouts are not supported. |
|     0 | PROPAGATION_REQUIRED      | Support a current transaction; create a new one if none exists.                                      |
|     1 | PROPAGATION_SUPPORTS      | Support a current transaction; execute non-transactionally if none exists.                           |
|     2 | PROPAGATION_MANDATORY     | Support a current transaction; throw an exception if no current transaction exists.                  |
|     3 | PROPAGATION_REQUIRES_NEW  | Create a new transaction, suspending the current transaction if one exists.                          |
|     4 | PROPAGATION_NOT_SUPPORTED | Do not support a current transaction; rather always execute non-transactionally.                     |
|     5 | PROPAGATION_NEVER         | Do not support a current transaction; throw an exception if a current transaction exists.            |
|     6 | PROPAGATION_NESTED        | Execute within a nested transaction if a current transaction exists.                                 |
+-------+---------------------------+------------------------------------------------------------------------------------------------------+

Isolation: l' isolement est l'une des propriétés ACID (atomicité, cohérence, isolation, durabilité) des transactions de base de données. L'isolement détermine la façon dont l'intégrité des transactions est visible pour les autres utilisateurs et systèmes. Il utilise pour le verrouillage des ressources, c'est-à-dire le contrôle de la concurrence, assurez-vous qu'une seule transaction peut accéder à la ressource à un moment donné.

Perception du verrouillage: le niveau d'isolement détermine la durée de maintien des verrous.

+---------------------------+-------------------+-------------+-------------+------------------------+
| Isolation Level Mode      |  Read             |   Insert    |   Update    |       Lock Scope       |
+---------------------------+-------------------+-------------+-------------+------------------------+
| READ_UNCOMMITTED          |  uncommitted data | Allowed     | Allowed     | No Lock                |
| READ_COMMITTED (Default)  |   committed data  | Allowed     | Allowed     | Lock on Committed data |
| REPEATABLE_READ           |   committed data  | Allowed     | Not Allowed | Lock on block of table |
| SERIALIZABLE              |   committed data  | Not Allowed | Not Allowed | Lock on full table     |
+---------------------------+-------------------+-------------+-------------+------------------------+

Lire la perception: les 3 types de problèmes majeurs suivants se produisent:

  • Lectures sales : lit les données non validées d'un autre tx (transaction).
  • Lectures non reproductibles : lectures validées à UPDATESpartir d'un autre tx.
  • Lectures fantômes : lectures validées INSERTSet / ou DELETESdepuis un autre tx

Niveaux d'isolement avec différents types de lectures:

+---------------------------+----------------+----------------------+----------------+
| Isolation Level Mode      |  Dirty reads   | Non-repeatable reads | Phantoms reads |
+---------------------------+----------------+----------------------+----------------+
| READ_UNCOMMITTED          | allows         | allows               | allows         |
| READ_COMMITTED (Default)  | prevents       | allows               | allows         |
| REPEATABLE_READ           | prevents       | prevents             | allows         |
| SERIALIZABLE              | prevents       | prevents             | prevents       |
+---------------------------+----------------+----------------------+----------------+

pour des exemples

Premraj
la source
20

Vous ne voulez presque jamais l'utiliser Read Uncommitedcar il n'est pas vraiment ACIDconforme. Read Commmitedest un bon point de départ par défaut. Repeatable Readn'est probablement nécessaire que dans les scénarios de génération de rapports, de cumul ou d'agrégation. Notez que de nombreuses bases de données, postgres incluses, ne prennent pas réellement en charge la lecture répétable, vous devez utiliser à la Serializableplace. Serializableest utile pour les choses dont vous savez qu'elles doivent se produire complètement indépendamment de toute autre chose; pensez-y comme synchronizedà Java. La sérialisation va de pair avec la REQUIRES_NEWpropagation.

J'utilise REQUIRESpour toutes les fonctions qui exécutent des requêtes UPDATE ou DELETE ainsi que des fonctions de niveau "service". Pour les fonctions de niveau DAO qui exécutent uniquement des SELECT, j'utilise SUPPORTSqui participera à un TX si une est déjà démarrée (c'est-à-dire être appelée à partir d'une fonction de service).

AngerClown
la source
13

L'isolement des transactions et la propagation des transactions, bien que liées, sont clairement deux concepts très différents. Dans les deux cas, les valeurs par défaut sont personnalisées au niveau du composant de limite client en utilisant la gestion des transactions déclarative ou la gestion des transactions programmatique . Les détails de chaque niveau d'isolement et attributs de propagation peuvent être trouvés dans les liens de référence ci-dessous.

Isolement de transaction

Pour deux transactions ou connexions en cours d'exécution ou plus vers une base de données, comment et quand les modifications apportées par les requêtes d'une transaction ont-elles un impact / sont-elles visibles pour les requêtes d'une transaction différente? Elle concernait également le type de verrouillage d'enregistrement de base de données qui sera utilisé pour isoler les modifications de cette transaction des autres transactions et vice versa. Ceci est généralement implémenté par la base de données / ressource qui participe à la transaction.

.

Propagation des transactions

Dans une application d'entreprise pour une demande / un traitement donné, de nombreux composants sont impliqués pour faire le travail. Certains de ces composants marquent les limites (début / fin) d'une transaction qui sera utilisée dans le composant respectif et ses sous-composants. Pour cette limite transactionnelle de composants, la proposition de transaction spécifie si le composant respectif participera ou non à la transaction et ce qui se passe si le composant appelant a déjà ou non une transaction déjà créée / démarrée. C'est la même chose que les attributs de transaction Java EE. Ceci est généralement implémenté par le gestionnaire de transactions / connexions client.

Référence:

Gladwin Burboz
la source
1
Super, toutes les informations en un seul endroit, les liens sont très utiles, merci @Gladwin Burboz
nitinsridar
7

J'ai couru outerMethod, method_1et method_2avec un mode de propagation différent.

Voici la sortie pour différents modes de propagation.

  • Méthode externe

    @Transactional
    @Override
    public void outerMethod() {
        customerProfileDAO.method_1();
        iWorkflowDetailDao.method_2();
    }
  • Méthode_1

    @Transactional(propagation=Propagation.MANDATORY)
    public void method_1() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "XXX");
            session.save(entity);
            System.out.println("Method - 1 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
  • Méthode_2

    @Transactional()
    @Override
    public void method_2() {
        Session session = null;
        try {
            session = getSession();
            Temp entity = new Temp(0l, "CCC");
            session.save(entity);
            int i = 1/0;
            System.out.println("Method - 2 Id "+entity.getId());
        } finally {
            if (session != null && session.isOpen()) {
            }
        }
    }
      • externalMethod - Sans transaction
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Annotation de transaction uniquement
      • Sortie: method_1 lèvera une exception qu'aucune transaction existante
      • externalMethod - Sans transaction
      • method_1 - Annotation de transaction uniquement
      • method_2 - Propagation.MANDATORY)
      • Sortie: method_2 lèvera une exception qu'aucune transaction existante
      • Sortie: method_1 conservera l'enregistrement dans la base de données.
      • externalMethod - Avec transaction
      • method_1 - Annotation de transaction uniquement
      • method_2 - Propagation.MANDATORY)
      • Sortie: method_2 conservera l'enregistrement dans la base de données.
      • Sortie: method_1 conservera l'enregistrement dans la base de données. - Ici, la transaction existante principale externe utilisée pour les méthodes 1 et 2
      • externalMethod - Avec transaction
      • method_1 - Propagation.MANDATORY) -
      • method_2 - Annotation de transaction uniquement et levée d'exception
      • Sortie: aucun enregistrement ne persiste dans la base de données signifie que la restauration est terminée.
      • externalMethod - Avec transaction
      • method_1 - Propagation.REQUIRES_NEW)
      • method_2 - Propagation.REQUIRES_NEW) et lève l'exception 1/0
      • Sortie: method_2 lève une exception afin que l'enregistrement method_2 ne soit pas conservé.
      • Sortie: method_1 conservera l'enregistrement dans la base de données.
      • Sortie: il n'y a pas de restauration pour la méthode_1
NIrav Modi
la source
3

On peut ajouter pour cela:

@Transactional(readOnly = true)
public class Banking_CustomerService implements CustomerService {

    public Customer getDetail(String customername) {
        // do something
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateCustomer(Customer customer) {
        // do something
    }
}
Ankit
la source
1

Vous pouvez utiliser comme ceci:

@Transactional(propagation = Propagation.REQUIRES_NEW)
public EventMessage<ModificaOperativitaRapporto> activate(EventMessage<ModificaOperativitaRapporto> eventMessage) {
//here some transaction related code
}

Vous pouvez également utiliser cette chose:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}
Ankit
la source