(《深入實(shí)踐Spring Boot》筆記2)在Spring Boot中使用數(shù)據(jù)庫(kù)


IDE軟件:IntelliJ IDEA 15.0.2
操作系統(tǒng):Win10
Spring boot版本:1.4.1 Release
Maven版本:apache-maven-3.5.2


一、引言

??使用數(shù)據(jù)庫(kù)是開(kāi)發(fā)基本應(yīng)用的基礎(chǔ)。借助于開(kāi)發(fā)框架,我們已經(jīng)不用編寫(xiě)原始的訪問(wèn)數(shù)據(jù)庫(kù)的代碼,也不用調(diào)用JDBC(Java Data Base Connectivity)或者連接池等諸如此類(lèi)的被稱作底層的代碼,我們將在高級(jí)的層次上訪問(wèn)數(shù)據(jù)庫(kù)。而Spring Boot更是突破了以前所有開(kāi)發(fā)框架訪問(wèn)數(shù)據(jù)庫(kù)的方法,在前所未有的更加高級(jí)的層次上訪問(wèn)數(shù)據(jù)庫(kù)。因?yàn)镾pring Boot包含一個(gè)功能強(qiáng)大的資源庫(kù),為使用Spring Boot的開(kāi)發(fā)者提供了更加簡(jiǎn)便的接口進(jìn)行訪問(wèn)。 本文學(xué)習(xí)怎樣使用傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù),以及近期一段時(shí)間異軍突起的NoSQL(Not Only SQL)數(shù)據(jù)庫(kù)。 實(shí)例工程使用了分模塊的方式構(gòu)建,各模塊的定義如表:

項(xiàng)目 工程 功能
MySql模塊 mysql 使用MySql
Redis模塊 redis 使用Redis
MongoDB模塊 mongodb 使用MongoDB
Neo4j模塊 neo4j 使用Neo4j

二、使用Mysql數(shù)據(jù)庫(kù)

??對(duì)于傳統(tǒng)關(guān)系型數(shù)據(jù)庫(kù)來(lái)說(shuō),Spring Boot使用JPA(Java Persistence API)資源庫(kù)來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的操作,使用MySQL也是如此。簡(jiǎn)單地說(shuō),JPA就是為POJO(Plain Ordinary Java Object)提供持久化的標(biāo)準(zhǔn)規(guī)范,即將Java的普通對(duì)象通過(guò)對(duì)象關(guān)系映射(Object-Relational Mapping,ORM)持久化到數(shù)據(jù)庫(kù)中。

  1. MySQL依賴配置
    ??為了使用JPA和MySQL,首先在工程中引入它們的Maven依賴,如代碼清單所示。其中,指定了在運(yùn)行時(shí)調(diào)用MySQL的依賴。
 <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>

2. 實(shí)體建模
??首先創(chuàng)建一些普通對(duì)象,用來(lái)與數(shù)據(jù)庫(kù)的表建立映射關(guān)系,接著演示如何使用JPA對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪查改等存取操作。 假如現(xiàn)在有三個(gè)實(shí)體:部門(mén)、用戶和角色,并且它們具有一定的關(guān)系,即一個(gè)用戶只能隸屬于一個(gè)部門(mén),一個(gè)用戶可以擁有多個(gè)角色。Spring Boot的實(shí)體建模與使用Spring框架時(shí)的定義方法一樣,同樣比較方便的是使用了注解的方式來(lái)實(shí)現(xiàn)。
??注解@Table指定關(guān)聯(lián)的數(shù)據(jù)庫(kù)的表名,注解@Id定義一條記錄的唯一標(biāo)識(shí),并結(jié)合注解@GeneratedValue將其設(shè)置為自動(dòng)生成。部門(mén)實(shí)體只有兩個(gè)字段:id和name。

部門(mén)實(shí)體的建模如代碼Department.java:

package springboot.example.domain;

import javax.persistence.*;

/**
 * Created by zhang on 2018/2/10.
 */
@Entity
@Table(name="department")
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;

    public Department() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

