Eureka

Spring Cloud Eureka是spring cloud 微服务套件中的一部分,她基于Netflix Eureka做了二次封装,主要负责完成微服务架构中的服务治理功能

在了解Eureka之前,我们先来讲下服务发现的由来

1 服务发现的由来

服务发现及注册中心是与软件开发的架构方式演化而来的,大概归纳如下

1.1 单体架构

最早的开发中,大多使用单体架构,服务自成一体。对于依赖的少数外部服务,会采用配置地址的方式进行访问。比如第三方接口,会使用appId和appKey来进行调用。

1.2 SOA架构

随着SOA架构流行,有些公司的内部开始将单体应用拆分成粒度较为粗的服务化架构。

查看: https://blog.csdn.net/chenleixing/article/details/44926955

1.3 微服务时代

在微服务时代,底层运维方式发生了很大的变化,并且随着docker和kubernetes的流行,业务服务不在部署在固定的虚拟机上,其ip地址也不再固定,这个时候前面的解决方式就显得有点儿吃力了。针对这个问题,提出了一些解决方案,例如

1:以nginx为例,在没有引入服务注册中心的时候,那么需要我们手工或是通过脚本的方式,在部署的受去更新nginx的配置文件,然后reload。或是使用ngx_http_dyups_module通过rest api在运行的受进行更新upstream而不需要reload

2: 将服务中心作为一个标配的分布式服务组件,网管等都从服务注册中心获取相关服务的实例信息,实现动态路由。例如consule-template+nginx方案,然后通过consul监听服务实例变化,再更新nginx的配置文件。

从上面的例子我们可以看出,随着服务架构模式以及底层运维方式的变化,服务注册中心逐步再分布式系统架构中占据了一个重要的地位。

2 Eureka简介

Eureka是Netflix公司开源的一款服务发现组件,该组件提供的服务发现可以为负载均衡、failover等提供了支持。Eureka包括Eureka Server和Eureka Client。Eureka Server提供了RESTFul服务,而Eureka Client则是使用Java编写的客户端,用于简化Eureka Server的交互。

2.1 服务发现技术选型

名称 类型 AP或CP 语言 依赖 集成 一致性算法
Zookeeper General CP Java JVM Client Binding Paxos
Doozer General CP Go Client Binding Paxos
Consul General CP GO HTTP/DNS Library Raft
Etcd General Mixed (1) Go Client Binding/HTTP Raft
SmartStack Dedicated AP Ruby haproxy/Zookeeper Sidekick (nerve/synapse)
Eureka Dedicated AP Java JVM Java Client
NSQ (lookupd) Dedicated AP Go Client Binding
Serf Dedicated AP Go Local CLI
Spotify (DNS) Dedicated AP N/A Bind DNS Library
SkyDNS Dedicated Mixed (2) Go HTTP/DNS Library

CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得 。

查看:http://www.ruanyifeng.com/blog/2018/07/cap.html

img

从列表中我们可以看出,有很多服务组件可以选择,针对AP及CP这里我们主要选择Eureka来进行讲解

EurekaServer采用P2P(Peer To Peer)模式https://www.cnblogs.com/linsanshu/p/5546948.html,不保证复制操作一定能成功,因此提供的是一个最终一致性的服务实例视图。Client端再Server端的注册信息有一个带期限的租约,一旦Server端再指定期间没有收到Client端发送的心跳,则Server端会认为Client端注册的服务是不健康的,定时任务会将从注册表中删除。

那么我们为什么要选择Eureka呢?主要有以下几点

  • Eureka是基于AP而不是CP
  • 团队是Java开发,除了问题好排查
  • Eureka能和zuul ribbon整合性好

3 Eureka入门

eureka配置参数

配置参数 默认值 说明
服务注册中心配置 Bean类:org.springframework.cloud.netflix.eureka.server.EurekaServerConfigBean
eureka.server.enable-self-preservation false 关闭注册中心的保护机制,Eureka 会统计15分钟之内心跳失败的比例低于85%将会触发保护机制,不剔除服务提供者,如果关闭服务注册中心将不可用的实例正确剔除
服务实例类配置 Bean类:org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean
eureka.instance.prefer-ip-address false 不使用主机名来定义注册中心的地址,而使用IP地址的形式,如果设置了eureka.instance.ip-address 属性,则使用该属性配置的IP,否则自动获取除环路IP外的第一个IP地址
eureka.instance.ip-address IP地址
eureka.instance.hostname 设置当前实例的主机名称
eureka.instance.appname 服务名,默认取 spring.application.name 配置值,如果没有则为 unknown
eureka.instance.lease-renewal-interval-in-seconds 30 定义服务续约任务(心跳)的调用间隔,单位:秒
eureka.instance.lease-expiration-duration-in-seconds 90 定义服务失效的时间,单位:秒
eureka.instance.status-page-url-path /info 状态页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.status-page-url 状态页面的URL,绝对路径
eureka.instance.health-check-url-path /health 健康检查页面的URL,相对路径,默认使用 HTTP 访问,如果需要使用 HTTPS则需要使用绝对路径配置
eureka.instance.health-check-url 健康检查页面的URL,绝对路径
服务注册类配置 Bean类:org.springframework.cloud.netflix.eureka.EurekaClientConfigBean
eureka.client.service-url. 指定服务注册中心地址,类型为 HashMap,并设置有一组默认值,默认的Key为 defaultZone;默认的Value为 http://localhost:8761/eureka ,如果服务注册中心为高可用集群时,多个注册中心地址以逗号分隔。如果服务注册中心加入了安全验证,这里配置的地址格式为:http://:@localhost:8761/eureka 其中 为安全校验的用户名; 为该用户的密码
eureka.client.fetch-registery true 检索服务
eureka.client.registery-fetch-interval-seconds 30 从Eureka服务器端获取注册信息的间隔时间,单位:秒
eureka.client.register-with-eureka true 启动服务注册
eureka.client.eureka-server-connect-timeout-seconds 5 连接 Eureka Server 的超时时间,单位:秒
eureka.client.eureka-server-read-timeout-seconds 8 读取 Eureka Server 信息的超时时间,单位:秒
eureka.client.filter-only-up-instances true 获取实例时是否过滤,只保留UP状态的实例
eureka.client.eureka-connection-idle-timeout-seconds 30 Eureka 服务端连接空闲关闭时间,单位:秒
eureka.client.eureka-server-total-connections 200 从Eureka 客户端到所有Eureka服务端的连接总数
eureka.client.eureka-server-total-connections-per-host 50 从Eureka客户端到每个Eureka服务主机的连接总数

