二八佳人體似酥,腰間仗劍斬愚夫。雖然不見人頭落,暗裡教君骨髓枯。
上一章簡單介紹了SpringBoot整合JdbcTemplate(五),如果沒有看過,
請觀看上一章日常生活中,我們并不使用 JdbcTemplate, 而是使用 JPA和MyBatis,MyBatis-Plus. 這一章節,我們講解一下, JPA的相關操作。
一. SpringBoot 整合 JPA前期準備
按照老蝴蝶以前講解的方式,采用Maven 建構SpringBoot項目。
一.一 pom.xml 添加依賴
<!--引入MySql的驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--引入springboot與jpa整合的依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
一.二 application.yml 添加JPA的配置
# 引入 資料庫的相關配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: abc123
# JPA的相關配置
jpa:
# 設定資料庫平台
database-platform: org.hibernate.dialect.MySQLDialect
# 設定資料庫
database: mysql
# 是否展示SQL語句
show-sql: true
hibernate:
# 持久化規則是 update
ddl-auto: update
naming:
# 實體命名政策類的全限定名稱
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
一.三 建立 User 表
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(15) DEFAULT NULL,
`sex` varchar(20) DEFAULT NULL,
`age` int(6) DEFAULT NULL,
`description` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8;
二. 整合JPA應用
SpringBoot整合JPA時,需要建立相應的 實體類,工廠接口。
工廠接口有 Crud接口,有 PagingAndSorting,有 Jpa接口,也有 Specification 動态查詢接口。
每一種接口,都有其特殊的功能。
二.一 建立 POJO 類和業務類
在 pojo 包下,建立 User.java 類
@Data
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "user")
@Entity
public class User implements Serializable {
/**
* @param id id編号
* @param name 姓名
* @param sex 性别
* @param age 年齡
* @param description 描述
*/
@Id
//指定生成政策
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Integer id;
@Column(name = "name")
private String name;
@Column(name="sex")
private String sex;
@Column(name="age")
private Integer age;
@Column(name="description")
private String description;
}
在 service包下,建立相應的接口和實作類
public interface UserService {
}
@Service
public class UserServiceImpl implements UserService {
}
二.二 Crud 工廠接口
二.二.一 接口的相關定義
org.springframework.data.repository.CrudRepository
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
}
方法簽名 | 方法解釋 |
---|---|
<S extends T> S save(S entity); | 插入或者更新一個對象 |
<S extends T> Iterable<S> saveAll(Iterable<S> entities); | 插入或者更新多個對象,批量操作 |
Optional findById(ID id); | 根據id 查詢對象 |
boolean existsById(ID id); | 是否存在此id的記錄 |
Iterable findAll(); | 查詢所有的記錄 |
Iterable findAllById(Iterable ids); | 根據id集合查詢相關的記錄 |
long count(); | 統計數目 |
void deleteById(ID id); | 根據id進行删除 |
void delete(T entity); | 根據實體對象進行删除 |
void deleteAll(Iterable<? extends T> entities); | 根據實體對象集合進行删除,批量操作 |
void deleteAll(); | 删除所有的記錄 |
二.二.二. Crud 接口實作
在 repository 包下,建立 UserCrudRepository 接口。
public interface UserCrudRepository extends CrudRepository<User,Integer> {
}
二.二.三 Crud 測試
在 test測試目錄下,建立對應的測試類和測試方法,進行測試。
@SpringBootTest
@Log4j2
public class CrudRepositoryTests {
@Autowired
private UserService userService;
}
二.二.三.一 插入 save 方法
測試方法:
@Test
public void addTest(){
//1. 建構對象
User user=new User();
user.setName("歡歡");
user.setAge(22);
user.setSex("女");
user.setDescription("一個非常可愛的女孩紙");
//2. 添加方法
userService.addUser(user);
log.info("添加成功,{}",user);
}
省略接口方法,其對應的實作方法
添加Crud接口注入
@Autowired
private UserCrudRepository userCrudRepository;
@Override
public void addUser(User user) {
userCrudRepository.save(user);
}
控制台列印輸出:
二.二.三.二 更新 save 方法
測試方法
@Test
public void updateTest(){
//1. 建構對象
User user=new User();
user.setId(1); //id不存在,會添加
user.setName("歡歡");
user.setDescription("嶽澤霖最好的朋友");
//2. 修改方法
userService.updateUser(user);
log.info("修改成功,{}",user);
}
接口實作方法
@Override
public void updateUser(User user) {
userCrudRepository.save(user);
}
控制台列印輸出
先查詢,發現有,就更新,如果沒有的話,就插入。
二.二.三.三 删除 delete 方法
@Test
public void deleteTest(){
userService.deleteUser(1);
}
@Override
public void deleteUser(Integer id) {
userCrudRepository.deleteById(id);
}
資料庫裡面,沒有此條資料了。
二.二.三.四 批量更新資料 saveAll
重新執行一下, save() 方法,插入一條資料, id=2.
@Test
public void batchAddTest(){
//1. 建構對象
User user=new User();
user.setName("小歡歡");
user.setAge(22);
user.setSex("女");
user.setDescription("一個小壞蛋");
User user1=new User();
user1.setName("小澤霖");
user1.setAge(25);
user1.setSex("男");
user1.setDescription("一個大壞蛋");
//這是修改的操作,id=2已經存在這條記錄了。
User user2=new User();
user2.setName("嶽澤霖");
user2.setId(2);
user2.setAge(25);
user2.setSex("男性");
user2.setDescription("一個快樂的程式員");
//2. 放置到集合裡面
List<User> userList=new ArrayList<>();
userList.add(user);
userList.add(user1);
userList.add(user2);
userService.batchAddUser(userList);
}
接口實作方法:
@Override
public void batchAddUser(List<User> userList) {
userCrudRepository.saveAll(userList);
}
會插入前兩條,更新第三條記錄。
二.二.三.五 根據id 進行查詢 findById
@Test
public void findByIdTest(){
User user=userService.findById(3);
log.info(user);
}
@Override
public User findById(Integer id) {
Optional<User> optional= userCrudRepository.findById(id);
if(optional.isPresent()){
return optional.get();
}
return null;
}
二.二.三.六 查詢所有的資料 findAll
@Test
public void findAllTest(){
List<User> userList=userService.findAll();
userList.forEach(n->log.info(n));
}
@Override
public List<User> findAll() {
Iterable<User> iterator= userCrudRepository.findAll();
List<User> userList=new ArrayList<>();
//将 Iterable 轉換成 list
iterator.forEach(n->userList.add(n));
return userList;
}
二.二.三.七 根據id集合進行查詢 findAllById
@Test
public void findByIdsTest(){
List<Integer> ids= Arrays.asList(3,4,6);
List<User> userList=userService.findAllByIds(ids);
userList.forEach(n->log.info(n));
}
@Override
public List<User> findAllByIds(List<Integer> ids) {
Iterable<User> iterator= userCrudRepository.findAllById(ids);
List<User> userList=new ArrayList<>();
iterator.forEach(n->userList.add(n));
return userList;
}
二.二.三.八 查詢數目 count
@Test
public void countTest(){
Long count=userService.count();
log.info("總數目{}",count);
}
@Override
public Long count() {
return userCrudRepository.count();
}
這就是 Crud倉庫接口的基本用法。
二.三 PagingAndSorting 工廠接口
二.三.一 接口的相關定義
org.springframework.data.repository.PagingAndSortingRepository
分頁和排序的接口,繼承了 Crud的接口。
@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
Iterable findAll(Sort sort); | 排序 |
Page findAll(Pageable pageable); | 分頁 |
二.三.二 PagingAndSorting 接口實作
在 repository 包下,建立 UserPagingAndSortingRepository 接口
public interface UserPagingAndSortingRepository extends PagingAndSortingRepository<User,Integer> {
}
二.三.三 PagingAndSorting 測試
在 test目錄下,建立 PagingAndSortingRepositoryTests 測試類和測試方法。
多添加幾條資料,好便于分頁和排序。
二.三.三.一 排序,按照性别和年齡
@Autowired
private UserPagingAndSortingRepository userPagingAndSortingRepository;
@Test
public void sortTest(){
List<User> userList=userService.findAllOrderBySexAndAge();
userList.forEach(n->log.info(n));
}
業務接口方法
@Override
public List<User> findAllOrderBySexAndAge() {
Sort.Order sort1= Sort.Order.desc("sex");
Sort.Order sort2 = Sort.Order.asc("age");
Sort sort=Sort.by(sort1,sort2);
Iterable<User> userIterable=userPagingAndSortingRepository.findAll(sort);
List<User> userList=new ArrayList<>();
userIterable.forEach(n->userList.add(n));
return userList;
}
二.三.三.二 排序并分頁
@Test
public void pageTest(){
Page<User> page=userService.pageAll();
log.info("總頁數:{}",page.getTotalPages());
log.info("總的數目:{}",page.getTotalElements());
log.info("目前頁數:{}",page.getNumber()+1);
log.info("目前頁的數目:{}",page.getNumberOfElements());
List<User> userList= page.getContent();
userList.forEach(n->log.info(n));
}
@Override
public Page<User> pageAll() {
Sort sort=Sort.by(Sort.Direction.DESC,"id");
// 預設從0開始的
Pageable pageable= PageRequest.of(2-1, 4, sort);
Page<User> userPage=userPagingAndSortingRepository.findAll(pageable);
return userPage;
}
這就是 PageAndSorting倉庫接口的基本用法。
二.四 JpaRepository 工廠接口
二.四.一接口的相關定義
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {
}
繼承了分頁,還有一個查詢的接口。
List findAll(); | 查詢所有,傳回對象集合 |
List findAll(Sort sort); | 查詢所有,可排序,傳回對象集合 |
List findAllById(Iterable ids); | 根據id集合,查詢所有的記錄,傳回對象集合 |
<S extends T> List*lt;S> saveAll(Iterable<S> entities); | 批量儲存資料 |
void flush(); | 重新整理緩存 |
S saveAndFlush(S entity); | 儲存并重新整理 |
void deleteInBatch(Iterable entities); | 根據對象集合 |
void deleteAllInBatch(); | 批量删除所有 |
T getOne(ID id); | 根據id查詢目前對象,如果沒有的話,傳回null |
<S extends T> List<S> findAll(Example<S> example); | 根據條件,查詢資料,傳回對象集合 |
<S extends T> List<S> findAll(Example<S> example, Sort sort); | 根據條件,支援排序,查詢資料,傳回對象集合 |
二.四.二 Jpa 接口實作
在repository包下,建立 UserJpaRepository 接口。 通常使用的是這個Jpa的接口,會在這接口内添加相應的接口定義。
當接口的方法名稱定義符合相應的規範的時候,不用寫相應的實作,Jpa會幫我們自動比對生成相應的sql語句。
public interface UserJpaRepository extends JpaRepository<User,Integer> {
}
二.四.三 JPA測試
在test目錄下,建立JpaRepositoryTests 進行測試。
二.四.三.一 查詢全部
@Test
public void findAllTest(){
List<User> userList=userService.jpaFindAll();
userList.forEach(n->log.info(n));
}
業務方法
@Autowired
private UserJpaRepository userJpaRepository;
@Override
public List<User> jpaFindAll() {
return userJpaRepository.findAll();
}
不用往 UserJpaRepository 添加方法。
二.四.三.二 Example 查詢 (通常不用)
@Test
public void findByExampleTest(){
User user=new User();
user.setName("澤霖");
user.setAge(25);
user.setSex("男");
//1.建立比對器
ExampleMatcher exampleMatcher=ExampleMatcher.matching()
.withMatcher("sex",matcher -> matcher.contains())
.withMatcher("age",matcher -> matcher.exact())
.withMatcher("name",matcher ->matcher.contains());
//2. 生成Example 對象
Example example=Example.of(user,exampleMatcher);
//3. 進行查詢
List<User>userList=userService.findByExample(example);
userList.forEach(n->log.info(n));
}
業務實作接口
@Override
public List<User> findByExample(Example example) {
return userJpaRepository.findAll(example);
}
二.四.三.三 根據名稱進行查詢
@Test
public void findByNameTest(){
List<User> userList=userService.findByName("小歡歡");
userList.forEach(n->log.info(n));
}
@Override
public List<User> findByName(String name) {
return userJpaRepository.findByName(name);
}
需要往 UserJpaRepository 裡面添加方法,需要符合一定的規則
List<User> findByName(String name);
二.四.三.四 根據性别和年齡
@Test
public void findBySexAndAgeTest(){
List<User> userList=userService.findBySexAndAge("男",25);
userList.forEach(n->log.info(n));
}
@Override
public List<User> findBySexAndAge(String sex, Integer age) {
return userJpaRepository.findBySexAndAge(sex,age);
}
List<User> findBySexAndAge(String sex, Integer age);
二.四.三.五 根據性别查詢,年齡排序
@Test
public void findAllOrderByTest(){
List<User> userList=userService.findBySexOrderByAge("女");
userList.forEach(n->log.info(n));
}
@Override
public List<User> findBySexOrderByAge(String sex) {
return userJpaRepository.findBySexOrderByAgeDesc(sex);
}
List<User> findBySexOrderByAgeDesc(String sex);
二.四.三.六 使用原始SQL進行查詢部分字段
@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小歡歡");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}
@Override
public List<Map<String,Object>> findQueryByName(String name) {
return userJpaRepository.findQueryByName(name);
}
@Query(value="select id as id,name as name from user where name=:name",nativeQuery = true)
List<Map<String,Object>> findQueryByName(@Param("name") String name);
二.四.三.七 使用原始SQL進行查詢全部字段
@Test
public void findQueryNameTest(){
List<Map<String,Object>> userMapList=userService.findQueryByName("小歡歡");
for(Map<String,Object> map:userMapList){
log.info("id是:{},name是{}",map.get("id"),map.get("name"));
}
}
@Override
public List<User> jpaFindAllSql(String name) {
return userJpaRepository.findAllSql(name);
}
@Query(value="select * from user where name=:name",nativeQuery = true)
List<User> findAllSql(@Param("name")String name);
這就是 jpa的一些基本的用法。
二.五 JpaSpecificationExecutor 動态查詢接口
二.五.一 接口的相關定義
public interface JpaSpecificationExecutor<T> {
}
Optional findOne(@Nullable Specification spec); | 根據條件,查詢最多隻有一條記錄 |
List findAll(@Nullable Specification spec); | 根據條件,查詢多條記錄 |
Page findAll(@Nullable Specification spec, Pageable pageable); | 根據條件,分頁查詢,可包含排序 |
List findAll(@Nullable Specification spec, Sort sort); | 根據條件,排序查詢 |
long count(@Nullable Specification spec); | 查詢數目 |
二.五.二. JpaSpecificationExecutor實作
通常都是與 JpaRepository 一起使用的。
public interface UserSpecificationRepository extends JpaRepository<User, Integer>,
JpaSpecificationExecutor<User>{
}
二.五.三 Specification 動态查詢測試
@Test
public void nameAndSexAndDescTest(){
User user=new User();
user.setName("小歡歡1");
user.setSex("女");
user.setAge(27);
user.setDescription("小壞蛋");
List<User> userList=userService.findByNameSexAndDesc(user);
userList.forEach(n->log.info(n));
}
@Override
public List<User> findByNameSexAndDesc(User user) {
//1. 根據條件建立 Specification 對象資訊
Specification<User> specification=new Specification<User>(){
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
//1. 用于接收封裝的查詢對象
List<Predicate> predicateList = new ArrayList<>();
if(user!=null){
//1.如果name 不為空的話,對name 進行精确比對
if(!StringUtils.isEmpty(user.getName())){
Predicate namePredicate = criteriaBuilder.equal(root.get("name"), user.getName());
predicateList.add(namePredicate);
}
//2.如果sex 不為空的話,也是精确比對
if(!StringUtils.isEmpty(user.getSex())){
Predicate sexPredicate=criteriaBuilder.equal(root.get("sex"),user.getSex());
predicateList.add(sexPredicate);
}
//3.如果age不為空的話,就是 < 比對
if(!StringUtils.isEmpty(user.getAge())){
Predicate agePreDicate=criteriaBuilder.lt(root.get("age"),user.getAge());
predicateList.add(agePreDicate);
}
//4. 如果description 不為空的話,進行模糊比對
if(!StringUtils.isEmpty(user.getDescription())){
Predicate descPredicate=criteriaBuilder.like(root.get("description"),"%"+user.getDescription()
+"%");
predicateList.add(descPredicate);
}
}
return criteriaBuilder.and(predicateList.toArray(
new Predicate[predicateList.size()]
));
}
};
//傳入條件,也可以傳入分頁資訊。這兒就不舉例分頁了。
return userSpecificationRepository.findAll(specification);
}
如果将 user條件的屬性全部去掉,是一種sql查詢,sex去掉,又是一種sql查詢,會根據屬性的不同,動态的處理。
動态查詢,也可以傳入分頁和排序的相關資訊。
這個非常重要,需要重點掌握一下。
本章節的代碼放置在 github 上:
https://github.com/yuejianli/springboot/tree/develop/Jpa謝謝您的觀看,如果喜歡,請關注我,再次感謝 !!!