The usual problem
So you have this rich domain model and you're wondering how to get it in and out of your database. Of course I could just tell you to "use an object oriented DB and be done with it", but let's assume that you are somehow required to use a relational store.
Hibernate and other full-blown JPA implementations can be a bit overkill due to the amount of "magic" going on in the background. They take a lot of work off your shoulders. But if anything goes wrong, you often spend a large amount of time debugging (two words: lazy loading).
When looking for alternatives, many people - me included - often recommend Mybatis. Mybatis is radically simple in its concept, being just a thin wrapper on top of JDBC. It is great for mapping even the most complicated queries. This makes it especially suited for working with legacy databases out of your control or for batch processing and business intelligence queries.
However, Mybatis is not an ORM. It does not know your objects. It just knows SQL queries. So when you select the same thing with two different queries, you will get two different instances. This makes handling complex object relationships very involved. Also, since Mybatis does not keep track of you objects, saving changes becomes an even greater challenge Things like cascading persist just don't exist. So if you were to load and persist complex object trees, you would need to implement a lot of ORM logic on top of Mybatis.
The conceptBut there is a middle ground. It is called Ebean. Ebean reads JPA annotations, but does not implement JPA itself. It has a much simpler model. Ebean has a persistence context that guarantees object identity within transactions. But it does not "automagically" synchronize changes between memory and the database. You need to explicitly call "save" to persist changes to an object. You could view this as objects always being "detached". This is not entirely true, because with Ebean your objects keep track of changes themselves, so they are somewhere between what JPA calls "Managed" and "Detached". This makes reasoning about a program a lot simpler.
FeaturesEbean comes with nice criteria queries that support automatic join fetching. So if you ask for all employees who work in the "IT" department, you can write something like "eq("department.name", "IT")" and Ebean will know that it has to join the employee table and the department table to find the correct answer. You can also specify fetch strategies on a per-query basis. So if you have two different use cases, one where you always need a certain association and one where you don't need it, you can write two different queries, resulting in optimized performance Lazy loading just works, as Ebean can keep connections open after a transaction has finished in order to lazily load more data.
Ebean also has nice support for raw SQL, similar to what Mybatis can do, just a little less flexible. You can write classes that represent the result of a SQL query (like some aggregation result) and tell Ebean how to map the result set to the bean's fields. Batch processing is also supported in two ways. RawSQL objects never get attached to the persistence context, so they don't eat up your memory. And if you want to batch process Entities, you can use a visitor-like query that executes a callback for every entity found, but does not put those entities into the persistence context.
The configuration of Ebean is very pluggable, so you can change datasources, transaction management, naming conventions and a lot more. The source code is also neatly structured. So if you find something lacking, it is pretty easy to find the correct place for you enhancement. I was able to add a naming convention for embeddable objects (so you don't need @AttributeOverride so often) in about 5 lines of code.