3.1 单机版

3.1.1 Eureka Server

3.1.1.1 引入Eureka Server依赖
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
3.1.1.2 配置启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
3.1.1.3 配置properties或yml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#服务名称
spring.application.name=smartke-cloud-eureka-server
#服务端口
server.port=8761
#eureka设置
eureka.instance.hostname=localhost
# 表示是否注册自身到eureka服务器
eureka.client.register-with-eureka=false
# 是否从eureka上获取注册信息
eureka.client.fetch-registry=false
#关闭eureka自我保护机制
eureka.server.enable-self-preservation=false
#剔除失效服务间隔
eureka.server.eviction-interval-timer-in-ms=2000
#设置访问eureka的时候需要使用用户名和密码进行登录
#security.basic.enabled=true
#spring.security.user.name=smartke
#spring.security.user.password=smartke
#日志设置
logging.level.root=error
logging.level.org.springframework=info
logging.file=${spring.application.name}.log

3.1.2 Eureka Client

3.1.2.1 引入依赖
1
2
3
4
5
6
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
3.1.2.2 配置启动类
1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
3.1.2.3 配置properties或yml文件
1
2
3
4
5
6
7
8
9
10
#port
server.port=8002
###application name
spring.application.name=smartke-portal
###eureka
eureka.client.serviceUrl.defaultZone=http://admin:admin@localhost:8761/eureka/
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(客户端告诉服务端自己会按照该规则)默认为30
eureka.instance.lease-renewal-interval-in-seconds=1
#Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒,超过则剔除(客户端告诉服务端按照此规则等待自己)默认为90
eureka.instance.lease-expiration-duration-in-seconds=2

说明一下,这里需要指定spring.application.name不然会再Eureka Server界面显示为UNKNOW

20190106102425

eureka错误解释

自我保护机制

当我们再本地调式基于eureka的程序时,基本上都会碰到这样一个问题,再服务注册中心的信息面板中出现以下红色的警告信息

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

实际上,该警告触发了eureka server的自我保护机制。服务注册到eureka server之后,会维护一个心跳连接,告诉eureka server自己还活着。eureka server再运行期间,会统计心跳失败比例再15分钟之内是否低于85%,如果出现低于的情况(在本地单机调试的时候十分容易满足,生产环境的情况下,通常是由于网络不稳定导致),eureka server 会将当前的实例注册信息保护起来,让这些实例不会过期,尽可能保护这些注册信息(这就是为什么选择Eureka,因为eureka是AP的)但是,在这段时间之内实例若出现问题,那么客户端很容易拿到实际上已经不存在的服务实例,会出现调用失败的情况,所以客户端必须要有容错机制,比如可以使用请求重试、断路器等机制。

那么出现上面的情况,我们该怎么解决呢?可以在本地配置文件中加入eureka.server.enable-self-preservation=false参数来关闭,以确保注册中心可以将不可用的实例及时剔除。

关闭自我保护之后,启动eureka server会出现以下提示,可以忽略。

THE SELF PRESERVATION MODE IS TURNED OFF. THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS

3.2 Peer to Peer架构

一般而言,分布式系统的数据在多个副本之间的复制方式,可以分为主从复制和对等复制

3.2.1 主从复制

主从复制就是我们熟知的Master-Slave模式,即有一个主副本,其他副本为从副本。所有对数据的写操作都提交到主副本,最后再由主副本更新到其他从副本。具体更新的方式,还可以细分为同步更新、异步更新、同步及异步混合。

对于主从复制模式来讲,写操作的压力都在主副本上面,她是整个系统的瓶颈,但是从副本可以帮主副本分担读请求。

3.2.2 对等复制

即P2P的模式,副本之间部分主从,任何副本都可以接受写操作,然后每个副本之间相互进行数据更i性能。对于对等复制模式来讲,由于任何副本都可以进行写操作,各个副本之间的数据哦同步及冲突处理是一个比较棘手的问题。

Eureka Server就是采用的Peer to Peer 模式。

Eureka Server从一开始设计就考虑了高可用的问题,在Eureka的服务治理设计中,所有的节点即是服务提供方,又是服务消费方,服务注册中心也不例外。Eureka Server的高可用实际上就是将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组互相注册的服务注册中心,以实现服务清单的互相同步。达到高可用的效果。

3.2.3 代码示例

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.com.bmsmart</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-server</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

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

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
  • 创建application-peer1.properties,作为peer1服务中心的配置,并将serviceUrl指向peer2:
1
2
3
4
5
spring.application.name=eureka-server
server.port=8761

eureka.instance.hostname=peer1
eureka.client.serviceUrl.defaultZone=http://peer2:8762/eureka/
  • 创建 application-peer2.properties,作为peer2服务中心的配置,并将serviceUrl指向peer1
