1. 程式人生 > >SpringBoot:SpringData JPA:訪問關係型資料庫

SpringBoot:SpringData JPA:訪問關係型資料庫

一、SpringData JPA是什麼

上一節學習瞭如何入門SpringBoot,本篇章介紹springboot對資料庫是如何進行訪問的。在介紹之前,有必要了解一下SpringDataJPA,這是SpringCore中的一個專案,致力於簡化對資料庫的訪問,增強了ORM的操作。對於JPA(Java persisten API),全稱為Java持久化API,是JAVAEE中的一套規範API。它推出的目的是對ORM框架進行大統一,它提供一套介面,讓廠商們(如hibernate)對JPA提供實現。JPA與hibernate的關係就像JDBC與Mysql驅動、Oracle驅動一樣的關係,只是它更加高度抽象,可以稱之為ORM框架的介面,它的層遞關係是這樣的:


而SpringDataJPA是什麼樣子的呢?相信學過hibernate的同學一定也學過hibernate template,甚至自己實現過hibernate template。沒錯,SpringData JPA就很像這樣一款template,把該有的東西都給你封裝好,當然,其強大不止這一點點。相信學過mybatis的同學也知道,我們只需要一個mapper介面與一些mapper.xml,就可以讓其代理實現的持久層。SpringDataJPA在使用的時候,也只需要宣告一個介面,讓其Spring以代理的形式生成Dao。對於SpringDataJPA與hibernate等ORM框架、JPA的關係是這樣子的:

SpringDataJPA對類似hibernate這樣的框架又做了一層封裝,以便於我們程式設計的時候使用更方便。好了,說了那麼多理論,下面就開始實踐吧。

二、開始實踐

1.建立maven web專案,名稱為springboot-jpa,在pom.xml中匯入以下依賴:

    

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.1.RELEASE</version>
    </parent>
 
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <springBoot.groupId>org.springframework.boot</springBoot.groupId>
    </properties>
 
    <dependencies>
        <!-- SpringBoot Start -->
        <dependency>
            <groupId>${springBoot.groupId}</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- jpa -->
        <dependency>
            <groupId>${springBoot.groupId}</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>${springBoot.groupId}</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

其中SpringDataJPA核心支援依賴是spring-boot-starter-data-jpa。


2.編輯配置

待maven構建導包完成後,請在mysql中建立好資料庫springboot_test,在src/main/resources中新增application.yml配置,程式碼如下所示:

server:
  port: 8080
  tomcat:
    uri-encoding: UTF-8
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/springboot_test?characterEncoding=utf8
    username: root
    password: root
  jpa:
    database: MYSQL
    show-sql: true
    #hibernate ddl auto(validate,create,update,create-drop)
    hibernate:
      ddl-auto: update
      naming:
        strategy: org.hibernate.cfg.ImprovedNamingStrategy
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL5Dialect


這些配置相信大家都能看得懂,最後一行是資料庫方言,如果是oracle,就有oracle的方言。

包結構組織如下圖:

在src/main/java中新建org.fage包,在其中建立一個Jpa的Java配置類:

package org.fage;
 
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
 
/**
 * 
 * @author Caizhfy
 * @email [email protected]
 * @createTime 2017年10月30日
 * @description JPA基礎配置類
 *
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass=true)
@EnableJpaRepositories(basePackages={"org.fage.**.repository"})
@EntityScan(basePackages={"org.fage.**.domain"})
public class JpaConfiguration {
    @Bean
    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
        return new  PersistenceExceptionTranslationPostProcessor();
    }
}


@Order聲明瞭元件載入的順序,其中接受一個整形值,值越低約先載入;
@Configuration聲明瞭這是一個配置類,該註解中包含有@Component註解,可以讓SpringBoot自動掃描載入;@EnableTransactionManagement聲明瞭開啟事務管理器代理;
@EnableJpaRepositories宣告repository(也就是原來的dao,SpringData中稱其為Repository)所在位置,值中的兩個星號是萬用字元,代表org.fage.任何路徑下的.repository包中都是repository;
@EntityScan是對實體元件位置的宣告與掃描,兩個星號依舊是萬用字元。在根包中建立入口類App.java:

package org.fage;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
 * 
 * @author Caizhfy
 * @email [email protected]
 * @createTime 2017年10月30日
 * @description Springboot-jpa學習建立步驟:
 *                         1.建立專案 
 *                        2.新增依賴,填寫配置類
 *                        3.配置application.yml
 *                        4.配置實體jpa關係
 *                        5.繼承jpaRepository
 *                        6.編寫測試用例
 */
 
