SpringBoot--實戰開發--整合Spring Data JPA(十一)

一、SpringData簡介

Spring Data是一個用于簡化數據庫訪問,并支持云服務的開源框架。其主要目標是使得對數據的訪問變得方便快捷。

  1. Spring Data JPA能干什么
      可以極大的簡化JPA的寫法,可以在幾乎不用寫實現的情況下,實現對數據的訪問和操作。除了CRUD外,還包括如分頁、排序等一些常用的功能。
  2. Spring Data JPA 有什么
      主要來看看Spring Data JPA提供的接口,也是Spring Data JPA的核心概念:
        1:Repository:最頂層的接口,是一個空的接口,目的是為了統一所有Repository的類型,且能讓組件掃描的時候自動識別。
        2:CrudRepository :是Repository的子接口,提供CRUD的功能
        3:PagingAndSortingRepository:是CrudRepository的子接口,添加分頁和排序的功能
        4:JpaRepository:是PagingAndSortingRepository的子接口,增加了一些實用的功能,比如:批量操作等。
        5:JpaSpecificationExecutor:用來做負責查詢的接口
        6:Specification:是Spring Data JPA提供的一個查詢規范,要做復雜的查詢,只需圍繞這個規范來設置查詢條件即可
  3. 特征
    1)強大的存儲庫和自定義對象映射抽象
    2)從存儲庫方法名稱中進行動態查詢導出
    3)實現域基類提供基本屬性
    4)支持透明審核(創建,最后更改)
    5)集成自定義存儲庫代碼的可能性
    6)Easy Spring通過JavaConfig和自定義XML命名空間進行集成
    7)與Spring MVC控制器進行高級集成

二、Maven依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

三、application.properties配置

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/iotManager?useUnicode=true&characterEncoding=UTF-8&useSSL=false&autoReconnect=true&failOverReadOnly=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=1234
#自動建表
spring.jpa.hibernate.ddl-auto=update
#設置數據庫方言
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
#打印sql
spring.jpa.show-sql=true

Jpa的配置后 jpa.hibernate.ddl-auto= update,在其他低版本的SpringBoot中也有使用spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop 這種配置的,具體根據版本而定。該配置的主要作用是:自動創建、更新、驗證數據庫結構
1、create:每次加載hibernate時都會刪除上一次的生成的表,然后根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因(一般只會在第一次創建時使用)
2、create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除
3、update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以后加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器后,表結構是不會被馬上建立起來的,是要等應用第一次運行起來后才會
4、validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值。

四、創建實體

創建一個User類,配置好上面的信息后,啟動項目,對應的數據庫就會自動生成對應的表結構。@Table、@Entity、@Id等注解是jpa的相關知識。

@Table(name = "t_user")
@Entity
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 設為主鍵 唯一不能為空
     * nullable 是否可以為空
     * unique 唯一
     * @GeneratedValue
     * 就是為一個實體生成一個唯一標識的主鍵
     * (JPA要求每一個實體Entity,必須有且只有一個主鍵)
     * @GeneratedValue提供了主鍵的生成策略。
     * 。@GeneratedValue注解有兩個屬性,分別是strategy和generator,
     * 其中generator屬性的值是一個字符串,默認為"",其聲明了主鍵生成器的名稱
     * (對應于同名的主鍵生成器@SequenceGenerator和@TableGenerator)。
     *  JPA為開發人員提供了四種主鍵生成策略,其被定義在枚舉類GenerationType中,
     *  包括GenerationType.TABLE,GenerationType.SEQUENCE,
     *  GenerationType.IDENTITY和GenerationType.AUTO。
     *  這里生成策略為自增長
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long id;
    @Column(nullable = false, unique = true)
    private String userName;
    @Column(nullable = false)
    private String passWord;
    @Column(nullable = false, unique = true)
    private String email;
    @Column(nullable = true, unique = true)
    private String nickName;
    @Column(nullable = false)
    private String regTime;

五、創建Dao

方法的名稱要遵循 findBy + 屬性名(首字母大寫) + 查詢條件(首字母大寫 Is Equals)
findByNameLike(String name)
findByName(String name)
findByNameAndAge(String name, Integer age)
findByNameOrAddress(String name) 等...

@Repository
public interface UserRepository  extends JpaRepository<User,Long> {

    /**
     * 根據年紀查詢用戶
     * @param age
     * @return
     */
    User findByAge(Integer age);

    /**
     * 根據年紀和姓名查詢
     * @param name
     * @param age
     * @return
     */
    User findByNameAndAge(String name, Integer age);

    /**
     * 對于復雜查詢可以使用@Query 編寫sql
     * @param name
     * @return
     */
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
}

該Dao成繼承了JpaRepository接口,指定了需要操作的實體對象和實體對象的主鍵類型,通過查看JpaRepository接口源碼可以看到,里面已經封裝了創建(save)、更新(save)、刪除(delete)、查詢(findAll、findOne)等基本操作的函數,使用起來非常方便了,但是還是會存在一些復雜的sql,spring-data-jpa還提供了一個非常方便的方式,通過實體屬性來命名方法,它會根據命名來創建sql查詢相關數據,對應更加復雜的語句,還可以用直接寫sql來完成。