1
2
3
4
5
6
spring.application.name=eureka-server
server.port=8762

eureka.instance.hostname=peer2

eureka.client.serviceUrl.defaultZone=http://peer1:8761/eureka/
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.com.bmsmart.eurekaserver;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}

}
  • 通过spring.profiles.active属性来分别启动peer1和peer2
1
2
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar eureka-server-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer2

此时访问peer1的注册中心http://localhost:8761,我们可以看到`registered-replicas`中有peer2的节点`http://peer2:8762/eureka/` 同样访问peer2 http://lcoalhost:8762也能看到`registered-replicas`中有peer1的节点`http://peer1:8761/eureka/`

1546746477978

20190106114838

eureka-client

依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.7.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.com.bmsmart</groupId>
<artifactId>eureka-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-client</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR2</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>

</project>
  • 配置文件
1
2
3
4
5
6
7
8
9
10
11
spring.application.name=eureka-client
server.port=8080
eureka.client.serviceUrl.defaultZone=http://peer1:8761/eureka/,http://peer2:8762/eureka/
logging.file=${spring.application.name}.log
eureka.instance.prefer-ip-address=true
# 健康检查
#eureka.client.healthcheck.enabled=true
# 随机端口配置
#eureka.instance.instance-id=${spring.application.name}:${random.int}
#server.port=0
#server.port=${random.int[10000,19999]}
  • 启动类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.com.bmsmart.eurekaclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
@EnableEurekaClient
public class EurekaClientApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}

jdk1.8新特性之lambda 表达式

1.使用()->{} 代替匿名内部类

允许把函数作为一个方法参数传入,使得代码变得更加简洁。强大的函数式编程。

减少代码冗余量,可读性强。

语法

lambda 表达式的语法格式如下:

(parameters) -> expression 或 (parameters) ->{ statements; }

2.方法引用

通过方法的名字来指向一个方法 ::

3.函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

1
@FunctionalInterface----  主要用于编译级错误检查

 定义:“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。jdk1.8提供了一个@FunctionalInterface注解来定义函数式接口,如果我们定义的接口不符合函数式的规范便会报错。

若一个接口中包含多个抽象方法,是没有办法通过表达式左侧的参数列表来定位到对应的方法。

类型推断机制的加入,java编译器可以根据上下文在后台推断出参数的类型,故无需再参数列表中制定参数类型。

函数式编程可以友好的进行

Predicate 接口是函数式接口,它接受一个输入参数T,返回一个布尔值结果

4.默认方法

jdk1.8新增默认方法,就是接口中可以有实现方法,而且不需要实现类去实现其方法。

只需要在方法前加上default关键字即可实现默认方法。

静态默认方法

Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法

为什么要有这个特性?

首先,之前的接口是个双刃剑,好处是面向抽象而不是面向具体编程,缺陷是,当需要修改接口时候,需要修改全部实现该接口的类,目前的 java 8 之前的集合框架没有 foreach 方法,通常能想到的解决办法是在JDK里给相关的接口添加新的方法及实现。然而,对于已经发布的版本,是没法在给接口添加新方法的同时不影响已有的实现。所以引进的默认方法。他们的目的是为了解决接口的修改与现有的实现不兼容的问题。

5.Stream 特性

新的抽象称为Stream ,可以以一种声明方式处理数据。

理解:

1.Stream是元素的集合,看起来类似Iterator

2.可以支持顺序和并行的对原Stream进行汇聚的操作;

Stream API 可以极大的提高java程序员的生产力,让程序员写出高效率,干净,简洁的代码。

筛选 排序 聚合等操作

常用方法:

1.生成流

stream() - 为集合创建串行流

parallelStream() - 为集合创建并行流

2.forEach()

迭代流中的每个元素

3.map

map 方法用于映射每个元素到对应的结果,

4.filter

filter 方法用于通过设置的条件过滤出元素。以下代码片段使用 filter 方法过滤出空字符串:

5.limit

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据

6.sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:

7.Collectors

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

6.base64

7.java api 新的日期时间API

什么是Spring-boot

设计目的是为了简化新spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,而使开发人员不需要定义样板化的配置。只是默认配置了很多框架的使用方式,类似maven整合了所有的jar包。

使用spring-boot的优势

  1. 简化编码:可以快速开启一个web容器

  2. 简化配置:java ee轻量级框架,但是其配置比较繁琐。spring boot 更多的采用java config的方式,对spring进行配置。

  3. 简化部署:spring boot 内嵌tomcat,只需要将项目打成jar包,使用java -jar xxx.jar 一键启动项目即可。

  4. 简化监控:使用spring-boot-start-actuator依赖,直接使用REST方式来获取进程的运行期性能参数,从而达到监控的目的。

  5. 但是spring -boot只是微架构,没有提供相应的服务发现和注册的配套功能,没有外围监控集成方案,没有外围的安全管理方案,所以在微服务架构中,还需要spring cloud配套使用。

    未来的发展趋势

    微服务是未来发展的趋势,项目会从传统架构慢慢转向微服务架构,使用独立的技术,更安全更频繁的部署。

spring-boot的三种启动方式

Spring: the source for modern java

摘自官网https://spring.io/

spring-modern-source

Spring Boot可以让我们轻松的创建独立的,生产级的应用程序,使程序变得更加轻量化。你可以 “just run”。你可以依靠一个Java的main函数来运行一个Spring应用。你也可以把你的应用打包为一个jar文件,通过java -jar来运行你的Spring程序。大多数Spring Boot应用程序只需要很少的Spring配置。

