《深入實踐Spring Boot》第2章 在Spring Boot中使用數據庫

[TOC]

第2章 在Spring Boot中使用數據庫

使用數據庫是開發基本應用的基礎。藉助於開發框架,我們已經不用編寫原始的訪問數據庫的代碼,也不用調用JDBC(Java Data Base Connectivity)或者連接池等諸如此類的被稱作底層的代碼,我們將在高級的層次上訪問數據庫。而Spring Boot更是突破了以前所有開發框架訪問數據庫的方法,在前所未有的更加高級的層次上訪問數據庫。因爲Spring Boot包含一個功能強大的資源庫,爲使用Spring Boot的開發者提供了更加簡便的接口進行訪問。

提示: 可以將Maven的中央倉庫修改爲阿里雲的倉庫地址,加快jar包的加載速度。參考博文:Maven將中央倉庫修改爲阿里雲的倉庫地址

本章將介紹怎樣使用傳統的關係型數據庫,以及近期一段時間異軍突起的NoSQL(Not Only SQL)數據庫。

本章的實例工程使用了分模塊的方式構建,各模塊的定義如表2-1所示。

項目 工程 功能
MySQL模塊 mysql 使用MySQL
Redis模塊 redis 使用Redis
MongoDB模塊 mongodb 使用MongoDB
Neo4j模塊 meo4j 使用Neo4j

提示:運行此實例需要從GitHub獲取源碼:https://github.com/shenhuanjie/spring-boot-db,並導入IDEA。

2.1 使用MySQL

對於傳統關係型數據庫來說,Spring Boot使用JPA(Java Persistence API)資源庫來實現對數據庫的操作,使用MySQL也是如此。簡單地說,JPA就是爲POJO(Plain Ordinary Java Object)提供持久化的標準規範,即將Java的普通對象通過對象關係映射(Object-Relational Mapping,ORM)持久化到數據庫中。

2.1.1 MySQL依賴配置

爲了使用JPA和MySQL,首先在工程中引入它們的Maven依賴,如代碼清單2-1所示。其中,指定了在運行時調用MySQL的依賴。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-db</artifactId>
        <groupId>springboot.db</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>mysql</artifactId>

    <dependencies>
        <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>
    </dependencies>
</project>

提示: 模塊pom.xml只需加上對應的引用即可,父級pom.xml則加上全局的spring.boot 引用。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>springboot.db</groupId>
    <artifactId>spring-boot-db</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>mysql</module>
        <module>mongodb</module>
        <module>neo4j</module>
        <module>redis</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.2.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.1.2 實體建模

首先創建一些普通對象,用來與數據庫的表建立映射關係,接着演示如何使用JPA對數據庫進行增刪改查等存取操作。

假如現在有三個實體:部門、用戶和角色,並且它們具有一定的關係,即一個用戶只能隸屬於一個部門,一個用戶可以擁有多個角色。它們的關係模型如圖2-1所示。

image.png

Spring Boot的實體建模與使用Spring框架時的定義方法一樣,同樣比較方便的是使用了註解的方式來實現。

部門實體的建模如代碼清單2-2所示,其中註解@Table指定關聯的數據庫的表名,註解@Id定義一條記錄的唯一標識,並結合註解@GeneratedValue將其設置爲自動生成。部門實體只有兩個字段:id和name。程序中省略了Getter和Setter方法的定義,這些方法可以使用IDEA的自動生成工具很方便地生成。

package dbdemo.mysql.entity;

import javax.persistence.*;

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

}

用戶實體包含三個字段:id、name和createdate,用戶實體建模如代碼清單2-3所示。其中註解@ManyToOne定義它與部門的多對一關係,並且在數據庫表中用字段did來表示部門的ID,註解@ManyToMany定義與角色實體的多對多關係,並且用中間表user_role來存儲它們各自的ID,以表示它們的對應關係。日期類型的數據必須使用註解@DateTimeFormat來進行格式化,以保證它在存取時能提供正確的格式,避免保存失敗。註解@JsonBackReference用來防止關係對象的遞歸訪問。

package dbdemo.mysql.entity;

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

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

@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 deparment;

    @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 getDeparment() {
        return deparment;
    }

    public void setDeparment(Department deparment) {
        this.deparment = deparment;
    }

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

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

角色實體建模比較簡單,只要按設計的要求,定義id和name字段即可,當然同樣必須保證id的唯一性並將其設定爲自動生成。角色實體的建模如代碼清單2-4所示。

package dbdemo.mysql.entity;

import javax.persistence.*;

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

2.1.3 實體持久化

通過上面三個實體的定義,實現了使用Java的普通對象(POJO)與數據庫表建立映射關係(ORM),接下來使用JPA來實現持久化。

用戶實體使用JPA進行持久化的例子如代碼清單2-5所示。它是一個接口,並繼承於JPA資源庫JpaRepository接口,使用註解@Repository將這個接口也定義爲一個資源庫,使它能被其他程序引用,併爲其他程序提供存取數據庫的功能。

使用相同的方法,可以定義部門實體和角色實體的資源庫接口。接口同樣繼承於JpaRepository接口,只要注意使用的參數是各自的實體對象即可。

