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ù)中。
- 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;
}
}
- 實(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);
- 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);
}
}
}