特点:

  • 创建独立的Spring应用程序
  • 内嵌Tomcat、Jetty或者Undertow(无需单独部署war文件)
  • 开箱即用,提供各种默认配置来简化项目配置
  • 没有冗余的代码生成也不需要XML配置
  • 尽可能的自动化配置Spring以及第三方库
  • 提供生产监控功能,例如metrics(指标)、health checks(运行状况检查)和externalized configuration(外部配置)

快速入门

先决条件

jdk 1.8

spring framework 5.x以上

使用maven构建项目

1 访问https://start.spring.io/

2 选择Maven Project、Spring Boot版本2.0.7选择依赖,这里选择web,可参考下图所示

2019-01-06_225027

3 点击Generate Project下载demo压缩包

将zip包解压缩导入IntelliJ IDEA中

项目结构解析

2019-01-06_230659

通过上面一系列的操作,我们已经成功的创建了一个Spring Boot程序。Spring Boot的基础结构共三个文件

  • src/main/java程序入口:DemoApplicatio
  • src/main/resources下的配置文件:application.properties
  • src/test/测试入口:DemoApplicationTests

生成的DemoApplicatioDemoApplicationTests类可以直接运行来启动当前创建的项目,由于目前该项目未配合Web模块,所以程序会在加载完Spring之后结束运行。

添加web模块以及单元测试模块的maven依赖

pom.xml文件中仅需要引入两个模块就行

  • spring-boot-starter-web:核心模块,包括自动配置支持、日志和YAML
  • spring-boot-starter-test:测试模块,包括JUnit、Hamcrest、Mockito
1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

编写controller

  • 创建cn.com.bmsmart.controller
  • 创建HelloController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.com.bmsmart.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* <p>Description: </p>
* Created by wusong on 2019-01-07 6:55.
*/
@RestController
public class HelloController {

@GetMapping(value = "/")
public String sayHello() {
return "Hello Spring Boot";
}
}

右键DemoApplication启动main方法,然后打开浏览器,输入http://localhost:8080/可以看到输出了Hello Spring Boot

2019-01-07_070403

编写单元测试

打开的工程的src/test/下的测试入口DemoApplicationTests类。然后编写一个单元测试来mock http请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package cn.com.bmsmart.demo;

import cn.com.bmsmart.demo.controller.HelloController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTests {

@Autowired
private HelloController helloController;

private MockMvc mvc;


@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(helloController).build();
}

@Test
public void contextLoads() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().string(equalTo("Hello Spring Boot")));
}

}

通过注入HelloController然后在@Before函数中把mock的对象HelloController传递到MockMvcBuilders.standaloneSetup(helloController).build()中,这样就可以构建我们的单元测试了。

  • 注意引入静态类,让statuscontentequalTo函数可用
1
2
3
import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

测试结果

2019-01-07_071527

另外一种用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package cn.com.bmsmart.demo;

import cn.com.bmsmart.demo.controller.HelloController;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import static org.hamcrest.Matchers.equalTo;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DemoApplication.class)
public class DemoApplicationTests {

@Autowired
private HelloController helloController;

private MockMvc mvc;


@Before
public void setUp() throws Exception {
mvc = MockMvcBuilders.standaloneSetup(helloController).build();
}

@Test
public void contextLoads() throws Exception {
// mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
// .andExpect(status().isOk())
// .andExpect(content().string(equalTo("Hello Spring Boot")));

MvcResult mvcResult = mvc.perform(
MockMvcRequestBuilders.get("/")
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
String content = mvcResult.getResponse().getContentAsString();
System.out.println(content);
}
}

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2019-01-07 07:29:31.555  INFO 7284 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/],methods=[GET]}" onto public java.lang.String cn.com.bmsmart.demo.controller.HelloController.sayHello()
2019-01-07 07:29:31.595 INFO 7284 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.test.web.servlet.setup.StubWebApplicationContext@623ebac7
2019-01-07 07:29:31.616 INFO 7284 --- [ main] o.s.mock.web.MockServletContext : Initializing Spring FrameworkServlet ''
2019-01-07 07:29:31.616 INFO 7284 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization started
2019-01-07 07:29:31.617 INFO 7284 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 1 ms

MockHttpServletRequest:
HTTP Method = GET
Request URI = /
Parameters = {}
Headers = {Accept=[application/json]}
Body = <no character encoding set>
Session Attrs = {}

Handler:
Type = cn.com.bmsmart.demo.controller.HelloController
Method = public java.lang.String cn.com.bmsmart.demo.controller.HelloController.sayHello()

Async:
Async started = false
Async result = null

Resolved Exception:
Type = null

ModelAndView:
View name = null
View = null
Model = null

FlashMap:
Attributes = null

MockHttpServletResponse:
Status = 200
Error message = null
Headers = {Content-Type=[application/json;charset=ISO-8859-1], Content-Length=[17]}
Content type = application/json;charset=ISO-8859-1
Body = Hello Spring Boot
Forwarded URL = null
Redirected URL = null
Cookies = []
Hello Spring Boot

1.什么是Spring Cloud

Spring Cloud是一个中间件,有Spring官方开发维护,基于Spring Boot开发,提供一套完整的微服务解决方案。在Spring Cloud中对微服务基础框架Netflix的多个开源组件进行了封装。Spring Cloud为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。

github地址:https://github.com/spring-cloud

官网地址:https://spring.io/projects/spring-cloud

初学者视频:https://www.youtube.com/watch?v=aO3W-lYnw-o

微服务架构风格是一种将单个应用程序作为一套小型服务开发的方法,每种应用程序都在自己的进程中运行,并与轻量级机制(通常是HTTP资源API)进行通信。 这些服务是围绕业务功能构建的,可以通过全自动部署机制独立部署。 这些服务的集中管理最少,可以用不同的编程语言编写,并使用不同的数据存储技术。