package dbdemo.mysql.repository;

import dbdemo.mysql.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Date;
import java.util.List;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    User findByNameLike(String name);
    User readByName(String name);
    List<User> getByCreateddateLessThan(Date star);
}

package dbdemo.mysql.repository;

import dbdemo.mysql.entity.Department;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DepartmentRepository extends JpaRepository<Department, Long> {
    
}
package dbdemo.mysql.repository;

import dbdemo.mysql.entity.Role;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

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

這樣就實現存取數據庫的功能了。現在可以對數據庫進行增刪改查、進行分頁查詢和指定排序的字段等操作。

或許你還有疑問,我們定義的實體資源庫接口並沒有聲明一個方法,也沒有對接口有任何實現的代碼,甚至一條SQL查詢語句都沒有寫,這怎麼可能?

是的,使用JPA就是可以這麼簡單。我們來看看JpaRepository的繼承關係,你也許會明白一些。如圖2-2所示,JpaRepository繼承於PagingAndSortingRepository,它提供了分頁和排序功能,PagingAndSortingRepository繼承於CrudRepository,它提供了簡單的增刪改查功能。

image.png

因爲定義的接口繼承於JpaRepository,所以它傳遞性地繼承上面所有這些接口,並擁有這些接口的所有方法,這樣就不難理解爲何它包含那麼多功能了。這些接口提供的一些方法如下:

image.png

JPA還提供了一些自定義聲明方法的規則,例如,在接口中使用關鍵值findBy、readBy、getBy作爲方法名的前綴,拼接實體類中的屬性字段(首個字母大寫),並可選擇拼接一些SQL查詢關鍵字來組合成一個查詢方法。例如,對於用戶實體,下列查詢關鍵字可以這樣使用:

image.png

又如下列對用戶實體類自定義的方法聲明,它們都是符合JPA規則的,這些方法也不用實現,JPA將會代理實現這些方法。

User findByNameLike(String name);
User readByName(String name);
List<User> getByCreateddateLessThan(Date star);

2.1.4 MySQL測試

現在,爲了驗證上面設計的正確性,我們用一個實例來測試一下。

首先,增加一個使用JPA的配置類,如代碼清單2-6所示。其中@EnableTransactionManagement啓用了JPA的事務管理;@EnableJpaRepositories啓用了JPA資源庫並制定了上面定義的接口資源庫的位置;@EntityScan指定了定義實體的位置,它將導入我們定義的實體。注意,在測試時使用的JPA配置類可能與這個配置略有不同,這個配置的一些配置參數是從配置文件中讀取的,而測試時使用的配置類把一些配置參數都包含在類定義中了。

package dbdemo.mysql.config;

import org.springframework.boot.orm.jpa.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;


@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數據庫服務器中創建一個數據庫test,然後配置一個可以訪問這個數據庫的用戶及其密碼。數據庫的表結構可以不用創建,在程序運行時將會按照實體的定義自動創建。如果還沒有創建一個具有完全權限訪問數據庫test的用戶,可以在連接MySQL服務器的查詢窗口中執行下面指令,這個指令假設你將在本地中訪問數據庫。

grant all privileges on test.* to 'root'@'localhost' identified by '123456';

然後,在Spring Boot的配置文件application.yml中使用如代碼清單2-7所示的配置,用來設置數據源和JPA的工作模式。

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

配置中將ddl-auto設置爲update,就是使用Hibernate來自動更新表結構的,即如果數據表不存在則創建,或者如果修改了表結構,在程序啓動時則執行表結構的同步更新。

最後,編寫一個測試程序,如代碼清單2-8所示。測試程序首先初始化數據庫,創建一個部門,命名爲“開發部”,創建一個角色,命名爲admin,創建一個用戶,命名爲user,同時將它所屬部門設定爲上面創建的部門,並將現有的所有角色都分配給這個用戶。然後使用分頁的方式查詢所有用戶的列表,並從查到的用戶列表中,打印出用戶的名稱、部門的名稱和第一個角色的名稱等信息。

package dbdemo.mysql.test;

import dbdemo.mysql.entity.Department;
import dbdemo.mysql.entity.Role;
import dbdemo.mysql.entity.User;
import dbdemo.mysql.repository.DepartmentRepository;
import dbdemo.mysql.repository.RoleRepository;
import dbdemo.mysql.repository.UserRepository;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;

import java.util.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {JpaConfiguration.class})
public class MysqlTest {
    private static Logger logger = LoggerFactory.getLogger(MysqlTest.class);

    @Autowired
    UserRepository userRepository;
    @Autowired
    DepartmentRepository departmentRepository;
    @Autowired
    RoleRepository roleRepository;

    @Before
    public void initData(){
        userRepository.deleteAll();
        roleRepository.deleteAll();
        departmentRepository.deleteAll();

        Department department = new Department();
        department.setName("開發部");
        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.setDeparment(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==== user name:{}, department name:{}, role name:{}",
                    user.getName(), user.getDeparment().getName(), user.getRoles().get(0).getName());
        }
    }
}