繼承了JpaRepository就相當于有了下面的數據訪問操作方法:

@NoRepositoryBean
public interface JpaRepository<T, ID extends Serializable> extends PagingAndSortingRepository<T, ID> {
    List<T> findAll();
    List<T> findAll(Sort var1);
    List<T> findAll(Iterable<ID> var1);
    <S extends T> List<S> save(Iterable<S> var1);
    void flush();
    <S extends T> S saveAndFlush(S var1);
    void deleteInBatch(Iterable<T> var1);
    void deleteAllInBatch();
    T getOne(ID var1);
}

六、單元測試

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringdataApplicationTests {

    @Autowired
    private UserRepository userRepository;

    /**
     * 新增用戶
     * @throws Exception
     */
    @Test
    public void testAddUser() throws Exception {
        User user = new User();
        user.setName("zhangsan");
        user.setAge(12);
        userRepository.save(user);

        User user2 = new User();
        user2.setName("lishi");
        user2.setAge(22);
        userRepository.save(user2);
    }

    /**
     * 刪除用戶(根據對象刪除時,必須要有ID屬性)
     * @throws Exception
     */
    @Test
    public void testDelUser() throws Exception {
        User user = new User();
        user.setId(1L);
        user.setName("zhangsan");
        user.setAge(12);
        userRepository.delete(user);
    }

    /**
     * 修改用戶信息
     * @throws Exception
     */
    @Test
    public void testUpdUser() throws Exception {
        User user = new User();
        user.setId(2L);
        user.setName("zhangsan11");
        user.setAge(122);
        userRepository.save(user);
    }

    /**
     * 查詢用戶
     * @throws Exception
     */
    @Test
    public void testQueryUser() throws Exception {
        User user = userRepository.findByAge(22);
        System.out.println(user.getName());

        User user2 = userRepository.findByNameAndAge("lishi", 22);
        System.out.println(user2.getName());

        User user3 = userRepository.findUser("zhangsan11");
        System.out.println(user3.getName());
    }

    /**
     * 查詢所有用戶
     * @throws Exception
     */
    @Test
    public void testQueryUserList() throws Exception {
        List<User> list = userRepository.findAll();
        for (User user : list) {
            System.out.println(user.getName());
        }
    }
}

如果沒有表,將會自動生成。

七、主要接口

CrudReposiroty : 繼承了Repository
Crud主要是添加了對數據的增刪改查的方法
PagingAndSortingRepository: 繼承了CrudRepository
JPARepository: 繼承了PagingAndSortingRepository接口
JpaSpecificationExecutor: 這個接口單獨存在,沒有繼承以上說的接口
主要提供了多條件查詢的支持,并且可以在查詢中添加分頁和排序。
因為這個接口單獨存在,因此需要配合以上說的接口使用,如:

/**
 * JpaSpecificationExecutor是單獨存在的,需要配合這JpaRepository一起使用
 */
@Repository
public interface UserJpaSpecificationExecutor extends JpaSpecificationExecutor<User>, JpaRepository<User, Integer> {
}

八、JPA的查詢方法

基于@Query注解的查詢和更新:

/**
     * SQL nativeQuery的值是true 執行的時候不用再轉化
     * @param name
     * @return
     */
    @Query(value = "SELECT * FROM table_user WHERE name = ?1", nativeQuery = true)
    List<User> findByUsernameSQL(String name);

基于HQL:

 /**
     * 基于HQL
     * @param name
     * @param id
     * @return
     */
    @Query("Update User set name = ?1 WHERE id = ?2")
    @Modifying
    int updateNameAndId(String name, Integer id);

在JPA中有三種方式可以進行數據的查詢(1,方法命名查詢 2,@NamedQuery查詢 3,@Query查詢),
假設有一張表叫PERSON,字段:ID(INT),NAME(VARCHAR),AGE(INT),ADDRESS(VARCHAR).
實體類:id(integer),name(String),age(integer),address(String)

  1. 第一種:方法命名查詢

1) 使用findBy,And關鍵字

public interface PersonRepository extends Repository<Person, Integer> {
       /*
    * 通過地址進行查詢,參數為address,
    * 相當于JPQL:select p from Person p where p.address=?1
    * */
    List<Person> findByAddress(String address);
       /*
    * 通過地址和名字進行查詢,參數為name,address
    * 相當于JPQL:select p from Person p where p.name=?1 and address=?2
    * */
    Person findByNameAndAddress(String name,String address);
}

從代碼可以看出,使用findBy,And這樣的關鍵字,其中的findBy可以用find,getBy,query,read來進行代替。
而And就相當于sql語句中的and。

2) 用關鍵字限制結果數量,用top和first來實現

*
*查詢符合條件的前十條記錄
*/
List<Person> findFirst10ByName(String name)
/*
*查詢符合條件的前30條記錄
*/
List<Person> findTop30ByName(String name);
  1. 第二種:@NamedQuery查詢
    Spring Data JPA 支持@NameQuery來定義查詢方法,即一個名稱映射一個查詢語句(要在實體類上寫,不是接口里寫):
