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<User> users=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通常是更灵活的选择。