4

I'm using a Data Access Object (DAO) pattern in Java and I have the same piece of code repeated all over my files. The thing is somethimng like this:

public User getById(int id) throws BDException {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.getTransaction();

    try {
        tx.begin();
        Query query = session.createQuery("SELECT u FROM User u WHERE u.id=:id");
        query.setString("id", id);
        User user = (User) query.uniqueResult();
        tx.commit();

        return user;
    }
    catch(javax.validation.ConstraintViolationException | org.hibernate.exception.ConstraintViolationException cve) {
        try {
            if(tx.getStatus() == TransactionStatus.ACTIVE) {
                tx.rollback();
            }
        }
        catch(Exception exc) {
            LOGGER.error("Error rollback in method='" + getMethodName() + "'");
        }
        throw new BDException(cve);
    }
    catch(RuntimeException ex) {
        try {
            if(tx.getStatus() == TransactionStatus.ACTIVE) {
                tx.rollback();
            }
        }
        catch(Exception exc) {
            LOGGER.error("Error rollback in method='" + getMethodName() + "'");
        }
        throw ex;
    }
    catch(Exception ex) {
        try {
            if(tx.getStatus() == TransactionStatus.ACTIVE) {
                tx.rollback();
            }
        }
        catch(Exception exc) {
            LOGGER.error("Error rollback in method='" + getMethodName() + "'");
        }
        throw new RuntimeException(ex);
    }
}

Well, I want you to look at the catch's part. I have it repeated in every method I have. If it was simple code, I could create a method, put all that code inside and call the method instead of repeat the code. The problem is that it is not normal code, they are exceptions.

So, is there any solution to reuse code and not to repeat (copy-pasting) the code in every method?

Thanks!

Ommadawn
  • 2,450
  • 3
  • 24
  • 48

2 Answers2

4

is there any solution to reuse code and not to repeat (copy-pasting) the code in every method?

There is.

The "meat" of your function is here

    Query query = session.createQuery("SELECT u FROM User u WHERE u.id=:id");
    query.setString("id", id);
    User user = (User) query.uniqueResult();

If you squint very carefully, you may see that this is a "function" that accepts a Session as an argument, and returns a User. What you can then do is make this function an argument to the thing that does all of the exception handling.

In Java, that usually means expressing the function as an "object"

User MyCrazyFunctionThing::uniqueResult(Session session) {
    Query query = session.createQuery(this.sql);
    query.setString("id", this.id);
    return query.uniqueResult();
}

User DatabaseGateway::execute(MyCrazyFunctionThing q) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.getTransaction();

    try {
        tx.begin();
        User user = q.uniqueResult(session)
        tx.commit();

        return user;
    } catch (...) {
        // ...
    }
}

Right away, you can turn that into logic that can be run any time you try to fetch a unique user from a session.

You can make that even more general with generics

interface MyCrazyGenericThing<T> {
    T uniqueResult(Session session);
}

class MyCrazyFunctionThing implements MyCrazyGenericThing<User> {
    User uniqueResult(Session session) {
        Query query = session.createQuery(this.sql);
        query.setString("id", this.id);
        return query.uniqueResult();
    }
}

<T> T DatabaseGateway::execute(MyCrazyGenericThing<T> q) {
    Session session = sessionFactory.getCurrentSession();
    Transaction tx = session.getTransaction();

    try {
        tx.begin();
        T result = q.uniqueResult(session)
        tx.commit();

        return result;
    } catch (...) {
        // ...
    }
}

What you are seeing here is the Strategy Pattern being used to specify what code should run inside the transaction logic.

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
2

Looks like a job for the Execute Around idiom.

Place the specialised code in a lambda expression. Pass the specialised code to a method with the general code that executes the object holding the lambda expression at the appropriate point.

For your code, depending on exactly what you want to factor out, usage may look something like:

public User getById(int id) throws BDException {
    return query(
        "SELECT u FROM User u WHERE u.id=:id",
        query -> {
            query.setString("id", id);
            return (User) query.uniqueResult();
        }
    );
}
Tom Hawtin - tackline
  • 145,806
  • 30
  • 211
  • 305
  • 1
    @Ommadawn In the `query` function. It doesn't really matter how that side of things looks - it's the client side which important. Write that how you want first. There's a lot of choices to be made. Only then the implementation side of things follows. The original question has a lot of boilerplate, much of which I'd suggest against for reasons not related to the question, so – Tom Hawtin - tackline Dec 26 '18 at 09:00
  • @tackline so I have to manage the exception in the frontend, redirecting to a error page, isn't it? – Ommadawn Dec 26 '18 at 13:47
  • 1
    @Ommadawn I don't follow. The functional interface you use for the lambda can throw an exception. The `query()` method above can catch and/or throw exceptions. If you try to use the standard set of functions interfaces in java.util.function then they don't tend to declare throws clauses - so you'll almost certainly need something more specific. – Tom Hawtin - tackline Dec 26 '18 at 13:52
  • thanks for you precious help. I can't achieve the idea, sorry. Could you edit your answer and add a mini mini mini example of how you put the exceptions there? I can't imagine. It would be very appreciated. – Ommadawn Dec 26 '18 at 14:05
  • 1
    @Ommadawn The linked answer has a Jon Skeet answer with an example using throws clauses and try-finally. – Tom Hawtin - tackline Dec 26 '18 at 15:50