@Entity
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1")
public class Person{
}

這樣子就重新定義了findByName這個方法了。
如果要將多個方法都進行重新定義,可以使用@NameQueries標簽,示例如下:

@Entity
@NamedQueries({
@NamedQuery(name="Person.findByName",
query="select p from Person p where p.name=?1"),
@NamedQuery(name = "Person.withNameAndAddressNamedQuery",
query = "select p from Person p where p.name=?1 and address=?2")
})
public class Person{

}

這個時候,接口里定義的findByName方法就是上面的方法了,不再是方法命名查詢的方法了。

  1. 第三種:@Query查詢
    Spring Data JPA 支持@Query來定義查詢方法,使用方法是將@Query寫在接口的方法上面:
public interface PersonRepository extends Repository<Person, Integer> {
     @Query("select p from Person p where p.name=?1 and p.address=?2")
    Person withNameAndAddressQuery(String name,String address);
   
}

這里的參數是根據索引號來進行查詢的。
當然我們也是可以根據名稱來進行匹配,然后進行查詢的,示例如下:

public interface PersonRepository extends Repository<Person, Integer> {   
@Query("select p from Person p where p.name= :name and p.address= :address")
Person withNameAndAddressQuery(@Param("name")String name,@Param("address")String address);
}

Spring Data JPA支持使用@Modifying和@Query注解組合來進行更新查詢,示例如下:

public interface PersonRepository extends Repository<Person, Integer> {

@Modifying
@Transcational
@Query("update Person p set p.name=?1 ")
int setName(String name);
}

int表示的是更新語句所影響的行數。

  1. 排序查詢
userRepository.findAll(new Sort(new Sort.Order(Sort.Direction.ASC,"id")));
  1. 分頁查詢
//分頁查詢全部,返回封裝了分頁信息,注意: 0為第一頁,1為第2頁
Page<User> pageInfo = userRepository.findAll(PageRequest.of(0, 3, Sort.Direction.ASC, "id"));
log.info("總頁數" + pageInfo.getTotalPages());
log.info("頁大小" + pageInfo.getSize());
log.info("當前頁" + pageInfo.getPageable().getPageNumber());
log.info("總記錄數" + pageInfo.getTotalElements());
//內容
List<User> userList = pageInfo.getContent();

  1. example查詢
User user = new User();
user.setUsername("admin");
Example<User> example = Example.of(user);
List<User> list = userRepository.findAll(example);
log.info(list);
  1. getOne方法
    需添加配置:
#延遲加載
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

運行:

# 只可讀取存在的數據,不存在拋異常
  User user=userRepository.getOne(1L);
  1. findOne方法
User user=new User();
user.setId(3L);
Example example=Example.of(user);
Optional<User> optionalUser=userRepository.findOne(example);
log.info(optionalUser.get().getUsername());

說明:如果不存在,會報:No value present異常

User user = new User();
user.setId(3L);
Example example = Example.of(user);
Optional<User> optionalUser = userRepository.findOne(example);
//判斷是否存在
if (optionalUser.isPresent()) {
    log.info(optionalUser.get().getUsername());
}
或:
//存在即返回, 無則提供默認值
User user = new User();
user.setId(id);
Example<User> userExample = Example.of(user);
return userRepository.findOne(userExample).orElse(null); 

在查詢時,通常需要同時根據多個屬性進行查詢,且查詢的條件也格式各樣(大于某個值、在某個范圍等等),Spring Data JPA 為此提供了一些表達條件查詢的關鍵字,大致如下:
And --- 等價于SQL 中的and 關鍵字,比如findByUsernameAndPassword(String user, Striang pwd);
Or --- 等價于SQL 中的or 關鍵字,比如findByUsernameOrAddress(String user, String addr);
Between --- 等價于SQL 中的between 關鍵字,比如findBySalaryBetween(int max, int min);
LessThan --- 等價于SQL 中的"<",比如findBySalaryLessThan(int max);
lGreaterThan --- 等價于SQL 中的">",比如findBySalaryGreaterThan(int min);
IsNull --- 等價于SQL 中的"is null",比如findByUsernameIsNull();
IsNotNull --- 等價于SQL 中的"is not null",比如findByUsernameIsNotNull();
NotNull --- 與IsNotNull 等價;
Like --- 等價于SQL 中的"like",比如findByUsernameLike(String user);
NotLike --- 等價于SQL 中的"not like",比如findByUsernameNotLike(String user);
OrderBy --- 等價于SQL 中的"order by",比如findByUsernameOrderBySalaryAsc(String user);
Not --- 等價于SQL 中的"! =",比如findByUsernameNot(String user);
In --- 等價于SQL 中的"in",比如findByUsernameIn(Collection<String> userList) ,方法的參數可以
是Collection 類型,也可以是數組或者不定長參數;
NotIn --- 等價于SQL 中的"not in",比如findByUsernameNotIn(Collection<String> userList) ,方法的參數可以是Collection 類型,也可以是數組或者不定長參數;

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容