2.8 Repository层

repository是最接近数据库的一个层,它的功能就是对数据库的表进行CRUD操作(create、Read、Update和Delete,也就是增删查改)。Spring Boot一个很重要的革新,就是repository仅有接口无需实现,按照特定的规则命名函数,Spring Data JPA会自动实现接口。在上一节已经看过一个例子,本节将介绍更多细节。
1.一个repository接口对应一个实体类
每个repository接口的命名一般都是按照实体类来,例如User表的repository接口就应该命名成UserRepository,例如代码所示:
package com.example.repos;
import com.example.demo.User;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository 
public interface UserRepository extends CrudRepository<User,String{
    User findByUsername(String username);
    /**
    
     根据账号查找role
     */
    @Query(value="SELECT u.role FROM User u WHERE u.username=:name")
    String queryRoleByName(String name);
    /**
    
     根据role查找用户
     */
    List findByRole(String role);
    /**
    
     保存用户
    
     */
    User save(User user);
    /**
    
     删除用户
    
     */
    void delete(User user);
    /**
    
     查询用户是否存在
    
     */
    boolean existsByUsername(String username);
}

UserRepository 首先需要标签@Repository,然后它必须继承Spring提供的接口,这里继承的是CrudRepository(还有很多个接口可供继承),还需要指定泛化的参数,第一个参数是绑定的实体类,第二个是这个实体类的主键的类型。
和实体类一样,repository的所有接口通常都放在一个包里。
下面是对这个接口的测试:
package com;
import com.example.demo.User;
import com.example.repos.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootApplication 
 
public class JPAApplication{
    private static final Logger log=LoggerFactory.getLogger(JPAApplication.class);
    public static void main(String[]args){
        SpringApplication.run(JPAApplication.class,args);
    }
    @Bean 
    public CommandLineRunner user(UserRepository repository){
        return(args)-{
            User user=repository.findByUsername("abcd");
            log.info(user.getUsername());
            log.info(user.getNickname());
            log.info(user.getCreateDate());
            log.info(user.getRole());
            List<Userusers=repository.findByRole("0");
            log.info("role为0的用户数量是:"+users.size());
        };
    }
}

2.方法命名规则
首先来看看UserRepository对应的实体类User:
package com.jssp.entities;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Set;
@Entity 
public class User{
    @Id 
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private String username;
    private String password;
    private String role;
    private String nickname;
    private String createDate;
    private String wxOpenid;
    // getter和setter略
}

现在看看UserRepository代码里面的几个方法。
a)findByUsername 根据主键查找,username是主键
b)findByRole 根据角色查找,此方法返回一个列表
c)save 新增或者更新都是用这个方法名,参数是要更新的实体对象
d)delete 删除,参数是要删除的实体对象
e)existsByUsername 判断用户是否存在,参数是主键
f)queryRoleByName 这个是自定义的查询方法,不必遵循特定的命名规则,写在上方的@Query标签处的是类SQL的查询语言JPQL。如果要使用原生的SQL语言,可以在@Query标签加上第二个参数,如代码所示:
@Query(value="SELECT role FROM users WHERE username = :name",nativeQuery=true)
public String queryRoleByNameWithNativeQuery(String name);
总结一下前缀:
普通查询,返回对象或集合:findBy
存在查询,返回布尔值:existsBy
保存或更新:save
删除:delete
自定义:方法名字自定,但需要在标签@Query写上查询语句
另外save,delete,findAll(查询所有)等几个方法如果没有别的逻辑要求,可以不定义。由JpaRepository和父接口提供的方法有:
save(S entity):保存给定的实体,可以是新的实体或更新现有实体。
saveAll(Iterable entities):保存给定的所有实体。
findById(ID id):根据id查找实体。
existsById(ID id):根据id判断实体是否存在。
findAll():查找所有实体。
findAll(Sort sort):查找所有实体,根据排序参数进行排序。
findAllById(Iterable ids):根据多个id查找对应的所有实体。
count():返回实体总数。
deleteById(ID id):根据id删除实体。
delete(T entity):删除一个实体。
deleteAll(Iterable entities):删除给定的实体集合。
deleteAll():删除所有实体。
deleteAllInBatch():批量删除表中的所有实体。
getOne(ID id):根据id获取实体的引用,此方法不会立即访问数据库,直到实体被真正使用。
findAll(Pageable pageable):分页查询实体。

3.常用的查询命名规则
查询是最常用,也是逻辑最复杂的,Spring Boot提供了很多规则来命名查询方法:
find用作查询,可以根据喜好使用query,read,get。
By后面跟属性名称:表示查询的条件(例如,findByUsername根据用户名查询)。

Containing(包含), Between(之间), LessThan(小于), GreaterThan(大于), Like。在属性名之后添加这些词,可以指定属性的查询方式。下面是具体的例子:
Containing(包含)
方法签名: List findByUsernameContaining(String username);
说明: 此方法会找出所有username字段包含给定username字符串的User实例。例如,如果数据库中存在用户名为"john_doe", "jane_doe", "johanna"的用户,调用findByUsernameContaining("john")将返回包含"john_doe"和"johanna"的User实例列表。这类似于SQL中的LIKE '%value%'操作。

Between(介于)
方法签名: List findByAgeBetween(int startAge, int endAge);
说明: 这个方法会返回年龄在startAge和endAge之间的所有User实例(包括边界值)。比如,findByAgeBetween(18, 25)会找出所有年龄在18到25岁之间的用户。

LessThan(小于)
方法签名: List findByAgeLessThan(int age);
说明: 此方法会返回年龄小于给定age的所有User实例。例如,findByAgeLessThan(18)将返回所有未满18岁的用户。

GreaterThan(大于)
方法签名: List findByAgeGreaterThan(int age);
说明: 这个方法会返回年龄大于给定age的所有User实例。如果调用findByAgeGreaterThan(65),它将找到所有年龄超过65岁的用户。

Like(模糊查询)
方法签名: List findByEmailLike(String emailPattern);
说明: 此方法将找出所有email字段匹配给定emailPattern字符串模式的User实例。例如,findByEmailLike("%@gmail.com")会返回所有电子邮件地址以"@gmail.com"结尾的用户。这与Containing相似,但Like可以更灵活地用于任何位置的匹配,而不仅仅是包含(尽管在实践中Containing通常被用来实现LIKE '%value%'查询)。

And 和Or,就是并且和或者。下面是具体的例子
AndAnd(并且)
方法签名: List findByUsernameAndAge(String username, int age);
说明: 此方法将会找出用户名等于给定username且年龄等于age的所有User实例。这相当于SQL中的查询条件WHERE username = ? AND age = ?。

Or(或者)
方法签名: List findByUsernameOrAge(String username, int age);
说明: 这个方法会返回用户名等于给定username或年龄等于age的所有User实例,相当于SQL中的WHERE username = ? OR age = ?。

d)OrderBy后面跟属性名称和Asc/Desc:表示查询结果的排序方式。例如:
方法签名: List findByAgeGreaterThanOrderByAgeAsc(int age);
说明: 此方法会查找所有年龄大于给定age的User实例,并按照年龄升序排序。这等同于SQL查询中的WHERE age > ? ORDER BY age ASC。

使用Top或者Firste)
限制查询结果的数量,例如:
findTop10ByOrderByAgeDesc方法将返回年龄最大的前10个用户。
findFirst5ByUsernameContaining方法将返回用户名中包含指定字符串的前5个用户。
这些方法对于实现简单的限制查询非常方便,但仅仅支持从开始取若干个数,它不能完全替代limit;如果要分页逻辑,使用Pageable通常是更灵活的选择。