说了这么多概念,微服务有什么样的具体特点呢?

1.独立部署,灵活扩展

传统的单体架构是以整个系统为单位进行部署,而微服务则是以每一个独立组件(例如用户服务,商品服务)为单位进行部署。

用一张经典的图来表现,就是下面这个样子:

img

图中左边是单体架构的集群,右边是微服务集群。

什么意思呢?比如根据每个服务的吞吐量不同,支付服务需要部署20台机器,用户服务需要部署30台机器,而商品服务只需要部署10台机器。这种灵活部署只有微服务架构才能实现。

而近几年流行的Docker,为微服务架构提供了有效的容器。

2.资源的有效隔离

微服务设计的原则之一,就是每一个微服务拥有独立的数据源,假如微服务A想要读写微服务B的数据库,只能调用微服务B对外暴露的接口来完成。这样有效避免了服务之间争用数据库和缓存资源所带来的问题。

img

同时,由于每一个微服务实例在Docker容器上运行,实现了服务器资源(内存、CPU资源等)的有效隔离。

3.团队组织架构的调整

微服务设计的思想也改变了原有的企业研发团队组织架构。传统的研发组织架构是水平架构,前端有前端的团队,后端有后端的团队,DBA有DBA的团队,测试有测试的团队。

img

而微服务的设计思想对团队的划分有着一定的影响,使得团队组织架构的划分更倾向于垂直架构,比如用户业务是一个团队来负责,支付业务是一个团队来负责。

img

当然,这种垂直划分只是一个理想的架构,实际在企业中并不会把团队组织架构拆分得这么绝对

微服务与面向服务架构SOA的区别

img

img

SOA是什么样子呢?可以是下面这样的Web Service:

img

也可以是下面这样的ESB企业服务总线:

img

总之,SOA架构强调的是异构系统之间的通信和解耦合,而微服务架构强调的是系统按业务边界做细粒度的拆分和部署。

img

img

img

微服务架构的不足

img

img

img

img

img

1. == 与equals()的区别

2. 遍历map的几种方法

3.Map与Set的区别

4.java 中常用的集合类型

5.java访问修饰符

6.hashmap 实现的原理

http head 里面的Content-type

1. 简介

post 请求中的Content-type方式一般有三种:

  • application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。

  • multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。

  • text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。

  • form的enctype属性为编码方式,常用有两种:application/x-www-form-urlencodedmultipart/form-data,默认为application/x-www-form-urlencoded

    当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串追加到url后面,用?分割,加载这个新的url。

    当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2…),然后把这个字串追加到url后面,用?分割,加载这个新的url

  • application/json 用来告诉服务器端消息主体是序列化后的json字符串

http状态码

简介

客户端/浏览器接受并显示网页时,此网页所在的服务器会返回一个包含http状态码的信息头用以响应浏览器的请求。 HTTP status code。

下面是常见的HTTP状态码:

​ 200—请求成功

​ 301– 重定向错误(资源被永久移除到其它URL)

​ 404– 请求的网页不存在(客户端错误)

​ 500– 内部服务器错误(服务器错误)

http请求方法

根据http标准,http请求可以使用多种请求方法:

http1.0定义了三种请求方法:GET POST HEAD

http2.0新增了五种请求方法: PUT DELETE COMMECT OPTIONS TANCE

序号 方法 描述
1 GET 请求指定的页面信息,并返回实体主体。
2 HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
3 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
4 PUT 从客户端向服务器传送的数据取代指定的文档的内容。
5 DELETE 请求服务器删除指定的页面。
6 CONNECT HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
7 OPTIONS 允许客户端查看服务器的性能。
8 TRACE 回显服务器收到的请求,主要用于测试或诊断。

http 消息结构

http 是基于客户端/服务器的机构模型,无状态的请求/响应协议。

POST和GET 方法的区别:

POST将请求参数隐藏进行传递,而GET会将参数拼接到请求地址后面传递;

POST相对比较安全而GET因为暴露参数容易被篡改;

根据HTTP传输协议两者对参数长度有不同的要求;

两者在与服务器发生交互时 有所不同,post方法会请求服务端两次,第一次先发送头部进行确认,服务端返回成功后再次发送数据,而GET只需要请求服务器一次。同时强调GET方法中http协议并未做参数长度限制,只是不同浏览器和服务端做了限制而已。

HTTP/1.1 和HTTP /1.0的区别?

面试中的那些坑[答疑 | 篇四]

HTTP 1.0 无状态,无连接

HTTP 1.1 持久连接,请求管道化,增加缓存处理(新的字段cache-control),增加Host字段、支持断点传输(把文件分成几部分)

HTTP 2.0 二进制分帧,多路复用(或连接共享),头部压缩,服务器推送

jap 简介

1
2
java 持久层api是java 规范 用于在java 对象和数据库之间保存数据。 JPA充当面向对象领域和关系数据库之间的桥梁。
JPA 只是一个规范,它本身不执行任何操作。它需要一个实现。因此,像hibernate toplink ibatis 这样的工具具体实现了JPA数据持久性规范。

jpa 发展

  • 2006年 发布1.0版本
  • 2009年 发布2.0版本
  • 2013年 发布2.1版本
  • 2017年 作为开发维护开发而发布

jpa 对象关系映射 ORM

轻松处理各种数据库操作,如插入,更新,删除等。

jpa 实体

实体可以理解为java持久性库中定义的对象。

实体属性

  • 持久性:对象存储在数据库中并且可以随时访问。
  • 事务性:ACID
  • 粒度

jpa创建实体

常用注解

