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)不用編寫原始的訪問(wèn)數(shù)據(jù)庫(kù)的代碼,也不用調(diào)用JDBC(Java Data Base Connectivity)或者連接池等諸如此類的被稱作底層的代碼霸奕,我們將在高級(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í)體:部門毡泻、用戶和角色,并且它們具有一定的關(guān)系粘优,即一個(gè)用戶只能隸屬于一個(gè)部門仇味,一個(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)生成带到。部門實(shí)體只有兩個(gè)字段:id和name。
部門實(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定義它與部門的多對(duì)一關(guān)系,并且在數(shù)據(jù)庫(kù)表中用字段did來(lái)表示部門的ID四康,注解@ManyToMany定義與角色實(shí)體的多對(duì)多關(guān)系搪搏,并且用中間表user_role來(lái)存儲(chǔ)它們各自的ID,以表示它們的對(duì)應(yīng)關(guān)系闪金。日期類型的數(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ù)的功能套利。 使用相同的方法,可以定義部門實(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)有寫财著,使用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í)體類中的屬性字段(首個(gè)字母大寫)扣泊,并可選擇拼接一些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í)體類自定義的方法聲明,它們都是符合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的配置類廊佩,其中@EnableTransac-tionManagement啟用了JPA的事務(wù)管理;@EnableJpaRepositories啟用了JPA資源庫(kù)并指定了上面定義的接口資源庫(kù)的位置贺拣;@EntityScan指定了定義實(shí)體的位置蓖谢,它將導(dǎo)入我們定義的實(shí)體。注意譬涡,在測(cè)試時(shí)使用的JPA配置類可能與這個(gè)配置略有不同闪幽,這個(gè)配置的一些配置參數(shù)是從配置文件中讀取的,而測(cè)試時(shí)使用的配置類把一些配置參數(shù)都包含在類定義中了涡匀。
配置類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)的同步更新底挫。
??最后恒傻,編寫一個(gè)測(cè)試程序,測(cè)試程序首先初始化數(shù)據(jù)庫(kù)建邓,創(chuàng)建一個(gè)部門盈厘,命名為“開(kāi)發(fā)部”,創(chuàng)建一個(gè)角色官边,命名為admin沸手,創(chuàng)建一個(gè)用戶,命名為user注簿,同時(shí)將它的所屬部門設(shè)定為上面創(chuàng)建的部門契吉,并將現(xiàn)有的所有角色都分配給這個(gè)用戶。然后使用分頁(yè)的方式查詢所有用戶的列表诡渴,并從查到的用戶列表中捐晶,打印出用戶的名稱、部門的名稱和第一個(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);
}
}
}