??用戶實(shí)體包含三個(gè)字段:id、name和createdate,其中注解@ManyToOne定義它與部門(mén)的多對(duì)一關(guān)系,并且在數(shù)據(jù)庫(kù)表中用字段did來(lái)表示部門(mén)的ID,注解@ManyToMany定義與角色實(shí)體的多對(duì)多關(guān)系,并且用中間表user_role來(lái)存儲(chǔ)它們各自的ID,以表示它們的對(duì)應(yīng)關(guān)系。日期類(lèi)型的數(shù)據(jù)必須使用注解@DateTimeFormat來(lái)進(jìn)行格式化,以保證它在存取時(shí)能提供正確的格式,避免保存失敗。注解@JsonBackReference用來(lái)防止關(guān)系對(duì)象的遞歸訪問(wèn)。

用戶實(shí)體的建模如代碼User.java:

package springboot.example.domain;

import com.fasterxml.jackson.annotation.JsonBackReference;
import org.springframework.context.annotation.Role;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.*;
import java.util.Date;
import java.util.List;

/**
 * Created by zhang on 2018/2/10.
 */
@Entity
@Table(name="user")
public class User implements java.io.Serializable{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String name;
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createDate;

    @ManyToOne
    @JoinColumn(name="did")
    @JsonBackReference
    private Department department;

    @ManyToMany(cascade = {}, fetch = FetchType.EAGER)
    @JoinTable(name="user_role",
        joinColumns = {@JoinColumn(name = "user_id")},
        inverseJoinColumns = {@JoinColumn(name="roles_id")})
    private List<Role> roles;