1
2
@Entity --标记这个类是一个实体,该注解必须放在类上
@Id -- 此注释位于持有持久标识属性的特定字段上。该字段被视为数据库中的主键。

spring-data-jpa的使用

简介

spring data jpa 是spring 基于ORM框架,JPA规范的基础上封装的一套jpa应用框架,用极简的代码即可实现对数据的访问和操作。极大的提高开发效率。

1
spring data jpa 让我们脱离Dao层操作,基本上所有的CRUD都可以依赖于它实现。

基本查询

基本查询分为两种,一种是spring data默认已经实现,一种是根据查询的方法来自动解析成SQL。

预先生成方法

spring data jpa 默认预先生成一些基本的CRUD的方法 继承JpaRepository

自定义简单查询

根据方法名来自动生成SQL:

自定义的简单查询就是根据方法名来自动生成SQL,主要的语法是findXXBy,readAXXBy,queryXXBy,countXXBy, getXXBy后面跟属性名称:

1
User findByUserName(String userName);

也使用一些加一些关键字AndOr

1
User findByUserNameOrEmail(String username, String email);

修改、删除、统计也是类似语法

1
2
3
Long deleteById(Long id);

Long countByUserName(String userName)

基本上SQL体系中的关键词都可以使用,例如:LIKEIgnoreCaseOrderBy

1
2
3
4
5
List<User> findByEmailLike(String email);

User findByUserNameIgnoreCase(String userName);

List<User> findByUserNameOrderByEmailDesc(String email);

复杂查询

特殊的方法或者自定义SQL

分页查询

spring data jpa 已经帮我们实现了分页的功能,在查询的方法中,需要传入参数Pageable

Pageable
,当查询中有多个参数的时候Pageable建议做为最后一个参数传入

1
2
3
Page<User> findALL(Pageable pageable);

Page<User> findByUserName(String userName,Pageable pageable);

Pageable 是spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Test
public void testPageQuery() throws Exception {
int page=1,size=10;
Sort sort = new Sort(Direction.DESC, "id");
Pageable pageable = new PageRequest(page, size, sort);
userRepository.findALL(pageable);
userRepository.findByUserName("testName", pageable);
}
限制查询

有时候我们只需要查询前N个元素,或者支取前一个实体。

ser findFirstByOrderByLastnameAsc();

User findTopByOrderByAgeDesc();

Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);

List<User> findFirst10ByLastname(String lastname, Sort sort);

List<User> findTop10ByLastname(String lastname, Pageable pageable);

自定义sql 查询

其实Spring data 觉大部分的SQL都可以根据方法名定义的方式来实现,但是由于某些原因我们想使用自定义的SQL来查询,spring data也是完美支持的;在SQL的查询方法上面使用@Query注解,如涉及到删除和修改在需要加上@Modifying.也可以根据需要添加 @Transactional 对事物的支持,查询超时的设置等

1
2
3
4
5
6
7
8
9
10
11
12
@Modifying
@Query("update User u set u.userName = ?1 where c.id = ?2")
int modifyByIdAndUserId(String userName, Long id);

@Transactional
@Modifying
@Query("delete from User where id = ?1")
void deleteByUserId(Long id);

@Transactional(timeout = 10)
@Query("select u from User u where u.emailAddress = ?1")
User findByEmailAddress(String emailAddress);

多表查询

​ 多表查询在spring data jpa中有两种实现方式,第一种是利用hibernate的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果,这里主要第二种方式。

首先需要定义一个结果集的接口类。

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface HotelSummary {

City getCity();

String getName();

Double getAverageRating();

default Integer getAverageRatingRounded() {
return getAverageRating() == null ? null : (int) Math.round(getAverageRating());
}

}

查询的方法返回类型设置为新创建的接口

1
2
3
4
5
6
7
@Query("select h.city as city, h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r where h.city = ?1 group by h")
Page<HotelSummary> findByCity(City city, Pageable pageable);

@Query("select h.name as name, avg(r.rating) as averageRating "
- "from Hotel h left outer join h.reviews r group by h")
Page<HotelSummary> findByCity(Pageable pageable);

使用

1
2
3
4
Page<HotelSummary> hotels = this.hotelRepository.findByCity(new PageRequest(0, 10, Direction.ASC, "name"));
for(HotelSummary summay:hotels){
System.out.println("Name" +summay.getName());
}

在运行中Spring会给接口(HotelSummary)自动生产一个代理类来接收返回的结果,代码汇总使用getXX的形式来获取

多数据源的支持

同源数据库的多源支持

日常使用分布式开发模式,不同的服务有不同的数据源,常常需要在一个项目中使用多个数据源,因此需要配置spring data jpa对多数据源的使用,一般分为三步:

  • 1 配置多数据源

  • 2 不同源的实体类放入不同包路径

  • 3 声明不同的包路径下使用不同的数据源、事务支持

    这里有一篇文章写的很清楚:Spring Boot多数据源配置与使用

异构数据库多源支持

比如我们的项目中,即需要对mysql的支持,也需要对mongodb的查询等。

实体类声明@Entity 关系型数据库支持类型、声明@Document 为mongodb支持类型,不同的数据源使用不同的实体就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface PersonRepository extends Repository<Person, Long> {

}

@Entity
public class Person {

}

interface UserRepository extends Repository<User, Long> {

}

@Document
public class User {

}

但是,如果User用户既使用mysql也使用mongodb呢,也可以做混合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
interface JpaPersonRepository extends Repository<Person, Long> {

}

interface MongoDBPersonRepository extends Repository<Person, Long> {

}

@Entity
@Document
public class Person {

}

也可以通过对不同的包路径进行声明,比如A包路径下使用mysql,B包路径下使用mongoDB