**提示:**test目錄下同樣加入JpaConfiguration配置類

package dbdemo.mysql.test;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;

import javax.sql.DataSource;
import java.util.Properties;


@Configuration
@EnableJpaRepositories(basePackages = "dbdemo.**.repository")
public class JpaConfiguration {

    @Bean
    PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor(){
        return new PersistenceExceptionTranslationPostProcessor();
    }

    @Bean
    public DataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf8");
        dataSource.setUsername("root");
        dataSource.setPassword("123456");

        return dataSource;
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
        entityManagerFactoryBean.setDataSource(dataSource());
        entityManagerFactoryBean.setPackagesToScan("dbdemo.mysql.entity");
        entityManagerFactoryBean.setJpaProperties(buildHibernateProperties());
        entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter() {{
            setDatabase(Database.MYSQL);
        }});
        return entityManagerFactoryBean;
    }

    protected Properties buildHibernateProperties()
    {
        Properties hibernateProperties = new Properties();

        hibernateProperties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
        hibernateProperties.setProperty("hibernate.show_sql", "true");
        hibernateProperties.setProperty("hibernate.use_sql_comments", "false");
        hibernateProperties.setProperty("hibernate.format_sql", "true");
        hibernateProperties.setProperty("hibernate.hbm2ddl.auto", "update");
        hibernateProperties.setProperty("hibernate.generate_statistics", "false");
        hibernateProperties.setProperty("javax.persistence.validation.mode", "none");

        //Audit History flags
        hibernateProperties.setProperty("org.hibernate.envers.store_data_at_delete", "true");
        hibernateProperties.setProperty("org.hibernate.envers.global_with_modified_flag", "true");

        return hibernateProperties;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }

    @Bean
    public TransactionTemplate transactionTemplate() {
        return new TransactionTemplate(transactionManager());
    }
}

好了,現在可以使用JUnit來運行這個測試程序了,在IDEA的Run/Debug Configuration配置中增加一個JUnit配置項,模塊選擇mysql,工作目錄選擇模塊所在的根目錄,程序選擇dbdemo.mysql.test.MysqlTest,並將配置項目名稱保存爲mysqlTest,如圖2-3所示。

image.png

用Debug方式運行測試配置項目mysqltest,可以在控制檯中看到執行的過程和結果。如果狀態欄中顯示爲綠色,並且提示“All Tests passed”,則表示測試全部通過。在控制檯中也可以查到下列打印信息:

dbdemo.mysql.test.MysqlTest - ====user==== ,user name:user,department name:開發部,role name:admin

這時如果在MySQL服務器中查看數據庫test,不但可以看到表結構都已經創建了,還可以看到上面測試生成的一些數據。

這是不是很激動人心?在Spring Boot使用數據庫,就是可以如此簡單和有趣。到目前爲止,我們不僅沒有寫過一條查詢語句,也沒有實現一個訪問數據庫的方法,但是已經能對數據庫執行所有的操作,包括一般的增刪改查和分頁查詢。

2.2 使用Redis

關係型數據庫在性能上總是存在一些這樣那樣的缺陷,所以大家有時候在使用傳統關係型數據庫時,會與具有高效存取功能的緩存系統結合使用,以提高系統的訪問性能。在很多流行的緩存系統中,Redis是一個不錯的選擇。Redis是一種可以持久存儲的緩存系統,是一個高性能的key-value數據庫,它使用鍵-值對的方式來存儲數據。

2.2.1 Redis依賴配置

需要使用Redis,可在工程的Maven配置中加入spring-boot-starter-redis依賴,如代碼清單2-9所示。其中gson是用來轉換Json數據格式的工具,mysql是引用了上一節的模塊,這裏使用2.1節定義的實體對象來存取數據,演示Redis中的存取操作。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-boot-db</artifactId>
        <groupId>springboot.db</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>redis</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.4</version>
        </dependency>
        <dependency>
            <groupId>springboot.db</groupId>
            <artifactId>mysql</artifactId>
            <version>${project.version}</version>
        </dependency>
    </dependencies>

</project>

2.2.2 創建Redis服務類

Redis提供了下列幾種數據類型可供存取:

  • string;
  • hash;
  • list;
  • set及zset;

在實例中,將使用string即字符串類型來演示數據的存取操作。對於Redis,Spring Boot沒有提供像JPA那樣相應的資源庫接口,所以只能仿照上一節中Repository的定義編寫一個實體User的服務類,如代碼清單2-10所示。這個服務類可以存取對象User以及由User組成的列表List,同時還提供了一個刪除的方法。所有這些方法都是使用RedisTemplate來實現的。

package dbdemo.redis.repository;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import dbdemo.mysql.entity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Repository
public class UserRedis {
    /**
     * 這些方法都是使用RedisTemplate來實現的。
     */
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * Add User
     *
     * @param key
     * @param time
     * @param user
     */
    public void add(String key, Long time, User user) {
        Gson gson = new Gson();
        redisTemplate.opsForValue().set(key, gson.toJson(user), time, TimeUnit.MINUTES);
    }

