@Entity
public class Book {
@Id
@GeneratedValue
private Long id;
@Column(unique = true)
private String title;
/** For hibernate */
protected Book() {
}
public Book(String title) {
this.title = title;
}
public Long getId() {
return this.id;
}
public String getTitle() {
return this.title;
}
}
@Entity
public class Book implements PersistentEntity<Long> {
-- snip --
public Long getPrimaryKey() {
return this.id;
}
}
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where title = ?")
Book getByTitle(String title);
}
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://autodao.sourceforge.net/schemas/autodao-0.1 http://autodao.sourceforge.net/schemas/autodao-0.1.xsd"
xmlns:dao="http://autodao.sourceforge.net/schemas/autodao-0.1">
</beans>
<dao:hibernate id="dao" interface="net.sf.autodao.BookDao" />
BookDao dao = ...; //Inject DAO from Spring context
Book b = new Book("Wicket in Action");
dao.create(b); //New book was persisted.
-- later in your code --
Book fromDb = dao.findByTitle("Wicket in Action");
net.sf.autodao.Dao interface (that all your DAOs should inherit from) provides basic CRUD operations (I hope they're staightforward enough and don't need detailed explanation):
Currently, AutoDAO namespace has only single tag: dao:hibernate. Its only required attribute is interface - interface this DAO will implement.
By default, AutoDAO searches Hibernate SessionFactory under "sessionFactory" bean name. If you use another bean name, then you need to tell AutoDAO about it by specifying its name in session-factory attribute.
If you want to assign specific bean name to your DAO, use id attribute (by default, Spring-assigned generated names are used).
In very rare conditions, you might want to explicitely specify persistent entity class for DAO. Use entity attribute for that.
AutoDAO doesn't begin/commit transactions itself. Instead, it participates in existing Spring-managed transactions via HibernateTemplate usage. So it's your responsibility to properly setup transaction management. Read Transaction management Spring documentation.
<dao:hibernate id="bookDao" session-factory="customSessionFactoryBeanName" interface="net.sf.autodao.BookDao" />
If you have a legacy codebase with lots of named queries and don't want to move them into @Finder annotations, you can happily use existing named queries as is:
public interface BookDao extends Dao<Book, Long> {
@Finder(namedQuery="Book.getByTitle")
Book getByTitle(String title);
}
However, my mom thinks named queries are evil because they appear to be very far away from the code that uses them.
Thus, advice: use @Finder(query="...") instead of named queries.
Guys from warp-persist stick to named queries because they don't validate queries in @Finder annotation. However, that doesn't apply to AutoDAO, wherever you place your queries, they'll get the same amount of validation during application startup.
By default, finder method parameters are passed to Hibernate as indexed parameters. You can modify this behavior on per-method basis and use named parameters if you want:
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where title = :title")
Book getByTitle(@Named("title") String title);
}
It is a error to mix named and indexed parameters (AutoDAO will produce a error during startup).
Advice: use named parameters when you need to use same parameter more than once in query or if there are too many parameters. In other cases, indexed approach with descriptive method parameter names should be enough.
If you query needs result paging (also known as "maxResults"/"firstResult" or limit/offset), just use @Limit/@Offset annotations:
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book order by popularity")
List<Book> getPopular(@Offset int pageNum, @Limit int pageSize);
}
Arguments with either @Limit or @Offset annotations won't be passed as query parameters but instead will be used to invoke appropriate methods of Hibernate query.
If you have a query that is guaranteed to return only one record, you can avoid List boilerplate in finder method return value and specify record type directly (you already saw this trick in quickstart):
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where title = :title")
Book getByTitle(@Named("title") String title);
}
You'll get exception from Hibernate if your query happens to return more than one result or ClassCastException if query result cannot be cast to finder method return value type.
In some situations you want finder method return value to be more specific than just java.util.List. For example, you know that all elements are unique and want to store them as java.util.Set. Or you want to perform some filtering on return value or any other case that involves non-list return value. Nothing complex, just specify desired collection type in return value and returnAs parameter of @Finder annotation:
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where popularity > 20", returnAs=TreeSet.class)
SortedSet<Book> getTopPopular();
}
You can use any collection implementation in returnAs argument so long as is has a no-arg constructor.
You can pass Joda-time ReadableInstant subclasses (most popular are DateTime and DateMidnight) and they'll be converted into java.util.Calendar instances that can be understood by Hibernate.
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where date between ? and ?")
List<Book> findBetween(DateMidnight since, DateMidnight until);
}
If using named query parameters, you can declare finder methods with collection or array arguments:
public interface BookDao extends Dao<Book, Long> {
@Finder(query="from Book where id in :ids")
List<Book> getByIds(@Named("ids") long[] ids);
@Finder(query="from Book where author in :authors")
List<Book> findAllByAuthors(@Named("authors") Set<Author> authors);
}
In case you want to support custom query argument types, you can use query transformer API by writing a class that implements net.sf.autodao.QueryArgumentTransformer and declaring a bean of that class under "autodaoQueryArgumentTransformer" name in your application context.
Beware, by doing so, you'll override default transformer that provides Joda-time integration. So recommented approach is to extend net.sf.autodao.DefaultQueryArgumentTransformer, handle custom argument types and fallback to super.transform(...).