1
2
3
@EnableJpaRepositories(basePackages = "com.neo.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.neo.repositories.mongo")
interface Configuration { }

其他

使用枚举

使用枚举的时候,我们希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上面添加@Enumerated(EnumType.STRING) 注解

1
2
3
@Enumerated(EnumType.STRING) 
@Column(nullable = true)
private UserType type;

不需要和数据库映射的属性

正常情况下我们在实体类上加入注解@Entity,就会让实体类和表相关连如果其中某个属性我们不需要和数据库来关联只是在展示的时候做计算,只需要加上@Transient属性既可。

1
2
@Transient
private String userName;

[spring data jap 参考手册](https://docs.spring.io/spring-data/jpa/docs/current/reference/html/)

spring data jpa 注解

Springboot jpa提供了自动填充这两个字段的功能,简单配置一下即可。@CreatedDate、@LastModifiedDate@CreatedBy、`@LastModifiedBy`前两个注解就是起这个作用的,后两个是设置修改人和创建人的,这里先不讨论。

@Version注解表示对表添加乐观锁定的支持。

@Modifying 完成修改操作不支持新增操作

@DynamicUpdate 是否自动更新

spring data jpa的核心概念

springdata的核心接口是Repository。使用domian类去管理。

spring data jpa @Query 定义中的SpEL的支持

Spring Data JPA允许使用@Query注释手动定义要由存储库方法执行的查询。不幸的是,JPQL中的参数绑定非常有限,只允许您设置一个值并提供一些类型转换。Evans版本系列的最新Spring Data JPA M1版本通过添加
对使用SpEL表达式的支持来缓解这种痛苦,以便在@Query注释中的语句中使用动态绑定参数,这在手动定义查询时提供了额外的灵活性。

方法参数表达式

SpEL支持提供对查询方法参数的访问

1
2
@Query("select u from User u where u.firstname = :#{#customer.firstname}")
List<User> findUsersByCustomersFirstname(@Param("customer") Customer customer)

高级表达式

可以引用spring security 进行安全验证,如查询限制仅返回与当前经过身份验证的相关用户信息。

1
2
Query("select u from User u where u.emailAddress = ?#{principal.emailAddress}")
List<User> findCurrentUserWithCustomQuery();

如您所见,我们引用了Spring Security的属性principal。那么Spring Data SpEL如何支持与Spring Security集成。

1547115370479

1.接口绑定:两种方法,基于注解或者基于xml文档mapper,但要注意mapper的namespace要与接口路径完全一致。

2.orm格式转换:通过设置resultMap和ResultType,将数据库中的记录转换为代码的bean对象。得到list或者对象。

3.通过parameterType接收参数,进行动态sql生成。运用ognl表达式

4.走缓存,设置二级缓存。设置二级缓存源。

5.为什么要通过orm框架来走缓存呢?因为自己配置缓存策略相对复杂,比如当insert/update/delete时,要清除相应的缓存。当某些情况select又要添加进缓存。

6.orm框架,orm框架,它是怎么进行对象和数据库中表的转换的呢?答:数据库中的表要与代码中的类一一对应,包括属性。这样不就能进行匹配转换了嘛。

5.返回list,必须要配置resultMAp

6.insert操作时,要注意主键 主键生成策略,要设置useGeneraterKey = true,和 keyProperty=”id”,指定哪个是主键

1
2
3
4
5
<insert id="addUser" parameterType="User" 
useGeneratedKeys="true" keyProperty="id">
insert into user(userName,userAge,userAddress)
values(#{userName},#{userAge},#{userAddress})
</insert>

7.spring在于mybatis集成时,spring负责什么呢?谁来维护datasource,谁来建立sqlSessionFactory?

答:spring作为多个框架的粘合剂,spring负责建立datasource,sqlsessionFactpry。充分利用spring的ioc和aop功能。

复制代码;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spring 配置文件:applicationContext.xml 


<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">//spring管理配置datasource
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="password"/>
</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> //spring管理配置sqlsessionFactory
<!--dataSource属性指定要用到的连接池-->
<property name="dataSource" ref="dataSource"/>
<!--configLocation属性指定mybatis的核心配置文件-->
<property name="configLocation" value="config/Configuration.xml"/>
</bean>

复制代码;)

8.Mybatis的dao实现接口(接口绑定),是由mybatis来实现的,那又怎么来使用这个实现类呢?

答:当然是注册到spring里了,作为一个bean使用。

即:mybatis的dao实现类,也都要注册到spring的ioc容器中,以便利用spring的ioc和aop功能。

注意此时dao实现类,的具体类是mybatis固定的org.mybatis.spring.mapper.MapperFactoryBean类,这个类专门用来生成具体的dao实现类。

但要记住,一切的增删改查都是通过session来进行的,所以dao实现类要di依赖注入sqlSessionFactory这个属性。

再利用mapperInterface指定具体的mapper接口类。

复制代码;)

1
2
3
4
5
6
7
spring 配置文件:applicationContext.xml 
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
<!--sqlSessionFactory属性指定要用到的SqlSessionFactory实例-->
<property name="sqlSessionFactory" ref="sqlSessionFactory" />
<!--mapperInterface属性指定映射器接口,用于实现此接口并生成映射器对象-->
<property name="mapperInterface" value="com.yihaomen.mybatis.inter.IUserOperation" />
</bean>

复制代码;)

9.即:mybatis的dao实现类,也都要注册到spring的ioc容器中,以便利用spring的ioc和aop功能。

10.spring的配置文件applicationContext.XML负责配置与数据库相关,与mybatis sqlSessionFaction 整合,扫描所有mybatis mapper 文件等相关内容。

事务管理器也在spring的配置文件中配置,同时要依赖注入datasource属性