    public User() {
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getCreateDate() {
        return createDate;
    }

    public void setCreateDate(Date createDate) {
        this.createDate = createDate;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

??角色實(shí)體建模比較簡(jiǎn)單,只要按設(shè)計(jì)的要求,定義id和name字段即可,當(dāng)然同樣必須保證id的唯一性并將其設(shè)定為自動(dòng)生成。

角色實(shí)體的建模如代碼Role.java:

package springboot.example.domain;

import javax.persistence.*;

/**
 * Created by zhang on 2018/2/10.
 */



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

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 實(shí)體持久化
    ??通過(guò)上面三個(gè)實(shí)體的定義,實(shí)現(xiàn)了使用Java的普通對(duì)象(POJO)與數(shù)據(jù)庫(kù)表建立映射關(guān)系(ORM),接下來(lái)使用JPA來(lái)實(shí)現(xiàn)持久化。 它是一個(gè)接口,并繼承于JPA資源庫(kù)JpaRepository接口,使用注解@Repository將這個(gè)接口也定義為一個(gè)資源庫(kù),使它能被其他程序引用,并為其他程序提供存取數(shù)據(jù)庫(kù)的功能。 使用相同的方法,可以定義部門(mén)實(shí)體和角色實(shí)體的資源庫(kù)接口。接口同樣繼承于JpaRepository接口,只要注意使用的參數(shù)是各自的實(shí)體對(duì)象即可。

用戶實(shí)體使用JPA進(jìn)行持久化的例子如代碼UserRepository.java:

package springboot.example.Repository;

import org.springframework.data.jpa.repository.JpaRepository;
import springboot.example.domain.User;

/**
 * Created by zhang on 2018/2/10.
 */
public interface UserRepository extends JpaRepository<User,Long>{

}

??這樣就實(shí)現(xiàn)存取數(shù)據(jù)庫(kù)的功能了。現(xiàn)在可以對(duì)數(shù)據(jù)庫(kù)進(jìn)行增刪查改、進(jìn)行分頁(yè)查詢和指定排序的字段等操作。 或許你還有疑問(wèn),我們定義的實(shí)體資源庫(kù)接口并沒(méi)有聲明一個(gè)方法,也沒(méi)有對(duì)接口有任何實(shí)現(xiàn)的代碼,甚至連一條SQL查詢語(yǔ)句都沒(méi)有寫(xiě),使用JPA就是可以這么簡(jiǎn)單。我們來(lái)看看JpaRe-pository的繼承關(guān)系,JpaRepository繼承于PagingAndSortingRepository,它提供了分頁(yè)和排序功能,是的,使用JPA就是可以這么簡(jiǎn)單。我們來(lái)看看JpaRe-pository的繼承關(guān)系,你也許會(huì)明白一些。JpaRepository繼承于PagingAndSortingRepository,它提供了分頁(yè)和排序功能,PagingAndSortingRepository繼承于Crud-Repository,它提供了簡(jiǎn)單的增刪查改功能。因?yàn)槎x的接口繼承于JpaRepository,所以它傳遞性地繼承上面所有這些接口,并擁有這些接口的所有方法,這樣就不難理解為何它包含那么多功能了。這些接口提供的一些方法如下:

<S extends T> S save(S var1); 
T findOne(ID var1); 
long count(); 
void delete(ID var1); 
void delete(T var1); 
void deleteAll(); 
Page<T> findAll(Pageable var1); 
List<T> findAll(); 
List<T> findAll(Sort var1); 
List<T> findAll(Iterable<ID> var1); 
void deleteAllInBatch(); 
T getOne(ID var1); 
......

??JPA還提供了一些自定義聲明方法的規(guī)則,例如,在接口中使用關(guān)鍵字findBy、readBy、getBy作為方法名的前綴,拼接實(shí)體類(lèi)中的屬性字段(首個(gè)字母大寫(xiě)),并可選擇拼接一些SQL查詢關(guān)鍵字來(lái)組合成一個(gè)查詢方法。例如,對(duì)于用戶實(shí)體,下列查詢關(guān)鍵字可以這樣使用:

·And,例如findByIdAndName(Long id,String name);
·Or,例如findByIdOrName(Long id,String name);
·Between,例如findByCreatedateBetween(Date start,Date end);
·LessThan,例如findByCreatedateLessThan(Date start);
·GreaterThan,例如findByCreatedateGreaterThan(Date start);
·IsNull,例如findByNameIsNull();
·IsNotNull,例如findByNameIsNotNull(); 
·NotNull,與IsNotNull等價(jià);
·Like,例如findByNameLike(String name); 
·NotLike,例如findByNameNotLike(String name); 
·OrderBy,例如findByNameOrderByIdAsc(String name); 
·Not,例如findByNameNot(String name); 
·In,例如findByNameIn(Collection<String>nameList); 
·NotIn,例如findByNameNotIn(Collection<String>nameList)。

??又如下列對(duì)用戶實(shí)體類(lèi)自定義的方法聲明,它們都是符合JPA規(guī)則的,這些方法也不用實(shí)現(xiàn),JPA將會(huì)代理實(shí)現(xiàn)這些方法。

User findByNameLike(String name); 
User readByName(String name);
List<User> getByCreatedateLessThan(Date star);
  1. Mysql測(cè)試
    ??現(xiàn)在,為了驗(yàn)證上面設(shè)計(jì)的正確性,我們用一個(gè)實(shí)例來(lái)測(cè)試一下。 首先,增加一個(gè)使用JPA的配置類(lèi),其中@EnableTransac-tionManagement啟用了JPA的事務(wù)管理;@EnableJpaRepositories啟用了JPA資源庫(kù)并指定了上面定義的接口資源庫(kù)的位置;@EntityScan指定了定義實(shí)體的位置,它將導(dǎo)入我們定義的實(shí)體。注意,在測(cè)試時(shí)使用的JPA配置類(lèi)可能與這個(gè)配置略有不同,這個(gè)配置的一些配置參數(shù)是從配置文件中讀取的,而測(cè)試時(shí)使用的配置類(lèi)把一些配置參數(shù)都包含在類(lèi)定義中了。

配置類(lèi)JpaConfiguration.java

package springboot.example.config;

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;

/**
* Created by zhang on 2018/2/10.
*/
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@EnableTransactionManagement(proxyTargetClass=true)
@EnableJpaRepositories(basePackages="dbdemo.**.repository")
@EntityScan(basePackages="dbdemo.**.entity")
public class JpaConfiguration {
   @Bean
   PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
       return new PersistenceExceptionTranslationPostProcessor();
   } 
}

??其次,在MySQL數(shù)據(jù)庫(kù)服務(wù)器中創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)test,然后配置一個(gè)可以訪問(wèn)這個(gè)數(shù)據(jù)庫(kù)的用戶及其密碼。數(shù)據(jù)庫(kù)的表結(jié)構(gòu)可以不用創(chuàng)建,在程序運(yùn)行時(shí)將會(huì)按照實(shí)體的定義自動(dòng)創(chuàng)建。如果還沒(méi)有創(chuàng)建一個(gè)具有完全權(quán)限訪問(wèn)數(shù)據(jù)庫(kù)test的用戶,可以在連接MySQL服務(wù)器的查詢窗口中執(zhí)行下面指令,這個(gè)指令假設(shè)你將在本地中訪問(wèn)數(shù)據(jù)庫(kù)。

