Specific use of Try-catch

Asked

Viewed 1,204 times

14

I am studying Hibernate and with it came some doubts that have come to me before but I did not question in depth.

public void salvar(Usuario usuario){
    Session sessao = null;
    Transaction transacao = null;

    try{
        sessao = HibernateUtil.getSessionFactory().openSession();
        transacao = sessao.beginTransaction();
        sessao.save(usuario);
        transacao.commit();
    }catch(HibernateException e){
        System.out.println("Não foi possível inserir o usuário. Erro: "+e.getMessage());
    }finally{
        try{
            sessao.close();
        }catch(Throwable e){
            System.out.println("Não foi possível fechar operação de atualização. Messagem: "+e.getMessage());
        }
    }

}

The way it looks to me it seems kind of "dirty" I mean, it’s overcrowded try-catch for all places, a clean code is really like this?

Another question is, what is the use of try with resource? I should use whenever possible the try w/ Resources?

4 answers

9


A peculiar situation

This example you gave is interesting because a HibernateException, that is not a checked Exception (that is, exception that must be treated or passed on to the level above) can be triggered by a SQLException of JDBC, which is checked, which in turn can be fired by a IOException, which is also checked, and it can happen when the connection with the bank is lost (after it has been established, of course). But the fact of HibernateException not to be checked clear the treatment of the exception, which may or may not be recommended in this situation, to be checked below (and you will see that it needs to be treated).

When connection drops any of the methods beginTransaction(), save() or commit() will launch a HibernateException, indicating that the connection has been lost and the resource (socket opened with the bank) needs to be released. Or, in other words, signalling that sessao.close() needs to be called. But sessao.close(), that beneath the rags blazes Socket.close(), besides releasing the socket (system feature) will also trigger an exception when trying to close the connection that has already been lost.

Obs.: According to the documentation of the methods Session.close() and Session.disconnect(), they only need to be called in applications that pass a persistent JDBC connection (long-Session or long-Conversation) to Hibernate instead of letting him create a pool of them alone. If you use a pool of connections don’t call sessao.close() and ignore what I say about the second exception.

Second exception: capture and then ignore

This second exception does not need to receive any special treatment because it is a normal event arising from the loss of network connection. So just capture this second exception and do nothing else with it, not even log in. The socket will be released and will no longer be a busy feature in the system, which would be a potential problem if the number of open connections is (or can be) too large. The operating system is limited in the number of sockets it can keep busy, even if disconnected.

The only problem of this second exception, besides not needing to log anything, is that the catch (Throwable e) should be more specific: catch (HibernateException e).

First exception: move to the level above

Already the catch for the first exception may or may not be necessary. In general in this situation the level above will want to treat some fault exception in the bank, perhaps by displaying a dialog box to the user or in some other way, so instead of capturing the HibernateException and simply log in you have the option to let this exception go to the above level directly or else capture it and cast in its place a more "high level" exception, which does not specify that it is a Hibernate exception. You may think that HibernateException is a specific thing of a certain persistence solution that your system should be free to exchange for others, and so should exchange it for a DaoException (Do Daos make sense in Hibernate? I guess not so much heheheh), or PersistenceException or something like that (I’m giving an example of any exception here, just to emphasize this idea):

// Resolvi tornar checked também, mas isso é opcional
public class PersistenceException extends Exception {
    public PersistenceException(Throwable e) {
        super(e);
    }
}

public void salvar(Usuario usuario) throws PersistenceException {
    Session sessao = null;

    try {
        sessao = HibernateUtil.getSessionFactory().openSession();
        Transaction transacao = sessao.beginTransaction();
        sessao.save(usuario);
        transacao.commit();
    } catch (HibernateException e) {
        throw new PersistenceException(e);
    } finally {
        try{
            if (sessao != null) {
                sessao.close();
            }
        } catch (HibernateException e) {
            // Não precisa fazer nada
        }
    }
}

Completion

In this case, you can observe that even HibernateException not being checked and you not being required to treat the two exceptions that have been generated, it is best to treat them in some way to prevent a system resource from being left busy.

About the Try with Resources, Session does not implement the interface Closeable then you can’t use.

  • Got it! So I really need to close the session, I had no idea about Session not implementing Closeable. Thank you so much.

8

No, you’re right, people capture exceptions much more than they should. Although Java requires or at least encourages it to capture many exceptions that should not be captured, or at least should be captured at another time. I say it everywhere here on the site.

I find it unlikely that the try more internal is necessary. If you happen to be unable to close the connection you are probably in such a catastrophic condition that this is a programming error or the environment is so compromised that the best thing to do is to let the application break. You can even capture the exception elsewhere to give a cute message and maybe log in what happened but should be a general treatment of the entire application and not something more localized and specific.

It is almost certain that capture Exception is wrong, except in the main() or somewhere where the execution of the application is centered, where it is the point of entry and exit. I would say capture Throwable is always wrong. In addition to being too generic (see why it is bad in the answers of link above), it picks up programming errors or that can’t be done anything. These errors should not be handled, they should be solved.

The other try can be useful. Of course, if it’s just to show the error in the console and nothing else, I don’t know if it’s so useful, it might even be in some case, but I don’t think this is the reason for the question.