1
2
3
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>

11.mybatis的分页功能,可以自己利用mysql代码实现,也可以利用mybatis分页插件,如pageHelper

12.mybatis的传入参数只能是一个,只能有1个。可以是各种Java的基本数据类型:包含int,String,Date等。基本数据类型作为传参,只能传入一个。通过#{参数名} 即可获取传入的值 ,复杂数据类型:包含JAVA实体类、Map。通过#{属性名}或#{map的KeyName}即可获取传入的值,但是如果想传入一个collection怎么办呢?

经查找后发现可以使用mapper配置文件中的foreach语句,借用别人写的文章:

\13. foreach

对于动态SQL 非常必须的,主是要迭代一个集合,通常是用于IN 条件。List 实例将使用“list”做为键,数组实例以“array” 做为键。

foreach元素是非常强大的,它允许你指定一个集合,声明集合项和索引变量,它们可以用在元素体内。它也允许你指定开放和关闭的字符串,在迭代之间放置分隔符。这个元素是很智能的,它不会偶然地附加多余的分隔符。

\14. 在Java实体对象对中,一对多可以根据List和Set来实现,两者在mybitis中都是通过collection标签来配合使用

15.

一对一关联

1
2
3
根据班级id查询班级信息(带老师的信息)
10 ##1. 联表查询
11 SELECT * FROM class c,teacher t WHERE c.teacher_id=t.t_id AND c.c_id=1;

复制代码;)

1
2
3
4
5
6
7
8
9
10
11
12
<select id="getClass" parameterType="int" resultMap="ClassResultMap">
24 select * from class c, teacher t where c.teacher_id=t.t_id and c.c_id=#{id}
25 </select>
26 <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
27 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap">
28 <id property="id" column="c_id"/>
29 <result property="name" column="c_name"/>
30 <association property="teacher" javaType="me.gacl.domain.Teacher">
31 <id property="id" column="t_id"/>
32 <result property="name" column="t_name"/>
33 </association>
34 </resultMap>

复制代码;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 
37 方式二:嵌套查询:通过执行另外一个SQL映射语句来返回预期的复杂类型
38 SELECT * FROM class WHERE c_id=1;
39 SELECT * FROM teacher WHERE t_id=1 //1 是上一个查询得到的teacher_id的值
40 -->
41 <select id="getClass2" parameterType="int" resultMap="ClassResultMap2">
42 select * from class where c_id=#{id}
43 </select>
44 <!-- 使用resultMap映射实体类和字段之间的一一对应关系 -->
45 <resultMap type="me.gacl.domain.Classes" id="ClassResultMap2">
46 <id property="id" column="c_id"/>
47 <result property="name" column="c_name"/>
48 <association property="teacher" column="teacher_id" select="getTeacher"/>
49 </resultMap>
50
51 <select id="getTeacher" parameterType="int" resultType="me.gacl.domain.Teacher">
52 SELECT t_id id, t_name name FROM teacher WHERE t_id=#{id}
53 </select>

 MyBatis中使用association标签来解决一对一的关联查询,association标签可用的属性如下:

  • property:对象属性的名称
  • javaType:对象属性的类型
  • column:所对应的外键字段名称
  • select:使用另一个查询封装的结果

15.

2.6、MyBatis一对多关联查询总结

  MyBatis中使用collection标签来解决一对多的关联查询,ofType属性指定集合中元素的对象类型。

本文部分转自http://www.cnblogs.com/xdp-gacl/p/4264440.html

16.mybatis调用存储过程

复制代码;)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
三、编辑userMapper.xml

  编辑userMapper.xml文件,添加如下的配置项

复制代码
1 <!--
2 查询得到男性或女性的数量, 如果传入的是0就女性否则是男性
3 -->
4 <select id="getUserCount" parameterMap="getUserCountMap" statementType="CALLABLE">
5 CALL mybatis.ges_user_count(?,?)
6 </select>
7
8 <!--
9 parameterMap.put("sexid", 0);
10 parameterMap.put("usercount", -1);
11 -->
12 <parameterMap type="java.util.Map" id="getUserCountMap">
13 <parameter property="sexid" mode="IN" jdbcType="INTEGER"/>
14 <parameter property="usercount" mode="OUT" jdbcType="INTEGER"/>
15 </parameterMap>
复制代码
四、编写单元测试代码

复制代码
1 package me.gacl.test;
2
3 import java.util.HashMap;
4 import java.util.List;
5 import java.util.Map;
6
7 import me.gacl.custom.model.ConditionUser;
8 import me.gacl.domain.User;
9 import me.gacl.util.MyBatisUtil;
10 import org.apache.ibatis.session.SqlSession;
11 import org.junit.Test;
12
13 /**
14 * @author gacl
15 * 测试调用存储过程
16 */
17 public class Test6 {
18
19 @Test
20 public void testGetUserCount(){
21 SqlSession sqlSession = MyBatisUtil.getSqlSession();
22 /**
23 * 映射sql的标识字符串,
24 * me.gacl.mapping.userMapper是userMapper.xml文件中mapper标签的namespace属性的值,
25 * getUserCount是select标签的id属性值,通过select标签的id属性值就可以找到要执行的SQL
26 */
27 String statement = "me.gacl.mapping.userMapper.getUserCount";//映射sql的标识字符串
28 Map<String, Integer> parameterMap = new HashMap<String, Integer>();
29 parameterMap.put("sexid", 1);
30 parameterMap.put("usercount", -1);
31 sqlSession.selectOne(statement, parameterMap);
32 Integer result = parameterMap.get("usercount");
33 System.out.println(result);
34 sqlSession.close();
35 }
36 }
复制代码

复制代码;)