    /**
     * Add User List
     *
     * @param key
     * @param time
     * @param users
     */
    public void add(String key, Long time, List<User> users) {
        Gson gson = new Gson();
        redisTemplate.opsForValue().set(key, gson.toJson(users), time, TimeUnit.MINUTES);
    }

    /**
     * Get User
     *
     * @param key
     * @return
     */
    public User get(String key) {
        Gson gson = new Gson();
        User user = null;
        String userJson = redisTemplate.opsForValue().get(key);
        if (!StringUtils.isEmpty(userJson))
            user = gson.fromJson(userJson, User.class);
        return user;
    }

    /**
     * Get User List
     *
     * @param key
     * @return
     */
    public List<User> getList(String key) {
        Gson gson = new Gson();
        List<User> ts = null;
        String listJson = redisTemplate.opsForValue().get(key);
        if (!StringUtils.isEmpty(listJson))
            ts = gson.fromJson(listJson, new TypeToken<List<User>>() {
            }.getType());
        return ts;
    }

    /**
     * Delete Obj
     *
     * @param key
     */
    public void delete(String key) {
        redisTemplate.opsForValue().getOperations().delete(key);
    }
}

Redis沒有表結構的概念,所以要實現MySQL數據庫中表的數據(即普通Java對象映射的實體數據)在Redis中存取,必須做一些轉換,使用JSON格式的文本作爲Redis與Java普通對象互相交換數據的存儲格式。這裏使用Gson工具將類對象轉換爲JSON格式的文本進行存儲,要取出數據時,再將JSON文本轉化爲Java對象。

因爲Redis使用key-value的方式存儲數據,所以存入時要生成一個唯一的key,而要查詢或刪除數據時,就可以使用這個唯一的key進行相應的操作。

保存在Redis數據庫中的數據默認是永久存儲的,可以指定一個時限來確定數據的生命週期,超過指定時限的數據將被Redis自動清除。在代碼清單2-10中我們以分鐘爲單位設定了數據的存儲期限。

另外,爲了能正確調用RedisTemplate,必須對其進行一些初始化工作,即主要對它存取的字符串進行一個JSON格式的系列化初始配置,如代碼清單2-11所示。

package dbdemo.redis.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, String> redisTemplate(
            RedisConnectionFactory factory) {
        StringRedisTemplate template = new StringRedisTemplate(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

2.2.3 Redis測試

如果還沒有按照Redis服務器,可以參照本書附錄C提供的方法安裝,然後在工程的配置文件application.yml中配置連接Redis服務器等參數,如代碼清單2-12所示。其中host和port分別表示Redis數據庫服務器的IP地址和開放端口,database可以不用指定,由Redis根據存儲情況自動選定(注:測試時這些配置是集中在一個配置類中實現的)。

spring:
  redis:
  # database: 1
    host: 127.0.0.1
    port: 6379
    pool:
      max-idle: 8
      min-idle: 0
      max-active: 8
      max-wait: -1

現在編寫一個JUnit測試程序,來演示如何在Redis服務器中存取數據,如代碼清單2-13所示。測試程序創建一個部門對象並將其命名爲“開發部”,創建一個角色對象並把它命名爲admin,創建一個用戶對象並把它命名爲user,同時設定這個用戶屬於“開發部”,並把admin這個角色分配給這個用戶。接着測試程序使用類名等參數生成一個key,並使用這個key清空原來的數據,然後用這個key存儲現在這個用戶的數據,最後使用這個key查詢用戶,並將查到的信息打印出來。

package dbdemo.redis.test;


import dbdemo.mysql.entity.Department;
import dbdemo.mysql.entity.Role;
import dbdemo.mysql.entity.User;
import dbdemo.redis.repository.UserRedis;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RedisConfig.class, UserRedis.class})
public class RedisTest {
    private static Logger logger = LoggerFactory.getLogger(RedisTest.class);

    @Autowired
    UserRedis userRedis;

    @Before
    public void setup() {
        Department department = new Department();
        department.setName("開發部");

        Role role = new Role();
        role.setName("admin");

        User user = new User();
        user.setName("user");
        user.setCreatedate(new Date());
        user.setDepartment(department);

        List<Role> roles = new ArrayList<>();
        roles.add(role);

        user.setRoles(roles);

        userRedis.delete(this.getClass().getName() + ":userByName:" + user.getName());
        userRedis.add(this.getClass().getName() + ":userByName:" + user.getName(), 10L, user);

    }

    @Test
    public void get() {
        User user = userRedis.get(this.getClass().getName() + ":userByName:user");
        Assert.notNull(user);
        logger.info("======user====== name:{}, department:{}, role:{}",
                user.getName(), user.getDepartment().getName(), user.getRoles().get(0).getName());
    }
}

**提示:**可以使用RedisStudio進行可視化監控

要運行這個測試程序,可以在IDEA的Run/Debug Configuration配置中增加一個JUnit配置項目,模塊選擇redis,工作目錄選擇模塊所在的根目錄,類選擇這個測試程序即dbdemo.redis.test.RedisTest,並將配置保存爲redistest。

使用Debug方式運行測試項目redistest。如果測試通過,會輸出一個用戶的用戶名、所屬部門和擁有角色等簡要信息,如下所示:

