一、简单使用
CrudRepository接口提供了简单的增删改查,只要如下,写一个接口继承,就可以直接使用。
public interface UserDao extends CrudRepository<User,Integer> {
}
//直接调用接口
User user = userDao.findById(2).get();
类似还有PagingAndSortingRepository提供简单的分页与排序实现,JpaRepository提供进一步接口等,可以参考:Spring Boot学习笔记(三)Repository的使用
到这里其实只使用Spring而初次接触Spring-JPA的话,其实就已经有疑问了:为什么这里只有一个interface继承,却可以直接使用接口,具体实现跑哪里去了?
二、原理
具体原理可以参见spring-data-jpa原理探秘(1)-运行环境创建及加载Repository接口,简单来讲,spring-data-jpa利用cg-lib,在需要注入一个实现了Repository接口的派生接口时,都会尝试实现一个实例创建Bean注入进去。如例中的UserDao,派生自CrudRepository–>Repository,所以在注入时,会自动实现一个实例创建Bean(由工厂类来生成,该工厂类可自己实现,默认实现类为SimpleJpaRepository)注入。
有看得仔细的会注意到,所有派生自Repository的接口,那应该包括CrudRepository啊!是的,也包括这个接口,所以又专门写了一个注解@NoRepositoryBean,来标注不需要这种操作的接口,看源码的话会发现CrudRepository是添加了这个注解的。
三、进阶一——自定义简单函数
spring-data-jpa支持自定义简单函数,只要在interface中直接声明函数就可以了,Respository会根据函数名,自动生成sql,如:
public interface UserDao extends CrudRepository<User,Integer> {
List<User> findByName(String name);
}
//直接使用,无需实现函数体
List<User> list = userDao.findByName("test");
//可以查看日志,生成的sql,类似:
//select * from user where name=?
而且还支持findByNameAndValue/findByNameLike等复杂逻辑,具体支持的规则及生成的SQL参见:官方文档:Spring-data-jpa Query Creation
四、进阶二——自定义函数实现
上面实现的是按Spring-data-jpa定义的规则生成函数,如果需要自己定义复杂点的实现,也提供了一种方式
- 首先简单定义一个CustomizedUserRepository接口以及实现CustomizedUserRepositoryImpl,除了名称上,实现的名字必须是接口名+Impl外,没有其他强制规则
- 让上例中的UserDao,同时继承Repository
就是这么简单,剩余的事情,spring-data-jpa会帮你做,也就是说在二、原理 中提到的自动生成实例并注入时,会将你自定义的实现也合并进去。具体代码如下:
public interface CustomizedUserRepository {
User getUserByName();
}
public class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
@PersistenceContext
private EntityManager em;
@Override
public User getUserByName() {
return em.find(User.class,2);
}
}
public interface UserDao extends CrudRepository<User,Integer>,CustomizedUserRepository {
}
//直接使用
User user = userDao.getUserByName();
更详细的文档参见官方文档 4.6.1. Customizing Individual Repositories
五、进阶三——自定义Repository基类
虽然Spring-data-jpa提供了详细且方便的实现,但我们有时还是会需要自定义一些公共的函数,Spring-data-jpa也提供了实现的方法:
- 自定义一个基础接口BaseRepository及实现类BaseRepositoryImpl,实现自己的公共函数
- 添加自定义Repository类配置:@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
实现虽然很简单,但也碰到了一个坑,因为官方文档上并没有提到使用接口,接口只是为了后续自己的interface使用方便,具体看代码:
//下面再解释为什么要加@NoRepositoryBean
@NoRepositoryBean
public interface BaseRepository<T> {
public List<T> findBySql(String sql, Object... values);
}
public class BaseRepositoryImpl<T,ID extends Serializable> extends SimpleJpaRepository<T,ID> implements BaseRepository<T> {
private final EntityManager entityManager;
public BaseRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
this.entityManager = entityManager;
}
private Session getSession(){
return entityManager.unwrap(Session.class);
}
private NativeQuery createSqlQuery(String sql , Object... values){
NativeQuery query = getSession().createNativeQuery(sql);
for(int i = 0;i < values.length;++i){
query.setParameter(i + 1,values[i]);
}
return query;
}
public List<T> findBySql(String sql, Object... values){
return createSqlQuery(sql,values).addEntity(getDomainClass()).list();
}
}
public interface UserDao extends CrudRepository<User,Integer>, BaseRepository<User> {
}
@SpringBootApplication
@EnableJpaRepositories(repositoryBaseClass = BaseRepositoryImpl.class)
public class SpringbootdemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootdemoApplication.class, args);
}
}
//直接使用
List<User> list = userDao.findBySql("select * from user");
这里的坑就是为什么BaseRepository要用@NoRepositoryBean这个注解,因为BaseRepository只是一个简单的接口,明明什么都没做,为什么要特殊处理呢?其实做了一个实验就会知道为什么:如果将BaseRepository重新命名为OtherRepository,但BaseRepositoryImpl保持不变,则不加@NoRepositoryBean注解也是可以的。再联想下第四条里的内容,就可以知道,因为我们这里使用的接口+实现且实现名=接口名+Impl的命名规则,以及UserDao的继承方式,和四、进阶二——自定义函数实现 中的处理都是完全一样的,所以会被误处理,这时,加上@NoRepositoryBean注解,就是用来取消这个错误处理的!
所以SpringBoot有很多自动配置,确实可以减少大量工作,但用不好的话,很难调试及发现。
官方文档这部分的内容较少,参见:4.6.2. Customize the Base Repository