數(shù)據(jù)源和JPA配置application.yml:

spring:
    datasource:
         url: jdbc:mysql://localhost:3306/test?characterEncoding=utf8
         username: root
         password: root
    jpa:
         database: MYSQL
         show-sql: true
         #Hibernate ddl auto (validate|create|create-drop|update)         
         hibernate:
                ddl-auto: update             
         properties:
                hibernate:                 
                dialect: org.hibernate.dialect.MySQL5Dialect

??配置中將ddl-atuo設(shè)置為update,就是使用Hibernate來(lái)自動(dòng)更新表結(jié)構(gòu)的,即如果數(shù)據(jù)表不存在則創(chuàng)建,或者如果修改了表結(jié)構(gòu),在程序啟動(dòng)時(shí)則執(zhí)行表結(jié)構(gòu)的同步更新。

??最后,編寫(xiě)一個(gè)測(cè)試程序,測(cè)試程序首先初始化數(shù)據(jù)庫(kù),創(chuàng)建一個(gè)部門(mén),命名為“開(kāi)發(fā)部”,創(chuàng)建一個(gè)角色,命名為admin,創(chuàng)建一個(gè)用戶,命名為user,同時(shí)將它的所屬部門(mén)設(shè)定為上面創(chuàng)建的部門(mén),并將現(xiàn)有的所有角色都分配給這個(gè)用戶。然后使用分頁(yè)的方式查詢所有用戶的列表,并從查到的用戶列表中,打印出用戶的名稱、部門(mén)的名稱和第一個(gè)角色的名稱等信息。

測(cè)試程序代碼清單:MysqlTest.java

import org.apache.log4j.Logger;
import org.junit.Test;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.util.Assert;
import springboot.example.Repository.DepartmentRepository;
import springboot.example.Repository.RoleRepository;
import springboot.example.Repository.UserRepository;
import springboot.example.config.JpaConfiguration;
import springboot.example.domain.Department;
import springboot.example.domain.Role;
import springboot.example.domain.User;
import java.util.Date;
import java.util.List;

/**
 * Created by zhang on 2018/2/10.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={JpaConfiguration.class})
public class MysqlTest{
    private static Logger logger = Logger.getLogger(MysqlTest.class);
    @Autowired
    private UserRepository userRepository;
    @Autowired
    DepartmentRepository departmentRepository;
    @Autowired
    RoleRepository roleRepository;
    @Before public void initData(){
        userRepository.deleteAll();
        roleRepository.deleteAll();
        departmentRepository.deleteAll();
        Department department = new Department();
        department.setName(" 開(kāi)發(fā)部 ");
        departmentRepository.save(department);
        Assert.notNull(department.getId());
        Role role = new Role();
        role.setName("admin");
        roleRepository.save(role);
        Assert.notNull(role.getId());
        User user = new User();
        user.setName("user");
        user.setCreateDate(new Date());
        user.setDepartment(department);
        List<Role> roles = roleRepository.findAll();
        Assert.notNull(roles);
        user.setRoles(roles);
        userRepository.save(user);
        Assert.notNull(user.getId());
    }

    @Test
    public void findPage(){
        Pageable pageable = new PageRequest(0, 10, new Sort(Sort.Direction.ASC, "id"));
        Page<User> page = userRepository.findAll(pageable);
        Assert.notNull(page);
        for(User user : page.getContent()) {
            logger.info(user);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容