@SpringBootApplication
@ImportAutoConfiguration(value=JpaConfiguration.class)
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}


3.建立實體建模:

建模關係是有部門、使用者、角色三個實體;部門與使用者是一對多的關係,使用者與角色是多對多的關係,在org.fage.domain包中建立三個實體,建模程式碼如下
部門實體:

@Entity
@Table(name = "department")
public class Department implements Serializable {
    
 
    private static final long serialVersionUID = 159714803901985366L;
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    @OneToMany(mappedBy="department",fetch=FetchType.LAZY)
    private List<User> users;
    
//getter and setter
...
}

使用者實體:

@Entity
@Table(name = "user")
public class User implements Serializable{
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    @Column(name = "create_date")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createDate;
    //一對多對映
    @ManyToOne
    @JoinColumn(name = "department_id")
    @JsonBackReference // 防止物件的遞迴訪問
    private Department department;
    //多對多對映
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name = "user_role",
            joinColumns = {@JoinColumn(name="user_id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id")}
            )
 
//getter and setter
}

角色實體:

@Entity
@Table(name="role")
public class Role implements Serializable{
    
    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    private Long id;
    private String name;
    
    
//getter and setter 
    
}

實體對映關係建立完成,解釋一下其中的註解,這些註解其實大多數都是在學JPA與hibernate的時候學會的:@Entity宣告這是一個實體類;@Table宣告該實體在表中對應的表名是什麼;@Id宣告該屬性為實體對應表的主鍵;@GeneratedValue聲明瞭主鍵策略是什麼,這裡使用的是自動增長主鍵策略;@ManyToOne與@OneToMany聲明瞭該實體與對應屬性的實體是多對一或者一對多的關係,其中如果設立雙向關係記得設定mappedBy,fetch的值聲明瞭載入方式是懶載入還是立即載入;@ManyToMany是實體間多對多的關係,@JoinTable設定了兩個多對多的實體的中間表外來鍵。

4.建立持久層Dao

在SpringDataJPA中,我們不在稱其為mapper或者dao,而是稱為repository,我們來為三個實體分別建立自己的repository。在org.fage.repository中建立如下三個介面,繼承自JpaRepository,泛型左邊為實體型別,右邊為該實體的主鍵型別:

部門repository:


package org.fage.repository;
 
import org.fage.domain.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long>{}

使用者repository:

@Repository
public interface UserRepository extends JpaRepository<User, Long>{
    //And用法
    User findById(long id);
    
    User findByIdAndUsername(long id, String username);
    
    //Or用法
    User findByIdOrUsername(long id, String name);
    
    //Between用法
    User findByCreateDateBetween(Date start, Date end);
    
    //LessThan用法
    List<User> findByCreateDateLessThan(Date start);
    
    //GreaterThan用法
    List<User> findByCreateDateGreaterThan(Date start);
    
    //IsNull/IsNutNull用法
    List<User> findByUsernameIsNull();
    
    //Like/NotLike用法
    List<User> findByUsernameLike(String username);
    
    //OrderBy用法
    List<User> findByUsernameOrderByIdAsc(String username);
    
    //Not用法
    List<User> findByUsernameNot(String username);
    
    //In/NotIn用法
    List<User> findByUsernameIn(Collection<String> nameList);
    
    
}

角色repository:

@Repository
public interface RoleRepository extends JpaRepository<Role, Long>{}

至此,我們可以建立Junit程式,測試使用這三個持久層介面了,在src/test/java中可以建立測試程式進行測試了,hibernate實現會自動幫我們生成表,SpringDataJPA提供了常用的CRUD操作: 