Catching an error where an external resource is informing you of a failure is usually a good thing. You just need to see if the location is the best possible one. You don’t need to capture it as quickly as possible. And yes, often it is the case, then it seems appropriate.

Java 7 introduced the try for resources where the finally becomes unnecessary. If Hibernate uses this pattern it can be eliminated. It seems that it does not use for this object, but it is the hint to delete in other cases.

  • 1

    But in this case Try for resources would not give me an autocloseable ? Being able to pass for a catch but without needing a Finally?

  • 1

    @Kolnmrk you’re right, you can use the catch with the try(). Fixed. Today I’m half asleep. Lots of language, each with a way...

  • 3

    @Check the code of my answer, I believe in the case of sessao null initialization is not redundant because openSession() can launch exception before giving time to return the instance of Session and this needs to be checked in sessao.close().

  • 1

    @Piovezan I think you’re right

6

Closing resources properly isn’t always as simple as it seems. More subtle problems can occur.

I’ve been reading the article these days How to Close JDBC Resources Properly - Every Time. For normal connections, that is, using Connection and Statement and not Hibernate or JPA, the most appropriate code is the following:

Connection connection = dataSource.getConnection();
try {
    Statement statement = connection.createStatement();
    try {
        ResultSet resultSet = statement.executeQuery("some query");
        try {
            //recuperar resultado aqui
        } finally {
            resultSet.close();
        }
    } finally {
        statement.close();
    }
} finally {
    connection.close();
}

In your case, specifically, you could do so:

try {
    Session sessao = HibernateUtil.getSessionFactory().openSession();
    try {
        Transaction transacao = sessao.beginTransaction();
        try {
            sessao.save(usuario);
            transacao.commit();
        } catch (Exception e) {
            transacao.rollback();
            throw e;
        }
    } finally {
        sessao.close();
    }
} catch(Throwable e) {
    trataErro(e);
}

The drawback of the code is the nesting level, it can be confusing. The advantage is that you don’t need to keep treating values unnecessarily. All resources are closed within the scope and, at least for me, easier to understand.

However, a common mistake in large applications is to stay using code boileroplate everywhere. Then some new programmer forgets some finally and its application begins to have memory leakage.

One way to solve this using direct access to the database is through a library like JdbcTemplate spring.

For JPA or Hibernate you can configure to use a Control Inversion and Dependency Injection framework like Spring to inject your Session and automatically manage its closure within the scope of a request (thinking of a web system).

Or, if you can’t or don’t want to use frameworks, you can package this logic yourself. Example using Java 8 and Amblas:

public void salvar(Usuario usuario) {
    execute(session -> session.save(usuario));
}

public void execute(Consumer<Session> consumer) {
    try {
        Session sessao = HibernateUtil.getSessionFactory().openSession();
        try {
            Transaction transacao = sessao.beginTransaction();
            try {
                consumer.accept(sessao);
                transacao.commit();
            } catch (Exception e) {
                transacao.rollback();
                throw e;
            }
        } finally {
            sessao.close();
        }
    } catch (Throwable e) {
        trataErro(e);
    }
}

Ready, now you never need to repeat that code.

And then, if you feel the need to add one more try/catch to make a rollback in case of error, just need to tinker in one place.

Example:

public void execute(Consumer<Session> consumer) {
    try {
        Session sessao = HibernateUtil.getSessionFactory().openSession();
        try {
            Transaction transacao = sessao.beginTransaction();
            try {
                consumer.accept(sessao);
                transacao.commit();
            } catch (Throwable e) {
                transacao.rollback(); 
                throw e;
            }
        } finally {
            sessao.close();
        }
    } catch (Throwable e) {
        trataErro(e);
    }
}
  • 2

    It hasn’t occurred to me that Resultsets, Statements and Transactions can be closed. This is certainly important information. Now, I disagree with the direct startup of the variable that maintains the connection or session. Getting the connection is an I/O operation and as such can generate an exception, which would need to be captured and handled.

  • 1

    @Piovezan Actually this was my mistake, transactions are not closed, but should have the commit or rollback. As soon as I get home I clean this up.

  • When I looked at the article I thought I saw the transaction being closed :)

  • In my case, I came to question the constant use of Try/catch, however, in addition to bringing an extensive code you came up with quite valuable information on the use of resources. It seems complex but it is very quiet to understand, I will try to apply this Pattern in my projects.

  • 2

    On second thought, direct initialization is not wrong if the intention is to pass the exception to the level above. But it has the restriction that I commented, you will be passing an implementation detail (exception of a specific framework) to the top layer, which may not be desirable.

  • 3

    @Piovezan I also do not like direct startup, so much so that in my final example I did a treatment to prevent this. I think the focus of the article was on close and not errors. From my point of view the application should properly log the error and encapsulate the original exception in a friendly response.

Show 1 more comment

3

Whenever you want to handle a possible error, you will need to use Try/catch. If the error Voce receives from Hibernate is enough for you in the above layers, just don’t treat the exception.

Depending on your choices for the presentation layer, there are other ways to treat errors in an automated way, such as Spring MVC (in the case of web applications) as explained in this article

As for Finally, I believe that the new versions of Hibernate treat session closure, without Voce having to worry about it.

But again, if Voce has a specific interest to treat the possibility of not immediately closing the session, then YES, Voce will have to do this code "dirty" to solve your problem.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.