SpringBoot--實(shí)戰(zhàn)開(kāi)發(fā)--整合Spring Data JPA(十一)

一、SpringData簡(jiǎn)介

Spring Data是一個(gè)用于簡(jiǎn)化數(shù)據(jù)庫(kù)訪(fǎng)問(wèn),并支持云服務(wù)的開(kāi)源框架。其主要目標(biāo)是使得對(duì)數(shù)據(jù)的訪(fǎng)問(wèn)變得方便快捷。

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

二、Maven依賴(lài)

<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
#自動(dòng)建表
spring.jpa.hibernate.ddl-auto=update
#設(shè)置數(shù)據(jù)庫(kù)方言
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 這種配置的,具體根據(jù)版本而定。該配置的主要作用是:自動(dòng)創(chuàng)建、更新、驗(yàn)證數(shù)據(jù)庫(kù)結(jié)構(gòu)
1、create:每次加載hibernate時(shí)都會(huì)刪除上一次的生成的表,然后根據(jù)你的model類(lèi)再重新來(lái)生成新表,哪怕兩次沒(méi)有任何改變也要這樣執(zhí)行,這就是導(dǎo)致數(shù)據(jù)庫(kù)表數(shù)據(jù)丟失的一個(gè)重要原因(一般只會(huì)在第一次創(chuàng)建時(shí)使用)
2、create-drop:每次加載hibernate時(shí)根據(jù)model類(lèi)生成表,但是sessionFactory一關(guān)閉,表就自動(dòng)刪除
3、update:最常用的屬性,第一次加載hibernate時(shí)根據(jù)model類(lèi)會(huì)自動(dòng)建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫(kù)),以后加載hibernate時(shí)根據(jù)model類(lèi)自動(dòng)更新表結(jié)構(gòu),即使表結(jié)構(gòu)改變了但表中的行仍然存在不會(huì)刪除以前的行。要注意的是當(dāng)部署到服務(wù)器后,表結(jié)構(gòu)是不會(huì)被馬上建立起來(lái)的,是要等應(yīng)用第一次運(yùn)行起來(lái)后才會(huì)
4、validate:每次加載hibernate時(shí),驗(yàn)證創(chuàng)建數(shù)據(jù)庫(kù)表結(jié)構(gòu),只會(huì)和數(shù)據(jù)庫(kù)中的表進(jìn)行比較,不會(huì)創(chuàng)建新表,但是會(huì)插入新值。

四、創(chuàng)建實(shí)體

創(chuàng)建一個(gè)User類(lèi),配置好上面的信息后,啟動(dòng)項(xiàng)目,對(duì)應(yīng)的數(shù)據(jù)庫(kù)就會(huì)自動(dòng)生成對(duì)應(yīng)的表結(jié)構(gòu)。@Table、@Entity、@Id等注解是jpa的相關(guān)知識(shí)。

@Table(name = "t_user")
@Entity
@Data
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 設(shè)為主鍵 唯一不能為空
     * nullable 是否可以為空
     * unique 唯一
     * @GeneratedValue
     * 就是為一個(gè)實(shí)體生成一個(gè)唯一標(biāo)識(shí)的主鍵
     * (JPA要求每一個(gè)實(shí)體Entity,必須有且只有一個(gè)主鍵)
     * @GeneratedValue提供了主鍵的生成策略。
     * 。@GeneratedValue注解有兩個(gè)屬性,分別是strategy和generator,
     * 其中g(shù)enerator屬性的值是一個(gè)字符串,默認(rèn)為"",其聲明了主鍵生成器的名稱(chēng)
     * (對(duì)應(yīng)于同名的主鍵生成器@SequenceGenerator和@TableGenerator)。
     *  JPA為開(kāi)發(fā)人員提供了四種主鍵生成策略,其被定義在枚舉類(lèi)GenerationType中,
     *  包括GenerationType.TABLE,GenerationType.SEQUENCE,
     *  GenerationType.IDENTITY和GenerationType.AUTO。
     *  這里生成策略為自增長(zhǎng)
     */
    @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;

五、創(chuàng)建Dao

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

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

    /**
     * 根據(jù)年紀(jì)查詢(xún)用戶(hù)
     * @param age
     * @return
     */
    User findByAge(Integer age);

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

    /**
     * 對(duì)于復(fù)雜查詢(xún)可以使用@Query 編寫(xiě)sql
     * @param name
     * @return
     */
    @Query("from User u where u.name=:name")
    User findUser(@Param("name") String name);
}