@RunWith(SpringRunner.class)
@SpringBootTest
public class RepositoryTest {
    private final Logger log = LoggerFactory.getLogger(RepositoryTest.class);
    
    
    @Autowired
    UserRepository userRepository;
    @Autowired
    RoleRepository roleRepository;
    @Autowired
    DepartmentRepository departmentRepository;
 
    @Before
    public void initData() {
        departmentRepository.deleteAll();
        roleRepository.deleteAll();
        userRepository.deleteAll();
 
        Department d = new Department();
        d.setName("開發部");
        departmentRepository.save(d);
        Assert.assertNotNull(d.getId());
 
        Role r = new Role();
        r.setName("部門經理");
        roleRepository.save(r);
        Assert.assertNotNull(r.getId());
        List<Role> roles = new ArrayList<Role>();
        roles.add(r);
 
        User u = new User();
        u.setUsername("蔡智法");
        u.setCreateDate(new Date());
        u.setDepartment(d);
        u.setRoles(roles);
        userRepository.save(u);
        Assert.assertNotNull(u.getId());
    }
 
    @Test
    public void testGeneralMethod(){
        System.out.println(userRepository.findByUsernameLike("蔡智法"));
    }
    
    
    @Test
    public void testFindPage() {
        //hibernate一對多分頁原理:先分頁,然後在將id作為引子查詢(效率低)
        Pageable pageable = new PageRequest(0, 5, new Sort(Sort.Direction.ASC, "id"));
        Page<Department> page = departmentRepository.findAll(pageable);
        System.out.println(page.getNumberOfElements());
    }
 
}


至此,執行成功。其實如果你也動手試一試,發現增刪改查都不需要實現程式碼就能擁有這些功能,這些Repository介面中還有很多方法可以嘗試,希望大家自行踴躍嘗試,太便捷了。

沒搞錯吧?這些介面甚至一行業務程式碼都不用寫(UserRepository中的程式碼待會兒解釋),就能實現增刪改查、分頁操作??沒錯,確實就是這麼簡單。不難發現,我們獲得支援的最重要一點是繼承了JpaRepository(該介面提供了上層介面更多的查詢操作)介面,跟蹤原始碼發現JpaRepository介面繼承了上層的PageAndSortingRepository(該介面提供了分頁以及排序的支援),PageAndSortingRepository繼承自CrudRepository(該介面提供基礎的增刪改查操作),而CrudRepository又繼承自Repository。我們知道,他們都是介面,本身沒有實現方法,但是SpringDataJPA幫我們提供了一套實現,在執行的時候會以代理的形式給我們生成實現類,只要你繼承了Spring給你的這些Repository介面,那麼你就能獲得這些方法支援。
問題又來了,UserRepository介面中自定義宣告的方法我們也沒有實現,為什麼也能正常使用呢?
那是因為在SpringDataJPA中,自定義的方法一般有兩種,第一種就是這種“約定命名”法,這種方法一定要查詢命名規範,比如findByXXX,SpringData會根據字首、中間連線詞(Or、And、Like、NotNull等等類似Sql中的關鍵詞),詳情使用請看下錶,內部會自己轉換成JPQL使用:


內部幫我們拼接sql代理生成方法的實現,不得不感嘆,真的太方便了。
第二種方法就是使用@Query註解使用JPQL(類似SQL與EL的組合)語句查詢,這種查詢一般不使用代理,是直接內部轉化成SQL進行執行,這種方法比前一種靈活一些,後面章節會提到(還有一種方法,就是自定義增強Repository實現),使用原生SQL查詢,這樣方便優化SQL。自定義增強Repository與框架原理解析,在後續文章中會陸續更新。

以上原始碼在https://github.com/Phapha1996/springboot-jpa能檢出。
在本文中,最重要的詞就是代理了,如果不瞭解代理模式的同學,希望能惡補一波代理,這是一組非常值得學習而有用的模式。
 

原文參考:https://blog.csdn.net/phapha1996/article/details/78712597