public interface UserRepository extends JpaRepository<User, Integer>{}
public class UserRepositoryImpl {}
那么这里为什么要这么做呢?原因是:spring-data-jpa提供基础的CRUD工作,同时也提供业务逻辑的功能(前面说了,这是该框架的威力所在),所以我们的Repository接口要做两项工作,继承spring-data-jpa提供的基础CRUD功能的接口,比如JpaRepository接口,同时自己还需要在UserRepository这个接口中定义自己的方法,那么导致的结局就是UserRepository这个接口中有很多的方法,那么如果我们的UserRepositoryImpl实现了UserRepository接口,导致的后果就是我们势必需要重写里面的所有方法,这是Java语法的规定,如此一来,悲剧就产生了,UserRepositoryImpl里面我们有很多的@Override方法,这显然是不行的,结论就是,这里我们不用去写implements部分。
public class SimpleJpaRepository<T, ID extends Serializable> implements JpaRepository<T, ID>,
JpaSpecificationExecutor<T>
我们可以看到这个类是实现了JpaRepository接口的,事实上如果我们按照上面的配置,在同一个包下面有UserRepository,但是没有UserRepositoryImpl这个类的话,在运行时期UserRepository这个接口的实现就是上面的SimpleJpaRepository这个接口。而如果有UserRepositoryImpl这个文件的话,那么UserRepository的实现类就是UserRepositoryImpl,而UserRepositoryImpl这个类又是SimpleJpaRepository的子类,如此一来就很好的解决了上面的这个不用写implements的问题。我们通过阅读这个类的源代码可以发现,里面包装了entityManager,底层的调用关系还是entityManager在进行CRUD。
User login(String name, String password);
然后在serviceImpl中写该方法的实现,大致这样:
@Override
public User login(String name, String password) {
return userDao.login(name, password);
}
接下来,UserDao大概是这么个样子:
User getUserByNameAndPassword(String name, String password);
然后在UserDaoImpl中大概是这么个样子:
复制代码
public User getUserByNameAndPassword(String name, String password) {
Query query = em.createQuery("select * from User t where t.name = ?1 and t.password = ?2");
query.setParameter(1, name);
query.setParameter(2, password);
return (User) query.getSingleResult();
}
复制代码
ok,这个代码运行良好,那么这样子大概有十来行代码,我们感觉这个功能实现了,很不错。然而这样子真正简捷么?如果这样子就满足了,那么spring-data-jpa就没有必要存在了,前面提到spring-data-jpa能够帮助你完成业务逻辑代码的处理,那他是怎么处理的呢?这里我们根本不需要UserDaoImpl这个类,只需要在UserRepository接口中定义一个方法
User findByNameAndPassword(String name, String password);
然后在service中调用这个方法就完事了,所有的逻辑只需要这么一行代码,一个没有实现的接口方法。通过debug信息,我们看到输出的sql语句是
select * from user where name = ? and password = ?
跟上面的传统方式一模一样的结果。这简单到令人发指的程度,那么这一能力是如何实现的呢?原理是:spring-data-jpa会根据方法的名字来自动生成sql语句,我们只需要按照方法定义的规则即可,上面的方法findByNameAndPassword,spring-data-jpa规定,方法都以findBy开头,sql的where部分就是NameAndPassword,被spring-data-jpa翻译之后就编程了下面这种形态:
where name = ? and password = ?
在举个例,如果是其他的操作符呢,比如like,前端模糊查询很多都是以like的方式来查询。比如根据名字查询用户,sql就是
select * from user where name like = ?
这里spring-data-jpa规定,在属性后面接关键字,比如根据名字查询用户就成了
User findByNameLike(String name);
被翻译之后的sql就是
select * from user where name like = ?
这也是简单到令人发指,spring-data-jpa所有的语法规定如下图:
@PersistenceContext
private EntityManager em;
@SuppressWarnings("unchecked")
public Page<Student> search(User user) {
String dataSql = "select t from User t where 1 = 1";
String countSql = "select count(t) from User t where 1 = 1";
if(null != user && !StringUtils.isEmpty(user.getName())) {
dataSql += " and t.name = ?1";
countSql += " and t.name = ?1";
}
复制代码
复制代码
/**
* Creates a {@link TypedQuery} for the given {@link Specification} and {@link Sort}.
*
* @param spec can be {@literal null}.
* @param sort can be {@literal null}.
* @return
*/
protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) {
复制代码
复制代码
/**
* Applies the given {@link Specification} to the given {@link CriteriaQuery}.
*
* @param spec can be {@literal null}.
* @param query must not be {@literal null}.
* @return
*/
private <S> Root<T> applySpecificationToCriteria(Specification<T> spec, CriteriaQuery<S> query) {
select * from student t left join clazz tt on t.clazz_id = tt.id
这是一个很常规的sql,但是JPQL是这么写:
select t from Student t left join t.clazz tt
left join右边直接就是t的属性,并且也没有了on t.clazz_id == tt.id,然而并不会出现笛卡尔积,这里解释一下为什么没有这个条件,在我们的实体中配置了属性的映射关系,并且ORM框架的最核心的目的就是要让我们以面向对象的方式来操作数据库,显然我们在使用这些框架的时候就不需要关心数据库了,只需要关系对象,而t.clazz_id = tt.id这个是数据库的字段,由于配置了字段映射,框架内部自己就会去处理,所以不需要on t.clazz_id = tt.id就是合理的。
前面介绍了spring-data-jpa的使用,还有一点忘了,悲观所和乐观锁问题,这里的乐观锁比较简单,jpa有提供注解@Version,加上该注解,自动实现乐观锁,byId修改的时候sql自动变成:update ... set ... where id = ? and version = ?,比较方便。
in操作的查询:
在日常手动写sql的时候有in这种查询是比较多的,比如select * from user t where t.id in (1, 2, 3);有人说in的效率不高,要少用,但是其实只要in是主键,或者说是带有索引的,效率是很高的,mysql中如果in是子查询貌似不会走索引,不过我个人经验,在我遇到的实际应用中,in(ids)这种是比较多的,所以一般来说是没有性能问题的。
复制代码
复制代码
/**
* Applies the given {@link Specification} to the given {@link CriteriaQuery}.
*
* @param spec can be {@literal null}.
* @param query must not be {@literal null}.
* @return
*/
private <S> Root<T> applySpecificationToCriteria(Specification<T> spec, CriteriaQuery<S> query) {