SpringBoot中使用SpringDataJPA

SpringDataJPA的使用

JPA是什麼?

JPA(Java Persistence API)是Sun官方提出的Java持久化規範. 爲Java開發人員提供了一種對象/關聯映射工具來管理Java應用中的關係數據. 它的出現是爲了簡化現有的持久化開發工做和整合ORM技術. 結束各個ORM框架各自爲營的局面.java

JPA僅僅是一套規範,不是一套產品, 也就是說Hibernate, TopLink等是實現了JPA規範的一套產品.mysql

Spring Data JPA

Spring Data JPA是Spring基於ORM框架、JPA規範的基礎上封裝的一套JPA應用框架,是基於Hibernate之上構建的JPA使用解決方案,用極簡的代碼實現了對數據庫的訪問和操做,包括了增、刪、改、查等在內的經常使用功能.git

實踐
  1. 引入依賴
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
  1. 添加配置文件
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://39.105.167.131:3306/smile_boot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: Nrblwbb7$

  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: create
        dialect: org.hibernate.dialect.MySQL5InnoDBDialect
        format_sql: true
    show-sql: true

hibernate.hbm2ddl.auto 參數的做用主要用於:自動建立、更新、驗證數據庫表結構,有四個值。程序員

  • create:每次加載 Hibernate 時都會刪除上一次生成的表,而後根據 model 類再從新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是致使數據庫表數據丟失的一個重要緣由。
  • create-drop:每次加載 Hibernate 時根據 model 類生成表,可是 sessionFactory 一關閉,表就自動刪除。
  • update:最經常使用的屬性,第一次加載 Hibernate 時根據 model 類會自動創建起表的結構(前提是先創建好數據庫),之後加載 Hibernate 時根據 model 類自動更新表結構,即便表結構改變了,但表中的行仍然存在,不會刪除之前的行。要注意的是當部署到服務器後,表結構是不會被立刻創建起來的,是要等應用第一次運行起來後纔會。
  • validate :每次加載 Hibernate 時,驗證建立數據庫表結構,只會和數據庫中的表進行比較,不會建立新表,可是會插入新值。

配置文件中:github

  • dialect 主要是指定生成表名的存儲引擎爲 InnoDB
  • show-sql 是否在日誌中打印出自動生成的 SQL,方便調試的時候查看
  1. 編寫代碼

實體類:spring

@Entity
public class User {

    @Id
    @GeneratedValue
    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;

    public User(String userName, String passWord, String email, String nickName, String regTime) {
        this.userName = userName;
        this.passWord = passWord;
        this.email = email;
        this.nickName = nickName;
        this.regTime = regTime;
    }

    public User() {
    }

    // getter and setter
}

註解:sql

  • @Entity(name="EntityName") 必須,用來標註一個數據庫對應的實體,數據庫中建立的表名默認和類名一致。其中,name 爲可選,對應數據庫中一個表,使用此註解標記 Pojo 是一個 JPA 實體。
  • @Table(name="",catalog="",schema="") 可選,用來標註一個數據庫對應的實體,數據庫中建立的表名默認和類名一致。一般和 @Entity 配合使用,只能標註在實體的 class 定義處,表示實體對應的數據庫表的信息。
  • @Id 必須,@Id 定義了映射到數據庫表的主鍵的屬性,一個實體只能有一個屬性被映射爲主鍵。
  • @GeneratedValue(strategy=GenerationType,generator="") 可選,strategy: 表示主鍵生成策略,有 AUTO、INDENTITY、SEQUENCE 和 TABLE 4 種,分別表示讓 ORM 框架自動選擇,generator: 表示主鍵生成器的名稱。
  • @Column(name = "user_code", nullable = false, length=32) 可選,@Column 描述了數據庫表中該字段的詳細定義,這對於根據 JPA 註解生成數據庫表結構的工具。name: 表示數據庫表中該字段的名稱,默認情形屬性名稱一致;nullable: 表示該字段是否容許爲 null,默認爲 true;unique: 表示該字段是不是惟一標識,默認爲 false;length: 表示該字段的大小,僅對 String 類型的字段有效。
  • @Transient可選,@Transient 表示該屬性並不是一個到數據庫表的字段的映射,ORM 框架將忽略該屬性。
  • @Enumerated 可選,使用枚舉的時候,咱們但願數據庫中存儲的是枚舉對應的 String 類型,而不是枚舉的索引值,須要在屬性上面添加 @Enumerated(EnumType.STRING) 註解。

基本都是hibernate的註解 4. Repository構建數據庫

public interface UserRepository extends JpaRepository<User,Long> {

    User findByUserNameOrEmail(String userName, String email);

    User findByUserName(String userName);

}

