Olá Hugo!
Sua pergunta é muito interessante. Realmente, a camada DAO é responsável por realizar as operações no banco de dados, mas não deveria ser responsável por fechar a conexão. Essa responsabilidade deve ser passada para a camada Service.
A conexão pode ser um atributo da classe DAO, mas ela deve ser aberta e fechada em cada método da classe Service que utiliza a DAO. Isso pode ser feito utilizando um try-with-resources, que fecha a conexão automaticamente após o uso. Um exemplo seria:
public class ClienteService {
private ClienteDAO dao = new ClienteDAO();
public List<Cliente> listar() {
try (Connection conn = dao.getConnection()) {
return dao.listar(conn);
} catch (SQLException e) {
// tratamento de exceção
}
}
public void salvar(Cliente cliente) {
try (Connection conn = dao.getConnection()) {
conn.setAutoCommit(false);
dao.salvar(conn, cliente);
conn.commit();
} catch (SQLException e) {
// tratamento de exceção
}
}
}
Note que o método getConnection() da classe DAO é chamado em cada método da classe Service que utiliza a DAO. Isso garante que a conexão seja aberta e fechada corretamente.
Quanto à organização da camada Service, existem vários padrões que podem ser utilizados, como o padrão Repository, o padrão Facade, entre outros. O importante é escolher um padrão que se adeque às necessidades do seu projeto e que facilite a manutenção do código.
Espero ter ajudado e bons estudos!