INFO dbdemo.redis.test.RedisTest - ======user====== name:user, department:開發部, role:admin

對於Redis的使用,還可以將註解方式與調用數據庫的方法相結合,那樣就不用再編寫像上面那樣的服務類,並且使用起來更加簡單,這將在後面的章節中介紹。

2.3 使用MongoDB

在當前流行的NoSQL數據庫中,MongoDB是大家接觸比較早而且用得比較多的數據庫。MongoDB是文檔型的NoSQL數據庫,具有大數據量、高併發等優勢,但缺點是不能建立實體關係,而且也沒有事務管理機制。

2.3.1 MongoDB依賴配置

在Spring Boot中使用MongoDB也像使用JPA一樣容易,並且同樣擁有功能完善的資源庫。同樣的,要使用MongoDB,首先必須在工程的Maven中引入它的依賴,如代碼清單2-14所示。除了MongoDB本身的依賴之外,還需要一些附加的工具配套使用。

<dependencies>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-mongodb</artifactId>
    </dependency>
    <dependency>
        <groupId>org.pegdown</groupId>
        <artifactId>pegdown</artifactId>
        <version>1.4.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-hateoas</artifactId>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
    </dependency>
</dependencies>

2.3.2 文檔建模

MongoDB是文檔型數據庫,使用MongoDB也可以像使用關係型數據庫那樣爲文檔建模。如代碼清單2-15所示,爲用戶文檔建模,它具有用戶名、密碼、用戶名稱、郵箱和註冊日期等字段,有一個用來保存用戶的數據集,還定義了一個構造函數,可以很方便地用來創建一個用戶實例。

package dbdemo.mongo.models;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.PersistenceConstructor;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

@Document(collection = "user")
public class User {
    @Id
    private String userId;
    @NotNull
    @Indexed(unique = true)
    private String username;
    @NotNull
    private String password;
    @NotNull
    private String name;
    @NotNull
    private String email;
    @NotNull
    private Date registrationDate = new Date();
    private Set<String> roles = new HashSet<>();

    public User() {
    }

    @PersistenceConstructor
    public User(String userId, String username, String password, String name, String email,
                Date registrationDate, Set<String> roles) {
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.name = name;
        this.email = email;
        this.registrationDate = registrationDate;
        this.roles = roles;

    }

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getName() {
        return name;
    }

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

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Date getRegistrationDate() {
        return registrationDate;
    }

    public void setRegistrationDate(Date registrationDate) {
        this.registrationDate = registrationDate;
    }

    public Set<String> getRoles() {
        return roles;
    }

    public void setRoles(Set<String> roles) {
        this.roles = roles;
    }

    @Override
    public String toString() {
        return "User{" +
                "userId=" + userId +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", name='" + name + '\'' +
                ", email='" + email + '\'' +
                ", registrationDate=" + registrationDate +
                ", roles=" + roles +
                '}';
    }

}

2.3.3 文檔持久化

MongoDB也有像使用JPA那樣的資源庫,如代碼清單2-16所示,爲用戶文檔創建了一個Repository接口,繼承於MongoRepository,實現了文檔持久化。

package dbdemo.mongo.repositories;


import dbdemo.mongo.models.User;
import org.springframework.data.mongodb.repository.MongoRepository;


public interface UserRepository extends MongoRepository<User, String> {
    User findByUsername(String username);
}

MongoRepository的繼承關係如圖2-4所示,看起來跟JPA的資源庫的繼承關係沒有什麼兩樣,它也包含訪問數據庫的豐富功能。

graph TD A[Repository] -->B[CrudRepository] B --> C[PagingAndSortingRepository] C --> D[MongoRepository]

代碼清單2-17是用在測試中的使用MongoDB的一個配置類定義,其中@PropertySource指定讀取數據庫配置文件的位置和名稱,@EnableMongoRepositories啓用資源庫並設定定義資源庫接口放置的位置,這裏使用環境變量Environment來讀取配置文件的一些數據庫配置參數,然後使用一個數據庫客戶端,連接MongoDB服務器。

package dbdemo.mongo.test;

import com.mongodb.Mongo;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;

import java.util.ArrayList;
import java.util.List;


@Configuration
@EnableMongoRepositories(basePackages = "dbdemo.mongo.repositories")
@PropertySource("classpath:test.properties")
public class TestDataSourceConfig extends AbstractMongoConfiguration {

    @Autowired
    private Environment env;

    @Override
    public String getDatabaseName() {
        return env.getRequiredProperty("mongo.name");
    }

    @Override
    @Bean
    public Mongo mongo() throws Exception {
        ServerAddress serverAddress = new ServerAddress(env.getRequiredProperty("mongo.host"));
        List<MongoCredential> credentials = new ArrayList<>();
        return new MongoClient(serverAddress, credentials);
    }

}

2.3.4 MongoDB測試

如果還沒有安裝MongoDB服務器,可以參照附錄B的方法安裝並啓動一個Mon共DB服務器。然後,使用如代碼清單2-18所示的配置方法配置連接服務器的一些參數,該配置假定你的MongoDB服務器安裝在本地,並使用默認的數據庫端口:27017。