由於是繼承,因此父類有的方法所有繼承,能夠查看父類的源碼來看看有哪些方法.springboot

  1. 測試
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRepositoryTest {

    @Resource
    private UserRepository userRepository;

    @Test
    public void test(){
        Date data = new Date();
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = dateFormat.format(data);

        userRepository.save(new User("aa","aa123456","aa@126.com","aa",formattedDate));
        userRepository.save(new User("bb","bb123456","bb@126.com","bb",formattedDate));
        userRepository.save(new User("cc","cc123456","cc@126.com","cc",formattedDate));

        Assert.assertEquals(3,userRepository.findAll().size());
        Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","bb@126.com").getNickName());
        userRepository.delete(userRepository.findByUserName("aa"));
    }
}
查詢語句

在jpa中查詢分爲兩類,一類是繼承了父類的方法的基本查詢,另外一類是自定義查詢.服務器

基本查詢

repository

圖中黑色就是自定義的,灰色的就是從父類繼承的.

自定義查詢

Spring Data JPA 能夠根據接口方法名來實現數據庫操做,主要的語法是 findXXBy、readAXXBy、queryXXBy、countXXBy、getXXBy 後面跟屬性名稱,利用這個功能僅須要在定義的 Repository 中添加對應的方法名便可,使用時 Spring Boot 會自動幫咱們實現.

根據用戶名查詢用戶:

User findByUserName(String userName);

也能夠加一些關鍵字 And、or:

User findByUserNameOrEmail(String username,String email);

修改、刪除、統計也是相似語法:

Long deleteById(Long id);
Long countByUserName(String userName)

基本上 SQL 體系中的關鍵詞均可以使用,如 LIKE 、IgnoreCase、OrderBy:

List<User> findByEmailLike(String email);

User findByUserNameIgnoreCase(String userName);

List<User> findByUserNameOrderByEmailDesc(String email);

關鍵字的使用和生產SQL:

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age ⇐ ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
After findByStartDateAfter … where x.startDate > ?1
Before findByStartDateBefore … where x.startDate < ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
NotLike findByFirstnameNotLike … where x.firstname not like ?1
StartingWith findByFirstnameStartingWith … where x.firstname like ?1 (parameter bound with appended %)
EndingWith findByFirstnameEndingWith … where x.firstname like ?1 (parameter bound with prepended %)
Containing findByFirstnameContaining … where x.firstname like ?1 (parameter bound wrapped in %)
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
Not findByLastnameNot … where x.lastname <> ?1
In findByAgeIn(Collection ages) … where x.age in ?1
NotIn findByAgeNotIn(Collection age) … where x.age not in ?1
TRUE findByActiveTrue() … where x.active = true
FALSE findByActiveFalse() … where x.active = false
IgnoreCase findByFirstnameIgnoreCase … where UPPER(x.firstame) = UPPER(?1)
自定義SQL查詢
  1. 在UserRepository中增長方法:
/**
     * @Author Smith
     * @Description 自定義Sql查詢.(這個原本是HQL的寫法,個人運行不了,改爲了本地的SQL)
     * @Date 10:18 2019/1/24
     * @Param 
     * @return org.springframework.data.domain.Page<com.jpa.springdatajpa.model.User>
     **/
    @Query(value = "select * from user",nativeQuery = true)
    Page<User> findALL(Pageable pageable);

    /**
     * @Author Smith
     * @Description 原生SQL的寫法,?1表示方法參數中的順序
     * @Date 10:20 2019/1/24
     * @Param 
     * @return org.springframework.data.domain.Page<com.jpa.springdatajpa.model.User>
     **/
    @Query(value = "select * from user where nick_name = ?1",nativeQuery = true)
    Page<User> findByNickName(String nickName, Pageable pageable);

    /**
     * @Author Smith
     * @Description 修改,添加事務的支持
     * @Date 10:21 2019/1/24
     * @Param 
     * @return int
     **/
    @Transactional(timeout = 10)
    @Modifying
    @Query("update User set userName = ?1 where id = ?2")
    int modifyById(String  userName, Long id);

    /**
     * @Author Smith
     * @Description 刪除
     * @Date 10:22 2019/1/24
     * @Param 
     * @return void
     **/
    @Transactional
    @Modifying
    @Query("delete from User where id = ?1")
    @Override
    void deleteById(Long id);

測試

@Test
public void testFindALL(){
    int page = 1;
    int size = 1;
    Sort sort = new Sort(Sort.Direction.DESC,"id");
    Pageable pageable = PageRequest.of(page,size,sort);
    Page<User> all = userRepository.findALL(pageable);
    Assert.assertEquals(1,all.getContent().size());
    Assert.assertEquals(2,all.getTotalPages());
}

@Test
public void testFindByNickName(){
    int page = 0;
    int size = 1;
    Sort sort = new Sort(Sort.Direction.DESC,"id");
    Pageable pageable = PageRequest.of(page,size,sort);
    Page<User> all = userRepository.findByNickName("bb",pageable);
    Assert.assertEquals(1,all.getContent().size());
    Assert.assertEquals(1,all.getTotalPages());
}