該Dao成繼承了JpaRepository接口,指定了需要操作的實(shí)體對(duì)象和實(shí)體對(duì)象的主鍵類(lèi)型,通過(guò)查看JpaRepository接口源碼可以看到,里面已經(jīng)封裝了創(chuàng)建(save)、更新(save)、刪除(delete)、查詢(xún)(findAll、findOne)等基本操作的函數(shù),使用起來(lái)非常方便了,但是還是會(huì)存在一些復(fù)雜的sql,spring-data-jpa還提供了一個(gè)非常方便的方式,通過(guò)實(shí)體屬性來(lái)命名方法,它會(huì)根據(jù)命名來(lái)創(chuàng)建sql查詢(xún)相關(guān)數(shù)據(jù),對(duì)應(yīng)更加復(fù)雜的語(yǔ)句,還可以用直接寫(xiě)sql來(lái)完成。

繼承了JpaRepository就相當(dāng)于有了下面的數(shù)據(jù)訪(fǎng)問(wèn)操作方法:

@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);
}

六、單元測(cè)試

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

    @Autowired
    private UserRepository userRepository;

    /**
     * 新增用戶(hù)
     * @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);
    }

    /**
     * 刪除用戶(hù)(根據(jù)對(duì)象刪除時(shí),必須要有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);
    }

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

    /**
     * 查詢(xún)用戶(hù)
     * @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());
    }

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

如果沒(méi)有表,將會(huì)自動(dòng)生成。

七、主要接口

CrudReposiroty : 繼承了Repository
Crud主要是添加了對(duì)數(shù)據(jù)的增刪改查的方法
PagingAndSortingRepository: 繼承了CrudRepository
JPARepository: 繼承了PagingAndSortingRepository接口
JpaSpecificationExecutor: 這個(gè)接口單獨(dú)存在,沒(méi)有繼承以上說(shuō)的接口
主要提供了多條件查詢(xún)的支持,并且可以在查詢(xún)中添加分頁(yè)和排序。
因?yàn)檫@個(gè)接口單獨(dú)存在,因此需要配合以上說(shuō)的接口使用,如:

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

八、JPA的查詢(xún)方法

基于@Query注解的查詢(xún)和更新:

/**
     * SQL nativeQuery的值是true 執(zhí)行的時(shí)候不用再轉(zhuǎn)化
     * @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中有三種方式可以進(jìn)行數(shù)據(jù)的查詢(xún)(1,方法命名查詢(xún) 2,@NamedQuery查詢(xún) 3,@Query查詢(xún)),
假設(shè)有一張表叫PERSON,字段:ID(INT),NAME(VARCHAR),AGE(INT),ADDRESS(VARCHAR).
實(shí)體類(lèi):id(integer),name(String),age(integer),address(String)

  1. 第一種:方法命名查詢(xún)

1) 使用findBy,And關(guān)鍵字

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

從代碼可以看出,使用findBy,And這樣的關(guān)鍵字,其中的findBy可以用find,getBy,query,read來(lái)進(jìn)行代替。
而And就相當(dāng)于sql語(yǔ)句中的and。

2) 用關(guān)鍵字限制結(jié)果數(shù)量,用top和first來(lái)實(shí)現(xiàn)

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

這樣子就重新定義了findByName這個(gè)方法了。
如果要將多個(gè)方法都進(jìn)行重新定義,可以使用@NameQueries標(biāo)簽,示例如下:

@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{

}

這個(gè)時(shí)候,接口里定義的findByName方法就是上面的方法了,不再是方法命名查詢(xún)的方法了。

  1. 第三種:@Query查詢(xún)
    Spring Data JPA 支持@Query來(lái)定義查詢(xún)方法,使用方法是將@Query寫(xiě)在接口的方法上面:
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);
   
}

這里的參數(shù)是根據(jù)索引號(hào)來(lái)進(jìn)行查詢(xún)的。
當(dāng)然我們也是可以根據(jù)名稱(chēng)來(lái)進(jìn)行匹配,然后進(jìn)行查詢(xún)的,示例如下:

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注解組合來(lái)進(jìn)行更新查詢(xún),示例如下:

public interface PersonRepository extends Repository<Person, Integer> {

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

int表示的是更新語(yǔ)句所影響的行數(shù)。

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

  1. example查詢(xún)
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

運(yùn)行:

# 只可讀取存在的數(shù)據(jù),不存在拋異常
  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());

說(shuō)明:如果不存在,會(huì)報(bào):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());
}
或:
//存在即返回, 無(wú)則提供默認(rèn)值
User user = new User();
user.setId(id);
Example<User> userExample = Example.of(user);
return userRepository.findOne(userExample).orElse(null); 

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容