# MongoDB
mongo.username=test
mongo.password=test
mongo.host=localhost
mongo.name=test
mongo.port=27017

這樣就可以編寫一個JUnit測試例子來測試UserRepository接口的使用情況,如代碼清單2-19所示。測試例子首先使用用戶文檔類創建一個用戶對象實例,然後使用資源接口調用save方法將用戶對象保存到數據庫中,最後使用findAll方法查詢所有用戶的列表,並使用一個循環輸出用戶的簡要信息。

package dbdemo.mongo.test;


import dbdemo.mongo.models.User;
import dbdemo.mongo.repositories.UserRepository;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;

import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {TestDataSourceConfig.class})
@FixMethodOrder
public class RepositoryTests {
    private static Logger logger = LoggerFactory.getLogger(RepositoryTests.class);
	
    @SuppressWarnings("SpringJavaAutowiringInspection")
    @Autowired
    UserRepository userRepository;

    @Before
    public void setup() {
        Set<String> roles = new HashSet<>();
        roles.add("manage");
        User user = new User("1", "user", "12345678", "name", "[email protected]", new Date(), roles);
        userRepository.save(user);
    }

    @Test
    public void findAll() {
        List<User> users = userRepository.findAll();
        Assert.notNull(users);
        for (User user : users) {
            logger.info("===user=== userid:{}, username:{}, pass:{}, registrationDate:{}",
                    user.getUserId(), user.getName(), user.getPassword(), user.getRegistrationDate());
        }
    }
}

//	> db.user.find()
//	{ "_id" : "1", "_class" : "dbdemo.mongo.models.User", "username" : "user", "pass
//		word" : "12345678", "name" : "name", "email" : "[email protected]", "registrationDate
//		" : ISODate("2016-04-13T06:27:02.423Z"), "roles" : [ "manage" ] }

現在可以在IDEA的Run/Debug Configuration配置中增加一個JUnit測試項目,模塊選擇mongodb,工作目錄選擇模塊所在的工程根目錄,類選擇上面編寫的測試例子,即dbdemo.mongo.test.RepositoryTests,並將配置保存爲mongotest。

使用Debug方式運行測試項目mongotest。如果通過測試,將輸出查到的用戶的簡要信息,如下所示:

dbdemo.mongo.test.RepositoryTests - ===user=== userid:1, username:name, pass:12345678, registrationDate: Tue Jun 07 14:26:02 CST 2016

這時使用MongoDB數據庫客戶端輸入下面的查詢指令,也可以查到這條文檔的詳細信息,這是一條JSON結構的文本信息。

> db.user.find()
{ "_id" : "1", "_class" : "dbdemo.mongo.models.User", "username" : "user", "password" : "12345678", "name" : "name", "email" : "[email protected]", "registrationDate", ISODate("2016-04-13T06:27:02.423Z"), "roles" : ["mange"] }

2.4 使用Neo4j

有沒有既具有傳統關係型數據庫的優點,又具備NoSQL數據庫優勢的一種數據庫呢?Neo4j就是一種這樣的數據庫。Neo4j是一個高性能的NoSQL圖數據庫,並且具備完全事務特性。Neo4j將結構化數據存儲在一張圖上,圖中每一個結點的屬性表示數據的內容,每一條有向邊表示數據的關係。Neo4j沒有表結構的概念,它的數據用結點的屬性來表示。

2.4.1 Neo4j依賴配置

在Spring Boot中使用Neo4j非常容易,因爲有spring-data-neo4j提供了強大的支持。首先,在工程的Maven管理中引入Neo4j的相關依賴,如代碼清單2-20所示。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-rest</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.data</groupId>
        <artifactId>spring-data-neo4j</artifactId>
        <version>4.0.0.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.voodoodyne.jackson.jsog</groupId>
        <artifactId>jackson-jsog</artifactId>
        <version>1.1</version>
        <scope>compile</scope>
    </dependency>
</dependencies>

2.4.2 結點和關係實體建模

雖然Neo4j沒有表結構的概念,但它有結點和關係的概念。例如,現在有演員和電影兩個實體,它們的關係表現爲一個演員在一部電影中扮演一個角色。那麼就可以創建演員和電影兩個節點實體,和一個角色關係實體。它們的實體-關係模型如圖2-5所示。這個實體-關係模型的定義比起關係型數據庫的實體-關係模型的定義要簡要得多,但是它更加形象和貼切地表現了實體之間的關係。更難能可貴的是,這個實體-關係模型是可以不經過任何轉換而直接存入數據庫的,也就是說,在Neo4j圖數據庫中保存的數據與圖2-5所示的相同,它仍然是一張圖。這對於業務人員和數據庫設計人員來說,它的意義相同。所以使用Neo4j數據庫,將在很大程度上減輕了設計工作和溝通工作。