須要注意的是Pageable分頁的使用,其他的基本沒什麼須要注意的.

限制查詢

只須要查詢前 N 個元素,或者只取前一個實體。

User findFirstByOrderByNickNameAsc();

User findTopByOrderByIdDesc();

Page<User> queryFirst10ByNickName(String nickName, Pageable pageable);

List<User> findFirst10ByNickName(String nickName, Sort sort);

List<User> findTop10ByNickName(String nickName, Pageable pageable);

這沒有作測試

複雜查詢

在某些狀況下查詢條件不少,須要不斷拼接屬性,方法名會顯得很長,這個時候就要使用JpaSpecificationExecutor 接口了.

概念:

  • Root<T> root,表明了能夠查詢和操做的實體對象的根,開一個經過 get("屬性名") 來獲取對應的值。
  • CriteriaQuery query,表明一個 specific 的頂層查詢對象,它包含着查詢的各個部分,好比 select 、from、where、group by、order by 等。
  • CriteriaBuilder cb,來構建 CritiaQuery 的構建器對象,其實就至關於條件或者是條件組合,並以 Predicate 的形式返回。

實體:

@Entity
public class UserDetail {

    @Id
    @GeneratedValue
    private Long id;
    @Column(nullable = false, unique = true)
    private Long userId;
    private Integer age;
    private String realName;
    private String status;
    private String hobby;
    private String introduction;
    private String lastLoginIp;
    
    // getter/setter

repository:

public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>,
        JpaRepository<UserDetail, Long> {
}

service和serviceImpl

public interface UserDetailService {

    public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable);


}

@Service
public class UserDetailServiceImpl implements UserDetailService {

    @Resource
    private UserDetailRepository userDetailRepository;

    @Override
    public Page<UserDetail> findByCondition(UserDetailParam detailParam, Pageable pageable){

        return userDetailRepository.findAll((root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            //equal 示例
            if (!StringUtils.isNullOrEmpty(detailParam.getIntroduction())){
                predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));
            }
            //like 示例
            if (!StringUtils.isNullOrEmpty(detailParam.getRealName())){
                predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));
            }
            //between 示例
            if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null) {
                Predicate agePredicate = cb.between(root.get("age"), detailParam.getMinAge(), detailParam.getMaxAge());
                predicates.add(agePredicate);
            }
            //greaterThan 大於等於示例
            /*if (detailParam.getMinAge()!=null){
                predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
            }*/
            return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
        }, pageable);

    }

}

測試:

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

    @Resource
    private UserDetailService userDetailService;

    @Test
    public void testFindByCondition()  {
        int page=0,size=10;
        Sort sort = new Sort(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(page, size, sort);
        UserDetailParam param=new UserDetailParam();
        param.setIntroduction("程序員");
        param.setMinAge(10);
        param.setMaxAge(30);
        Page<UserDetail> page1=userDetailService.findByCondition(param,pageable);
        for (UserDetail userDetail:page1){
            System.out.println("userDetail: "+userDetail.toString());
        }
    }

}

在我本地測試失敗了,報了個mysql的混合字符集的錯,找了會發現使用的方言的問題,能夠從數據庫看到生成表的排序規則是latin1_swedish_ci,因此報錯.解決方案是:新建一個配置類:

public class MysqlConfig extends MySQL5Dialect {

    @Override
    public String getTableTypeString() {
        return " ENGINE=InnoDB DEFAULT CHARSET=utf8";
    }
}

在配置文件中進行修改

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://39.105.167.131:3306/smile_boot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: Nrblwbb7$

  jpa:
    properties:
      hibernate:
        hbm2ddl:
          auto: create
        # 注意這行,爲本身的配置文件的路徑 
        dialect: com.jpa.springdatajpa.config.MysqlConfig
        format_sql: true
    show-sql: true

這樣生成的表就是utf8_general_ci了,問題就解決了.

多表查詢

新建實體類:

public interface UserInfo {
	String getUserName();
	String getEmail();
	String getHobby();
	String getIntroduction();
}

repository方法:

@Query("select u.userName as userName, u.email as email, d.introduction as introduction , d.hobby as hobby from User u , UserDetail d " +
            "where u.id=d.userId  and  d.hobby = ?1 ")
    List<UserInfo> findUserInfo(String hobby);

測試:

@Test
public void testUserInfo()  {
    List<UserInfo> userInfos=userDetailRepository.findUserInfo("釣魚");
    for (UserInfo userInfo:userInfos){
        System.out.println("userInfo: "+userInfo.getUserName()+"-"+userInfo.getEmail()+"-"+userInfo.getHobby()+"-"+userInfo.getIntroduction());
    }
}

上面就是關於springdatajpa在springboot中的使用了. 源碼連接: https://github.com/MissWangLove/SpringBoot

相關文章
相關標籤/搜索