I agree with your first approach of originating the exception in the service. IMHO, DAO's should be as dumb as possible, and only throw exceptions that are related to either a bad argument (IllegalArgumentException) or thier *specific* implementation. i.e. SqlExceptions if using straight sql, etc. The problem with throwing your checked exception from the dao, is that it's specific to a domain concern, someone is trying to save an existing user, and you want to catch the exception and recover. If this domain concern is left to the dao, someone wanting to add a new dao implementation also has to worry about whatever domain concerns the dao is supposed to provide PLUS it's actual data access requirements.
I view the actual service as the Repository, it's implementing the Domain concerns and calling DAOs to defer the actual data access.