像JPA使用了ORM意義,Neo4j使用了對象-圖形映射(Object-Graph Mapping,OGM)的方式來建模。代碼清單2-21是演員節點實體建模,使用註解@JsonIdentityInfo是防止查詢數據庫時引發遞歸訪問效應,註解@NodeEntity標誌這個類是一個節點實體,註解@GraphId定義了節點的一個唯一性標識,它將在創建結點時由系統自動生成,所以它是不可缺少的。這個節點預定義了其他兩個屬性,name和born。節點的屬性可以隨需要增加或減少,這並不影響結點的使用。

package dbdemo.neo4j.domain;


import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.NodeEntity;

@JsonIdentityInfo(generator = JSOGGenerator.class)
@NodeEntity
public class Actor {
    @GraphId
    Long id;
    private String name;
    private int born;

    public Actor() {
    }

    public String getName() {
        return name;
    }

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

    public void setBorn(int born) {
        this.born = born;
    }

    public int getBorn() {
        return born;
    }

}

代碼清單2-22是電影節點實體建模,註解@Realtionship表示List<Role>是一個關係列表,其中type設定了關係的類型,direction設定這個關係的方向,Relationship.INCOMING表示以這個節點爲終點。addRole定義了增加一個關係的方法。

package dbdemo.neo4j.domain;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.NodeEntity;
import org.neo4j.ogm.annotation.Relationship;

import java.util.ArrayList;
import java.util.List;

@JsonIdentityInfo(generator = JSOGGenerator.class)
@NodeEntity
public class Movie {
    @GraphId
    Long id;
    String title;
    String year;

    @Relationship(type = "ACTS_IN", direction = Relationship.DIRECTION)
    List<Role> roles = new ArrayList<>();

    public Role addRole(Actor actor, String name) {
        Role role = new Role(actor, this, name);
        this.roles.add(role);
        return role;
    }

    public Movie() {
    }

    public Long getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

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

代碼清單2-23是角色的關係實體建模,註解@RelationshipEntity表明這個類是一個關係實體,並用type指定了關係的類型,其中@StartNode指定起始節點的實體,@EndNode指定終止節點的實體,這說明了圖中一條有向邊的起點和終點的定義。其中定義了一個創建關係的構造函數Role(Actor actor, Movie movie, String name),這裏的name參數用來指定這個關係的屬性。

package dbdemo.neo4j.domain;


import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import org.neo4j.ogm.annotation.EndNode;
import org.neo4j.ogm.annotation.GraphId;
import org.neo4j.ogm.annotation.RelationshipEntity;
import org.neo4j.ogm.annotation.StartNode;

@JsonIdentityInfo(generator = JSOGGenerator.class)
@RelationshipEntity(type = "ACTS_IN")
public class Role {
    @GraphId
    Long id;
    String role;
    @StartNode
    Actor actor;
    @EndNode
    Movie movie;

    public Role() {
    }

    public Role(Actor actor, Movie movie, String name) {
        this.actor = actor;
        this.movie = movie;
        this.role = name;
    }

    public Long getId() {
        return id;
    }

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

    public String getRole() {
        return role;
    }

    public void setRole(String role) {
        this.role = role;
    }

    public Actor getActor() {
        return actor;
    }

    public Movie getMovie() {
        return movie;
    }

}

2.4.3 節點實體持久化

像對其他數據庫的訪問和存取等操作一樣,spring-data-neo4j提供了功能豐富的資源庫可供調用,因此,對於演員和電影節點實體,可以創建它們對應的資源庫接口,實現實體的持久化。代碼清單2-24是電影資源庫接口的定義,它繼承於GraphRepository接口,實現了電影實體的持久化。使用相同方法可以對演員的節點實體實現持久化。關係實體卻不用實現持久化,當保存結點實體時,結點實體的關係將會同時保存。

package dbdemo.neo4j.repositories;

import dbdemo.neo4j.domain.Movie;
import org.springframework.data.neo4j.repository.GraphRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface MovieRepository extends GraphRepository<Movie> {
    Movie findByTitle(@Param("title") String title);
}

其中GraphRepository接口的繼承關係也遵循了Spring Boot 資源庫定義的規則,即使用與JPA相同的標準規範,所以它同樣包含使用數據庫的豐富功能,如圖2-6所示。

2.4.4 Neo4j測試

代碼清單2-24是Neo4j的數據庫配置類,其中@EnableTransactionManagement啓用了事務管理,@EnableNeo4jRepositories啓用了Neo4j資源庫並指定了我們定義的資源庫接口的位置,在重載的SessionFactory函數中設定了定義實體的位置,這將促使定義的實體被作爲域對象導入,RemoteServer設定連接Neo4j服務器的URL、用戶名和密碼,這些參數要依據安裝Neo4j服務器的情況來設置。如果沒有安裝Neo4j服務器,可參考附錄A的方法進行安裝,安裝完成後啓動服務器已備使用。

package dbdemo.neo4j.config;

import org.neo4j.ogm.session.SessionFactory;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.Neo4jConfiguration;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;


@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(basePackages = {"dbdemo.neo4j.repositories"})
public class Neo4jConfig extends Neo4jConfiguration {
//SDN 升級到4.1.5,連接服務器的配置改在ogm.properties中設定,這樣可以訪問Neo4j 2.x 到 3.x 版本
//    @Override
//    public Neo4jServer neo4jServer() {
//        return new RemoteServer("http://192.168.1.221:7474","neo4j","12345678");
//    }

    @Override
    public SessionFactory getSessionFactory() {
        return new SessionFactory("dbdemo.neo4j.domain");
    }
}

現在可以編寫一個測試程序來驗證和演示上面編寫的代碼的功能,如代碼清單2-26所示。這個測試程序分別創建了三部電影和三個演員,以及三個演員在三部電影中各自扮演的角色,然後按照電影標題查出一部電影,按照其內在的關係輸出這部電影的信息和每個演員扮演的角色。這些數據的內容參照了Neo4j幫助文檔中提供的示例數據。

package dbdemo.neo4j.test;

import dbdemo.neo4j.config.Neo4jConfig;
import dbdemo.neo4j.domain.Actor;
import dbdemo.neo4j.domain.Movie;
import dbdemo.neo4j.domain.Role;
import dbdemo.neo4j.repositories.ActorRepository;
import dbdemo.neo4j.repositories.MovieRepository;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.Assert;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {Neo4jConfig.class})
public class MovieTest {
    private static Logger logger = LoggerFactory.getLogger(MovieTest.class);

    @Autowired
    MovieRepository movieRepository;
    @Autowired
    ActorRepository actorRepository;

    @Before
    public void initData() {
        movieRepository.deleteAll();
        actorRepository.deleteAll();

        Movie matrix1 = new Movie();
        matrix1.setTitle("The Matrix");
        matrix1.setYear("1999-03-31");

        Movie matrix2 = new Movie();
        matrix2.setTitle("The Matrix Reloaded");
        matrix2.setYear("2003-05-07");

        Movie matrix3 = new Movie();
        matrix3.setTitle("The Matrix Revolutions");
        matrix3.setYear("2003-10-27");

        Actor keanu = new Actor();
        keanu.setName("Keanu Reeves");

        Actor laurence = new Actor();
        laurence.setName("Laurence Fishburne");

        Actor carrieanne = new Actor();
        carrieanne.setName("Carrie-Anne Moss");

        matrix1.addRole(keanu, "Neo");
        matrix1.addRole(laurence, "Morpheus");
        matrix1.addRole(carrieanne, "Trinity");
        movieRepository.save(matrix1);
        Assert.notNull(matrix1.getId());

        matrix2.addRole(keanu, "Neo");
        matrix2.addRole(laurence, "Morpheus");
        matrix2.addRole(carrieanne, "Trinity");
        movieRepository.save(matrix2);
        Assert.notNull(matrix2.getId());

        matrix3.addRole(keanu, "Neo");
        matrix3.addRole(laurence, "Morpheus");
        matrix3.addRole(carrieanne, "Trinity");
        movieRepository.save(matrix3);
        Assert.notNull(matrix3.getId());
    }

    @Test
    public void get() {
        Movie movie = movieRepository.findByTitle("The Matrix");
        Assert.notNull(movie);
        logger.info("===movie=== movie:{}, {}", movie.getTitle(), movie.getYear());
        for (Role role : movie.getRoles()) {
            logger.info("====== actor:{}, role:{}", role.getActor().getName(), role.getRole());
        }
    }
}

在IDEA的Run/Debug Configuration配置中增加一個JUnit的配置項目,模塊選擇neo4j,工作目錄選擇模塊所在的根目錄,測試程序選擇MovieTest這個類,並將配置保存爲neo4jtest。

使用Debug模式運行測試項目neo4jtest,如果測試通過,將在控制檯中看到輸出查詢的這部電影和所有演員及其扮演的角色,如下所示:

=== movie === movie:The Matrix, 1999-03-31
===== actor:keanu Reeves, role:Neo
===== actor:Laurence Fishburne, role:Morpheus
===== actor:Carrie-Anne Moss, role:Trinty

這時,在數據庫客戶端的控制檯上,單擊左側欄的關係類型ACTS_IN,可以看到一個很酷的圖形,圖中每部電影和每個演員是一個節點,節點的每條有向邊代表了這個演員在那部電影中扮演的角色,如圖2-7所示。

2.5 小結

這一章,我們一口氣學習使用了4種數據庫:MySQL、Redis、MonogoDB、Neo4j,除了Redis以外,都使用了由Spring Boot提供的資源庫來訪問數據庫並對數據庫執行了一般的存取操作。可以看出,在Spring Boot框架中使用數據庫非常簡單、容易,這主要得益於Spring Boot資源庫的強大功能,Spring Boot資源庫整合了第三方資源,它把複雜的操作變成簡單的調用,它把所有“辛苦、繁重的事情”都包攬了,然後將“微笑和鮮花”獻給了我們。爲此,我們應該說聲謝謝,謝謝開發Spring Boot框架及所有第三方提供者的程序員們,因爲有了他們辛勤的付出,纔有了我們今天使用上的便利。

本章實例的完整代碼可以在IDEA中直接從GitHub中檢出。

本章實例都是使用JUnit的方式來驗證的,爲了能使用友好的界面來運行應用,下一章將介紹如何使用Thymeleaf來進行界面設計。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章