設(shè)計模式
83.有哪些熟悉的設(shè)計模式?
- 單例模式
某個類的實例對象只有一個,你沒有辦法去new钧汹,因為構(gòu)造器是被private修飾的,一般通過getInstance()的方法來獲取它們的實例录择。getInstance()的返回值是一個對象的引用拔莱,并不是一個新的實例
public class Singleton(單例模式) {
private static Singleton instance;
private Singleton (){} //構(gòu)造函數(shù)
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
//synchronized 是同步鎖
- 觀察者模式
舉個栗子:假設(shè)有三個人碗降,小美(女,22)塘秦,小王和小李讼渊。小美很漂亮,小王和小李是兩個程序猿嗤形,時刻關(guān)注著小美的一舉一動精偿。有一天,小美說了一句:“誰來陪我打游戲啊赋兵”恃剩”這句話被小王和小李聽到了,結(jié)果樂壞了霹期,蹭蹭蹭叶组,沒一會兒,小王就沖到小美家門口了历造,在這里甩十,小美是被觀察者,小王和小李是觀察者吭产,被觀察者發(fā)出一條信息侣监,然后觀察者們進行相應(yīng)的處理
看代碼
定義一個接口,它有一個函數(shù)
public interface Person {//(相當(dāng)于一個進群規(guī)則)
//小王和小李通過這個接口可以接收到小美發(fā)過來的消息
void getMessage(String s);
}
這個接口相當(dāng)于一個進群規(guī)則臣淤,只有Person才能進群橄霉,小美發(fā)送通知的時候就會在這個群里發(fā)消息,在這個群里面的人都會收到消息邑蒋。
public class LaoWang implements Person {
private String name = "小王";
public LaoWang() {
}
@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打過來的電話姓蜂,電話內(nèi)容是:" + s);
}
}
public class LaoLi implements Person {
private String name = "小李";
public LaoLi() {
}
@Override
public void getMessage(String s) {
System.out.println(name + "接到了小美打過來的電話,電話內(nèi)容是:->" + s);
}
}
再看看小美的代碼:
public class XiaoMei {
//這個list相當(dāng)于一個群医吊,注意這個群有一個規(guī)則钱慢,就是具有g(shù)etMessage的才能進來
List<Person> list = new ArrayList<Person>();
public XiaoMei(){
}
//把某個人(這個人必須滿足進群規(guī)則,就是繼承Person)拉進群里
public void addPerson(Person person){
list.add(person);
}
//遍歷list卿堂,把自己的通知發(fā)送給所有群里的人
public void notifyPerson() {
for(Person person:list){
person.getMessage("你們過來吧束莫,誰先過來誰就能陪我一起玩兒游戲!");
}
}
}
我們寫一個測試類來看一下結(jié)果對不對
public class Test {
public static void main(String[] args) {
XiaoMei xiao_mei = new XiaoMei();
LaoWang lao_wang = new LaoWang();
LaoLi lao_li = new LaoLi();
//小王和小李被小美拉進群
xiao_mei.addPerson(lao_wang);
xiao_mei.addPerson(lao_li);
//小美在群里發(fā)通知
xiao_mei.notifyPerson();
}
}
- 裝飾者模式 對已有的業(yè)務(wù)邏輯進一步的封裝,使其增加額外的功能
舉個栗子草描,我想吃三明治览绿,首先我需要一根大大的香腸,然后我喜歡吃奶油陶珠,在香腸上面加一點奶油挟裂,然后需要再放一點蔬菜享钞,最后再用兩片面包把它包裹起來揍诽,形成一個三明治诀蓉。
首先,我們需要寫一個Food類暑脆,讓其他所有食物都來繼承這個類
public class Food {
private String food_name;
public Food() {
}
public Food(String food_name) {
this.food_name = food_name;
}
public String make() {
return food_name;
};
}
然后我們寫幾個子類繼承它
構(gòu)造方法傳入一個Food類型的參數(shù)渠啤,然后在make方法中加入一些自己的邏輯
//面包類
public class Bread extends Food {
private Food basic_food;
public Bread(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+面包";
}
}
//奶油類
public class Cream extends Food {
private Food basic_food;
public Cream(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+奶油";
}
}
//蔬菜類
public class Vegetable extends Food {
private Food basic_food;
public Vegetable(Food basic_food) {
this.basic_food = basic_food;
}
public String make() {
return basic_food.make()+"+蔬菜";
}
}
Test類
public class Test {
public static void main(String[] args) {
Food food = new Bread(new Vegetable(new Cream(new Food("香腸"))));
System.out.println(food.make());
}
}
看到?jīng)]有,一層一層封裝添吗,我們從里往外看:最里面我new了一個香腸沥曹,在香腸的外面我包裹了一層奶油,在奶油的外面我又加了一層蔬菜碟联,最外面我放的是面包
- 適配器模式 將兩種完全不同的事物聯(lián)系到一起妓美,就像現(xiàn)實生活中的變壓器
假設(shè)一個手機充電器需要的電壓是20V,但是正常的電壓是220V鲤孵,這時候就需要一個變壓器壶栋,將220V的電壓轉(zhuǎn)換成20V的電壓,這樣普监,變壓器就將20V的電壓和手機聯(lián)系起來了贵试。
public class Test {
public static void main(String[] args) {
Phone phone = new Phone();//定義一個手機
VoltageAdapter adapter = new VoltageAdapter();//定義一個適配器
phone.setAdapter(adapter);//手機裝上適配器
phone.charge();//手機充電
}
}
// 手機類
class Phone {
public static final int V = 220;// 正常電壓220v,是一個常量
private VoltageAdapter adapter;
// 充電
public void charge() {
adapter.changeVoltage();
}
public void setAdapter(VoltageAdapter adapter) {
this.adapter = adapter;
}
}
// 變壓器
class VoltageAdapter {
// 改變電壓的功能
public void changeVoltage() {
System.out.println("正在充電...");
System.out.println("原始電壓:" + Phone.V + "V");
System.out.println("經(jīng)過變壓器轉(zhuǎn)換之后的電壓:" + (Phone.V - 200) + "V");
}
}
- 工廠模式
簡單工廠模式:1.一個抽象的接口凯正,2.多個抽象接口的實現(xiàn)類毙玻,3.一個工廠類,用來實例化抽象的接口
一個抽象的接口
// 抽象產(chǎn)品類
abstract class Car {
public void run();//能跑
public void stop();//能停
}
多個抽象接口的實現(xiàn)類
// 具體實現(xiàn)類
class Benz implements Car {
public void run() {
System.out.println("Benz開始啟動了廊散。桑滩。。奸汇。施符。");
}
public void stop() {
System.out.println("Benz停車了。擂找。戳吝。。贯涎。");
}
}
class Ford implements Car {
public void run() {
System.out.println("Ford開始啟動了听哭。。塘雳。");
}
public void stop() {
System.out.println("Ford停車了陆盘。。败明。隘马。");
}
}
一個工廠類
// 工廠類
class Factory {
public static Car getCarInstance(String type) {
Car c = null;
if ("Benz".equals(type)) {
c = new Benz();
}
if ("Ford".equals(type)) {
c = new Ford();
}
return c;
}
}
測試類
public class Test {
public static void main(String[] args) {
Car c = Factory.getCarInstance("Benz");
if (c != null) {
c.run();
c.stop();
} else {
System.out.println("造不了這種汽車。妻顶。酸员。");
}
}
}
- 代理模式
舉個栗子:打官司蜒车,律師就代理你進行辯論;開庭前(編譯期間)你花錢請了一個有名的律師幔嗦,他就是靜態(tài)代練酿愧,你沒錢,開庭的時候(運行期間)邀泉。法院隨機給你找一個律師嬉挡,這就是動態(tài)代理。
84.簡單工廠和抽象工廠有什么區(qū)別汇恤?
抽象工廠模式:
多個抽象產(chǎn)品類庞钢,每個抽象產(chǎn)品類可以派生出多個具體產(chǎn)品類
一個抽象工廠類,可以派生出多個具體工廠類
每個具體工廠類可以創(chuàng)建多個具體產(chǎn)品類的實例
Spring / Spring MVC
85.為什么要使用 spring?
簡介:Spring是一個輕量級的控制反轉(zhuǎn)(IOC)和面向切面(AOP)的容器框架
目的:解決企業(yè)應(yīng)用開發(fā)的復(fù)雜性
86.解釋一下什么是 aop因谎?
AOP(Aspect-Oriented Programming焊夸,面向方面編程),可以說是OOP(Object-Oriented Programing蓝角,面向?qū)ο缶幊蹋┑难a充和完善
目的:當(dāng)我們需要為分散的對象引入公共行為的時候阱穗,OOP則顯得無能為力。
操作:將那些影響了多個類的公共行為封裝到一個可重用模塊使鹅,并將其名為“Aspect”揪阶,即方面。簡單來說:將那些與業(yè)務(wù)無關(guān)患朱,卻為業(yè)務(wù)模塊所共同調(diào)用的邏輯或責(zé)任封裝起來鲁僚,便于減少系統(tǒng)的重復(fù)代碼,降低模塊間的耦合度裁厅,并有利于未來的可操作性和可維護性冰沙。比如權(quán)限認證、日志执虹、事務(wù)處理拓挥。
87.解釋一下什么是 ioc?
翻譯:控制反轉(zhuǎn)
解釋:借助于“第三方”實現(xiàn)具有依賴關(guān)系的對象之間的解耦
全部對象的控制權(quán)全部上繳給“第三方”IOC容器袋励,所以侥啤,IOC容器成了整個系統(tǒng)的關(guān)鍵核心,它起到了一種類似“粘合劑”的作用
為何叫控制反轉(zhuǎn)
軟件系統(tǒng)在沒有引入IOC容器之前茬故,對象A依賴于對象B盖灸,那么對象A在初始化或者運行到某一點的時候,
自己必須主動去創(chuàng)建對象B或者使用已經(jīng)創(chuàng)建的對象B磺芭。無論是創(chuàng)建還是使用對象B赁炎,控制權(quán)都在自己手上。
軟件系統(tǒng)在引入IOC容器之后钾腺,這種情形就完全改變了徙垫,琅攘,由于IOC容器的加入,對象A與對象B之間失去了直接聯(lián)系松邪,
所以,當(dāng)對象A運行到需要對象B的時候哨查,IOC容器會主動創(chuàng)建一個對象B注入到對象A需要的地方逗抑。
對象A獲得依賴對象B的過程,由主動行為變?yōu)榱吮粍有袨椋刂茩?quán)顛倒過來了寒亥,
這就是“控制反轉(zhuǎn)”這個名稱的由來邮府。
88.spring 常用的注入方式有哪些?
a.構(gòu)造方法注入
b.setter注入
c.基于注解的注入
聲明一個簡單的bean
public class Roles {
private int id;
private String roleName;
public Roles() {
}
public Roles(int id,String roleName) {
this.id=id;
this.roleName = roleName;
}
}
構(gòu)造方法注入
<bean id="roles" class="cn.com.wg.spring.model.Roles">
<constructor-arg value="1"/>
<constructor-arg value="管理員"/>
</bean>
setter注入 我們能發(fā)現(xiàn)構(gòu)造器<constructor-arg/> set注入是<property/>
<bean id="roles" class="cn.com.wg.spring.model.Roles">
<property name="id" value="2"/>
<property name="roleName" value="管理員"/>
</bean>
測試main方法
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Roles r=(Roles)context.getBean("roles");
System.out.println(r.toString());
}
基于注解的注入
Autowired是自動注入溉奕,自動從spring的上下文找到合適的bean來注入
Resource用來指定名稱注入
Qualifier和Autowired配合使用褂傀,指定bean的名稱,如
@Autowired
@Qualifier("userDAO")
private UserDAO userDAO;
89.spring 中的 bean 是線程安全的嗎加勤?
容器本身并沒有提供Bean的線程安全策略
90.spring 支持幾種 bean 的作用域仙辟?
singleton:單例模式,在整個Spring IoC容器中鳄梅,使用singleton定義的Bean將只有一個實例
prototype:原型模式叠国,每次通過容器的getBean方法獲取prototype定義的Bean時,都將產(chǎn)生一個新的Bean實例
request:對于每次HTTP請求戴尸,使用request定義的Bean都將產(chǎn)生一個新實例粟焊,即每次HTTP請求將會產(chǎn)生不同的Bean實例。只有在Web應(yīng)用中使用Spring時孙蒙,該作用域才有效
session:對于每次HTTP Session项棠,使用session定義的Bean都將產(chǎn)生一個新實例。同樣只有在Web應(yīng)用中使用Spring時挎峦,該作用域才有效
globalsession:每個全局的HTTP Session香追,使用session定義的Bean都將產(chǎn)生一個新實例。典型情況下坦胶,僅在使用portlet context的時候有效翅阵。同樣只有在Web應(yīng)用中使用Spring時,該作用域才有效
91.spring 自動裝配 bean 有哪些方式迁央?
1.在XML中進行顯式配置
2.在Java中進行顯式配置
3.隱式的bean發(fā)現(xiàn)機制和自動裝配
92.spring 事務(wù)實現(xiàn)方式有哪些掷匠?
含義:事務(wù)是邏輯上的一組操作,要么都執(zhí)行岖圈,要么都不執(zhí)行
事物的特性(ACID):
原子性: 事務(wù)是最小的執(zhí)行單位讹语,不允許分割。事務(wù)的原子性確保動作要么全部完成蜂科,要么完全不起作用顽决;
一致性: 執(zhí)行事務(wù)前后短条,數(shù)據(jù)保持一致;
隔離性: 并發(fā)訪問數(shù)據(jù)庫時才菠,一個用戶的事物不被其他事物所干擾茸时,各并發(fā)事務(wù)之間數(shù)據(jù)庫是獨立的;
持久性: 一個事務(wù)被提交之后赋访。它對數(shù)據(jù)庫中數(shù)據(jù)的改變是持久的可都,即使數(shù)據(jù)庫發(fā)生故障也不應(yīng)該對其有任何影響
來源https://juejin.im/post/5b00c52ef265da0b95276091
1.編程式事務(wù)管理對基于 POJO (通指沒有使用Entity Beans的普通java對象)的應(yīng)用來說是唯一選擇。我們需要在代碼中調(diào)用beginTransaction()蚓耽、commit()渠牲、rollback()等事務(wù)管理相關(guān)的方法,這就是編程式事務(wù)管理步悠。
2.基于 TransactionProxyFactoryBean 的聲明式事務(wù)管理
3.基于 @Transactional 的聲明式事務(wù)管理
4.基于 Aspectj AOP 配置事務(wù)
93.說一下 spring 的事務(wù)隔離签杈?
事務(wù)隔離級別指的是一個事務(wù)對數(shù)據(jù)的修改與另一個并行的事務(wù)的隔離程度,當(dāng)多個事務(wù)同時訪問相同數(shù)據(jù)時鼎兽,如果沒有采取必要的隔離機制答姥,就可能發(fā)生以下問題:
臟讀(Dirty read): 當(dāng)一個事務(wù)正在訪問數(shù)據(jù)并且對數(shù)據(jù)進行了修改,而這種修改還沒有提交到數(shù)據(jù)庫中谚咬,這時另外一個事務(wù)也訪問了這個數(shù)據(jù)踢涌,然后使用了這個數(shù)據(jù)。因為這個數(shù)據(jù)是還沒有提交的數(shù)據(jù)序宦,那么另外一個事務(wù)讀到的這個數(shù)據(jù)是“臟數(shù)據(jù)”睁壁,依據(jù)“臟數(shù)據(jù)”所做的操作可能是不正確的。
丟失修改(Lost to modify): 指在一個事務(wù)讀取一個數(shù)據(jù)時互捌,另外一個事務(wù)也訪問了該數(shù)據(jù)潘明,那么在第一個事務(wù)中修改了這個數(shù)據(jù)后,第二個事務(wù)也修改了這個數(shù)據(jù)秕噪。這樣第一個事務(wù)內(nèi)的修改結(jié)果就被丟失衰粹,因此稱為丟失修改狰腌。
例如:事務(wù)1讀取某表中的數(shù)據(jù)A=20,事務(wù)2也讀取A=20,事務(wù)1修改A=A-1懊缺,事務(wù)2也修改A=A-1森缠,最終結(jié)果A=19楣富,事務(wù)1的修改被丟失寄疏。不可重復(fù)讀(Unrepeatableread): 指在一個事務(wù)內(nèi)多次讀同一數(shù)據(jù)。在這個事務(wù)還沒有結(jié)束時灯荧,另一個事務(wù)也訪問該數(shù)據(jù)礁击。那么,在第一個事務(wù)中的兩次讀數(shù)據(jù)之間,由于第二個事務(wù)的修改導(dǎo)致第一個事務(wù)兩次讀取的數(shù)據(jù)可能不太一樣哆窿。這就發(fā)生了在一個事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況链烈,因此稱為不可重復(fù)讀。
幻讀(Phantom read): 幻讀與不可重復(fù)讀類似挚躯。它發(fā)生在一個事務(wù)(T1)讀取了幾行數(shù)據(jù)强衡,接著另一個并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時。在隨后的查詢中码荔,第一個事務(wù)(T1)就會發(fā)現(xiàn)多了一些原本不存在的記錄漩勤,就好像發(fā)生了幻覺一樣,所以稱為幻讀目胡。
不可重復(fù)度和幻讀區(qū)別:
不可重復(fù)讀的重點是修改,幻讀的重點在于新增或者刪除链快。
例1(同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來發(fā)現(xiàn)值不一樣了 ):事務(wù)1中的A先生讀取自己的工資為 1000的操作還沒完成誉己,事務(wù)2中的B先生就修改了A的工資為2000,導(dǎo) 致A再讀自己的工資時工資變?yōu)? 2000域蜗;這就是不可重復(fù)讀巨双。
例2(同樣的條件, 第1次和第2次讀出來的記錄數(shù)不一樣 ):假某工資單表中工資大于3000的有4人,事務(wù)1讀取了所有工資大于3000的人霉祸,共查到4條記錄筑累,這時事務(wù)2 又插入了一條工資大于3000的記錄,事務(wù)1再次讀取時查到的記錄就變?yōu)榱?條丝蹭,這樣就導(dǎo)致了幻讀慢宗。
94.說一下 spring mvc 運行流程?
Spring運行流程描述:
- 用戶向服務(wù)器發(fā)送請求奔穿,請求被Spring 前端控制Servelt DispatcherServlet捕獲镜沽;
- DispatcherServlet對請求URL進行解析,得到請求資源標(biāo)識符(URI)贱田。然后根據(jù)該URI缅茉,調(diào)用HandlerMapping獲得該Handler配置的所有相關(guān)的對象(包括Handler對象以及Handler對象對應(yīng)的攔截器),最后以HandlerExecutionChain對象的形式返回男摧;
- DispatcherServlet 根據(jù)獲得的Handler蔬墩,選擇一個合適的HandlerAdapter;(附注:如果成功獲得HandlerAdapter后耗拓,此時將開始執(zhí)行攔截器的preHandler(...)方法)
- 提取Request中的模型數(shù)據(jù)拇颅,填充Handler入?yún)ⅲ_始執(zhí)行Handler(Controller)乔询。 在填充Handler的入?yún)⑦^程中蔬蕊,根據(jù)你的配置,Spring將幫你做一些額外的工作:
HttpMessageConveter: 將請求消息(如Json、xml等數(shù)據(jù))轉(zhuǎn)換成一個對象岸夯,將對象轉(zhuǎn)換為指定的響應(yīng)信息
數(shù)據(jù)轉(zhuǎn)換:對請求消息進行數(shù)據(jù)轉(zhuǎn)換麻献。如String轉(zhuǎn)換成Integer、Double等
數(shù)據(jù)根式化:對請求消息進行數(shù)據(jù)格式化猜扮。 如將字符串轉(zhuǎn)換成格式化數(shù)字或格式化日期等
數(shù)據(jù)驗證: 驗證數(shù)據(jù)的有效性(長度勉吻、格式等),驗證結(jié)果存儲到BindingResult或Error中 - Handler執(zhí)行完成后旅赢,向DispatcherServlet 返回一個ModelAndView對象齿桃;
- 根據(jù)返回的ModelAndView,選擇一個適合的ViewResolver(必須是已經(jīng)注冊到Spring容器中的ViewResolver)返回給DispatcherServlet 煮盼;
- ViewResolver 結(jié)合Model和View短纵,來渲染視圖;
- 將渲染結(jié)果返回給客戶端僵控。
95.spring mvc 有哪些組件香到?
Spring MVC的核心組件:
DispatcherServlet:中央控制器,把請求給轉(zhuǎn)發(fā)到具體的控制類
Controller:具體處理請求的控制器
HandlerMapping:映射處理器报破,負責(zé)映射中央處理器轉(zhuǎn)發(fā)給controller時的映射策略
ModelAndView:服務(wù)層返回的數(shù)據(jù)和視圖層的封裝類
ViewResolver:視圖解析器悠就,解析具體的視圖
Interceptors :攔截器,負責(zé)攔截我們定義的請求然后做處理工作
96. @RequestMapping 的作用是什么充易?
RequestMapping是一個用來處理請求地址映射的注解梗脾,可用于類或方法上。用于類上盹靴,表示類中的所有響應(yīng)請求的方法都是以該地址作為父路徑炸茧。
RequestMapping注解有六個屬性,下面我們把她分成三類進行說明稿静。
value宇立, method:
value:指定請求的實際地址
method:指定請求的method類型, GET自赔、POST妈嘹、PUT、DELETE等绍妨;consumes润脸,produces
consumes:指定處理請求的提交內(nèi)容類型(Content-Type),例如
application/json, text/html他去;
produces:指定返回的內(nèi)容類型毙驯,僅當(dāng)request請求頭中的(Accept)類型中包含該指定類型才返回;params灾测,headers
params: 指定request中必須包含某些參數(shù)值是爆价,才讓該方法處理。
headers:指定request中必須包含某些指定的header值,才能讓該方法處理請求铭段。
Spring Boot / Spring Cloud
97.什么是 spring boot骤宣?
簡化了Spring眾多框架中所需的大量且繁瑣的配置文件,所以 SpringBoot是一個服務(wù)于框架的框架序愚,服務(wù)范圍是簡化配置文件憔披。
-
why?
Spring Boot使編碼變簡單
Spring Boot使配置變簡單
Spring Boot使部署變簡單
Spring Boot使監(jiān)控變簡單
Spring的不足
98. spring boot 核心配置文件是什么?
Spring Boot提供了兩種常用的配置文件:
properties文件
相對于properties文件而言爸吮,yml文件更年輕芬膝,也有很多的坑⌒谓浚可謂成也蕭何敗蕭何锰霜,yml通過空格來確定層級關(guān)系,使配置文件結(jié)構(gòu)跟清晰桐早,但也會因為微不足道的空格而破壞了層級關(guān)系癣缅。
99.spring boot 有哪些方式可以實現(xiàn)熱部署?
①. 使用spring loaded**
在項目配置文件中添加如下代碼:
<build>
<plugins>
<plugin>
<!-- springBoot編譯插件-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<dependencies>
<!-- spring熱部署 -->
<!-- 該依賴在此處下載不下來勘畔,可以放置在build標(biāo)簽外部下載完成后再粘貼進plugin中 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.6.RELEASE</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
添加完畢后需要使用mvn指令運行:
首先找到IDEA中的Edit configurations ,然后進行如下操作:(點擊左上角的"+",然后選擇maven將出現(xiàn)右側(cè)面板所灸,在紅色劃線部位輸入如圖所示指令丽惶,你可以為該指令命名(此處命名為MvnSpringBootRun))
點擊保存將會在IDEA項目運行部位出現(xiàn)炫七,點擊綠色箭頭運行即可
②. 使用spring-boot-devtools
在項目的pom文件中添加依賴:
<!--熱部署jar-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
然后:使用 shift+ctrl+alt+"/" (IDEA中的快捷鍵) 選擇"Registry" 然后勾選 compiler.automake.allow.when.app.running
100.jpa 和 hibernate 有什么區(qū)別?
JPA和Hibernate之間的關(guān)系钾唬,可以簡單的理解為JPA是標(biāo)準接口万哪,Hibernate是實現(xiàn)。那么Hibernate是如何實現(xiàn)與JPA的這種關(guān)系的呢抡秆。Hibernate主要是通過三個組件來實現(xiàn)的奕巍,及hibernate-annotation、hibernate-entitymanager和hibernate-core儒士。
- hibernate-annotation是Hibernate支持annotation方式配置的基礎(chǔ)的止,它包括了標(biāo)準的JPA annotation以及Hibernate自身特殊功能的annotation。
- hibernate-core是Hibernate的核心實現(xiàn)着撩,提供了Hibernate所有的核心功能诅福。
- hibernate-entitymanager實現(xiàn)了標(biāo)準的JPA,可以把它看成hibernate-core和JPA之間的適配器拖叙,它并不直接提供ORM的功能氓润,而是對hibernate-core進行封裝,使得Hibernate符合JPA的規(guī)范薯鳍。
101.什么是 spring cloud
從字面理解咖气,Spring Cloud 就是致力于分布式系統(tǒng)、云服務(wù)的框架。
Spring Cloud 為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具崩溪,例如:
配置管理
服務(wù)注冊與發(fā)現(xiàn)
斷路器
智能路由
服務(wù)間調(diào)用
負載均衡
微代理
控制總線
一次性令牌
全局鎖
領(lǐng)導(dǎo)選舉
分布式會話
集群狀態(tài)
分布式消息
……
使用 Spring Cloud 開發(fā)人員可以開箱即用的實現(xiàn)這些模式的服務(wù)和應(yīng)用程序浅役。這些服務(wù)可以任何環(huán)境下運行,包括分布式環(huán)境悯舟,也包括開發(fā)人員自己的筆記本電腦以及各種托管平臺担租。
一個認識spring cloud的項目(來源網(wǎng)絡(luò)...)
https://pan.baidu.com/s/1i7lFCPn
102. spring cloud 斷路器的作用是什么?
在Spring Cloud中使用了Hystrix 來實現(xiàn)斷路器的功能抵怎,斷路器可以防止一個應(yīng)用程序多次試圖執(zhí)行一個操作奋救,即很可能失敗,允許它繼續(xù)而不等待故障恢復(fù)或者浪費 CPU 周期反惕,而它確定該故障是持久的尝艘。斷路器模式也使應(yīng)用程序能夠檢測故障是否已經(jīng)解決,如果問題似乎已經(jīng)得到糾正姿染,應(yīng)用程序可以嘗試調(diào)用操作背亥。
斷路器增加了穩(wěn)定性和靈活性,以一個系統(tǒng)悬赏,提供穩(wěn)定性狡汉,而系統(tǒng)從故障中恢復(fù),并盡量減少此故障的對性能的影響闽颇。它可以幫助快速地拒絕一個操作盾戴,即很可能失敗的操作,而不是等待操作超時(或者不返回)的請求兵多,以保持系統(tǒng)的響應(yīng)時間尖啡。如果斷路器提高每次改變狀態(tài)的時間的事件,該信息可以被用來監(jiān)測由斷路器保護系統(tǒng)的部件的健康狀況剩膘,或以提醒管理員當(dāng)斷路器跳閘衅斩,以在打開狀態(tài)。
103.spring cloud 的核心組件有哪些怠褐?
①. 服務(wù)發(fā)現(xiàn)——Netflix Eureka
一個RESTful服務(wù)畏梆,用來定位運行在AWS地區(qū)(Region)中的中間層服務(wù)。由兩個組件組成:Eureka服務(wù)器和Eureka客戶端奈懒。Eureka服務(wù)器用作服務(wù)注冊服務(wù)器奠涌。Eureka客戶端是一個java客戶端,用來簡化與服務(wù)器的交互筐赔、作為輪詢負載均衡器铣猩,并提供服務(wù)的故障切換支持。Netflix在其生產(chǎn)環(huán)境中使用的是另外的客戶端茴丰,它提供基于流量达皿、資源利用率以及出錯狀態(tài)的加權(quán)負載均衡天吓。
②. 客服端負載均衡——Netflix Ribbon
Ribbon,主要提供客戶側(cè)的軟件負載均衡算法峦椰。Ribbon客戶端組件提供一系列完善的配置選項龄寞,比如連接超時、重試汤功、重試算法等物邑。Ribbon內(nèi)置可插拔、可定制的負載均衡組件滔金。
③. 斷路器——Netflix Hystrix
斷路器可以防止一個應(yīng)用程序多次試圖執(zhí)行一個操作色解,即很可能失敗,允許它繼續(xù)而不等待故障恢復(fù)或者浪費 CPU 周期餐茵,而它確定該故障是持久的科阎。斷路器模式也使應(yīng)用程序能夠檢測故障是否已經(jīng)解決。如果問題似乎已經(jīng)得到糾正忿族,應(yīng)用程序可以嘗試調(diào)用操作锣笨。
④. 服務(wù)網(wǎng)關(guān)——Netflix Zuul
類似nginx,反向代理的功能道批,不過netflix自己增加了一些配合其他組件的特性错英。
⑤. 分布式配置——Spring Cloud Config
這個還是靜態(tài)的,得配合Spring Cloud Bus實現(xiàn)動態(tài)的配置更新隆豹。
Hibernate
104.為什么要使用 hibernate椭岩?
對JDBC訪問數(shù)據(jù)庫的代碼做了封裝,大大簡化了數(shù)據(jù)訪問層繁瑣的重復(fù)性代碼噪伊。
Hibernate是一個基于JDBC的主流持久化框架簿煌,是一個優(yōu)秀的ORM(對象關(guān)系映射)實現(xiàn)氮唯。他很大程度的簡化DAO層的編碼工作
hibernate使用Java反射機制鉴吹,而不是字節(jié)碼增強程序來實現(xiàn)透明性。
hibernate的性能非常好惩琉,因為它是個輕量級框架豆励。映射的靈活性很出色。它支持各種關(guān)系數(shù)據(jù)庫瞒渠,從一對一到多對多的各種復(fù)雜關(guān)系良蒸。
105.什么是 ORM 框架?
對象-關(guān)系映射(Object-Relational Mapping伍玖,簡稱ORM)面向?qū)ο蟮拈_發(fā)方法是當(dāng)今企業(yè)級應(yīng)用開發(fā)環(huán)境中的主流開發(fā)方法嫩痰,關(guān)系數(shù)據(jù)庫是企業(yè)級應(yīng)用環(huán)境中永久存放數(shù)據(jù)的主流數(shù)據(jù)存儲系統(tǒng)。對象和關(guān)系數(shù)據(jù)是業(yè)務(wù)實體的兩種表現(xiàn)形式窍箍,業(yè)務(wù)實體在內(nèi)存中表現(xiàn)為對象串纺,在數(shù)據(jù)庫中表現(xiàn)為關(guān)系數(shù)據(jù)丽旅。內(nèi)存中的對象之間存在關(guān)聯(lián)和繼承關(guān)系,而在數(shù)據(jù)庫中纺棺,關(guān)系數(shù)據(jù)無法直接表達多對多關(guān)聯(lián)和繼承關(guān)系榄笙。因此,對象-關(guān)系映射(ORM)系統(tǒng)一般以中間件的形式存在祷蝌,主要實現(xiàn)程序?qū)ο蟮疥P(guān)系數(shù)據(jù)庫數(shù)據(jù)的映射茅撞。
106.hibernate 中如何在控制臺查看打印的 sql 語句?
spring.jpa.properties.hibernate.show_sql=true //控制臺是否打印
spring.jpa.properties.hibernate.format_sql=true //格式化sql語句
spring.jpa.properties.hibernate.use_sql_comments=true //指出是什么操作生成了該語句
107.hibernate 有幾種查詢方式巨朦?
1.hql查詢
HQL是面向?qū)ο蟛樵儾僮鞯?
hql查詢米丘,sql查詢,條件查詢
Query query = session.createQuery("from Customer where name = ?");
query.setParameter(0, "蒼老師");
Query.list();
2.sql查詢
SQL是結(jié)構(gòu)化查詢語言 是面向數(shù)據(jù)庫表結(jié)構(gòu)的
SQLQuery query = session.createSQLQuery("select * from customer");
List<Object[]> list = query.list();
3.條件查詢
查詢所有 session.creartCriteria(實體對象.class)
=======================================================
案例是:
/ 獲得session對象
Session session = HibernateUtils.openSession();
// 通過session獲得查詢對象 criteria
Criteria criteria = session.createCriteria(Customer.class); // 括號里面的參數(shù)就是查詢條件了,表示我們查詢的就是這個實體對象.
List<Customer> list = criteria.list(); // 這表示接收返回值.
// 遍歷打印結(jié)果
for (Customer customer : list) {
System.out.println(customer);
}
}
查詢單個字段
======================================================
案例是:
Session session = HibernateUtils.openSession();
// 通過session獲得查詢對象 criteria
Criteria criteria = session.createCriteria(Customer.class); // 括號里面的參數(shù)就是查詢條件了,表示我們查詢的就是這個實體對象.
// 賦值我們需要查詢的實體的變量名(也就是需要查詢的字段名)
criteria.setProjection(Projections.property("cust_name"));// Projection 投影的意思.
// 查詢后返回的就是Object
List<Object> list = criteria.list(); // 這表示接收返回值.
// 遍歷打印結(jié)果
for (Object object : list) {
System.out.println(object);
查詢多個字段
案例是
=======================================================
通過session獲得查詢對象 criteria
Criteria criteria = session.createCriteria(Customer.class); // 括號里面的參數(shù)就是查詢條件了,表示我們查詢的就是這個實體對象.
// 賦值我們需要查詢的實體的變量名(也就是需要查詢的字段名)
ProjectionList list2 = Projections.projectionList();
list2.add(Projections.property("cust_name"));
list2.add(Projections.property("cust_id"));
// 然后將這個集合與對象建立關(guān)系.
criteria.setProjection(list2);
// 查詢后返回的就是Object
List<Object[]> list = criteria.list(); // 這表示接收返回值.
// 遍歷打印結(jié)果
for (Object[] object : list) {
System.out.println(Arrays.toString(object));
}
}
108.在 hibernate 中使用 Integer 和 int 做映射有什么區(qū)別?
在Hibernate中糊啡,如果將OID定義為Integer類型蠕蚜,那么Hibernate就可以根據(jù)其值是否為null而判斷一個對象是否是臨時的,如果將OID定義為了int類型悔橄,還需要在hbm映射文件中設(shè)置其unsaved-value屬性為0靶累。
109.hibernate 是如何工作的?
hibernate工作原理:
- 通過
Configuration config = new Configuration().configure();
//讀取并解析hibernate.cfg.xml
配置文件 - 由
hibernate.cfg.xml
中的<mapping resource="com/xx/User.hbm.xml"/>
讀取并解析映射信息 - 通過
SessionFactory sf = config.buildSessionFactory();
//創(chuàng)建SessionFactory
-
Session session = sf.openSession();
//打開Sesssion -
Transaction tx = session.beginTransaction();
//創(chuàng)建并啟動事務(wù)Transation -
persistent operate
操作數(shù)據(jù)癣疟,持久化操作 -
tx.commit();
//提交事務(wù) - 關(guān)閉Session
- 關(guān)閉SesstionFactory
110.說一下 hibernate 的緩存機制挣柬?
- 什么是緩存機制?
當(dāng)我們頻繁訪問數(shù)據(jù)庫時,尤其像Hibernate持久層框架睛挚,會導(dǎo)致數(shù)據(jù)庫訪問性能降低邪蛔,因此我們期望有一種機制能提供一個"緩存空間",我們將需要的數(shù)據(jù)復(fù)制到這個"緩存空間"扎狱,當(dāng)數(shù)據(jù)查詢時侧到,我們先在這個"緩存空間"里找,如果沒有淤击,我們再去數(shù)據(jù)庫查找匠抗,這樣就減少了與數(shù)據(jù)庫的訪問,從而提高了數(shù)據(jù)庫訪問性能污抬,這就是緩存機制汞贸。
緩存分類
1:一級緩存:Hibernate默認的緩存機制,它屬于Session級別的緩存機制印机,也就是說Session關(guān)閉矢腻,緩存數(shù)據(jù)消失。
2:二級緩存:屬于SessionFactory級別的緩存射赛,二級緩存是全局性的多柑,應(yīng)用中的所有Session都共享這個二級緩存。
二級緩存默認是關(guān)閉的楣责,一旦開啟竣灌,當(dāng)我們需要查詢數(shù)據(jù)時诫隅,會先在一級緩存查詢,沒有帐偎,去二級緩存逐纬,還沒有,好削樊,咱們再去數(shù)據(jù)庫豁生,因此緩存機制大大提高了數(shù)據(jù)庫的訪問性能。什么樣的數(shù)據(jù)適合存放到第二級緩存中漫贞?
很少被修改的數(shù)據(jù) 帖子的最后回復(fù)時間
經(jīng)常被查詢的數(shù)據(jù) 電商的地點
不是很重要的數(shù)據(jù)甸箱,允許出現(xiàn)偶爾并發(fā)的數(shù)據(jù)
不會被并發(fā)訪問的數(shù)據(jù)
常量數(shù)據(jù)
擴展:hibernate的二級緩存默認是不支持分布式緩存的。使用 memcahe,redis等中央緩存來代替二級緩存
可以練習(xí)一下Hibernate
http://www.reibang.com/p/93b5f2c7cb83
111.hibernate 對象有哪些狀態(tài)迅脐?
hibernate里對象有三種狀態(tài):
- Transient(瞬時):對象剛new出來芍殖,還沒設(shè)id,設(shè)了其他值谴蔑。
- Persistent(持久):調(diào)用了save()豌骏、saveOrUpdate(),就變成Persistent隐锭,有id窃躲。
- Detached(脫管):當(dāng)session close()完之后,變成Detached钦睡。
112.在 hibernate 中 getCurrentSession 和 openSession 的區(qū)別是什么蒂窒?
- openSession 從字面上可以看得出來,是打開一個新的session對象荞怒,而且每次使用都是打開一個新的session洒琢,假如連續(xù)使用多次,則獲得的session不是同一個對象褐桌,并且使用完需要調(diào)用close方法關(guān)閉session衰抑。
- getCurrentSession ,從字面上可以看得出來撩嚼,是獲取當(dāng)前上下文一個session對象停士,當(dāng)?shù)谝淮问褂么朔椒〞r挖帘,會自動產(chǎn)生一個session對象完丽,并且連續(xù)使用多次時,得到的session都是同一個對象拇舀,這就是與openSession的區(qū)別之一.實際用這個多一點。
113.hibernate 實體類必須要有無參構(gòu)造函數(shù)嗎?為什么吼野?
必須,因為hibernate框架會調(diào)用這個默認構(gòu)造方法來構(gòu)造實例對象薄辅,即Class類的newInstance方法,這個方法就是通過調(diào)用默認構(gòu)造方法來創(chuàng)建實例對象的抠璃。
-站楚。-
注意就是,如果你沒有提供任何構(gòu)造方法搏嗡,虛擬機會自動提供默認構(gòu)造方法(無參構(gòu)造器)窿春,但是如果你提供了其他有參數(shù)的構(gòu)造方法的話,虛擬機就不再為你提供默認構(gòu)造方法采盒,這時必須手動把無參構(gòu)造器寫在代碼里旧乞,否則new Xxxx()是會報錯的,所以默認的構(gòu)造方法不是必須的磅氨,只在有多個構(gòu)造方法時才是必須的尺栖,這里“必須”指的是“必須手動寫出來”。
Mybatis
114.mybatis 中 #{}和 ${}的區(qū)別是什么
#
將傳入的數(shù)據(jù)都當(dāng)成一個字符串烦租,會對自動傳入的數(shù)據(jù)加一個雙引號延赌。如:order by #user_id#
,如果傳入的值是111,那么解析成sql時的值為order by "111"
, 如果傳入的值是id
叉橱,則解析成的sql為order by "id"
.
-
$
將傳入的數(shù)據(jù)直接顯示生成在sql
中皮胡。如:order by $user_id$
,如果傳入的值是111,那么解析成sql時的值為order by user_id
, 如果傳入的值是id赏迟,則解析成的sql為order by id
.
3.#
方式能夠很大程度防止sql注入屡贺。
4.$
方式無法防止Sql注入。
5.$
方式一般用于傳入數(shù)據(jù)庫對象锌杀,例如傳入表名.
6.一般能用#的就別用$.
7.#
和$
在預(yù)編譯處理中是不一樣的甩栈。#
類似jdbc中的PreparedStatement,對于傳入的參數(shù)糕再,在預(yù)處理階段會使用?代替量没,比如:
select * from student where id = ?
;
待真正查詢的時候即在數(shù)據(jù)庫管理系統(tǒng)中(DBMS)才會代入?yún)?shù)。
而${}則是簡單的替換
select * from student where id = 2
;
115.mybatis 有幾種分頁方式突想?
- 數(shù)組分頁
查詢出全部數(shù)據(jù)殴蹄,然后在list中截取需要的部分。
在dao層,創(chuàng)建StudentMapper接口
List<Student> queryStudentsByArray();
xml配置文件StudentMapper.xml文件
<select id="queryStudentsByArray" resultMap="studentmapper">
select * from student
</select>
service接口
List<Student> queryStudentsByArray(int currPage, int pageSize);
實現(xiàn)接口serviceImpl
@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
//查詢?nèi)繑?shù)據(jù)
List<Student> students = studentMapper.queryStudentsByArray();
//從第幾條數(shù)據(jù)開始
int firstIndex = (currPage - 1) * pageSize;
//到第幾條數(shù)據(jù)結(jié)束
int lastIndex = currPage * pageSize;
return students.subList(firstIndex, lastIndex); //直接在list中截取
}
- sql分頁
在dao層,創(chuàng)建StudentMapper接口
List<Student> queryStudentsBySql(Map<String,Object> data);
xml配置文件StudentMapper.xml文件
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
select * from student limit #{currIndex} , #{pageSize}
</select>
service接口
List<Student> queryStudentsBySql(int currPage, int pageSize);
實現(xiàn)接口serviceImpl
@Override
public List<Student> queryStudentsBySql(int currPage, int pageSize) {
Map<String, Object> data = new HashedMap();
data.put("currIndex", (currPage-1)*pageSize);
data.put("pageSize", pageSize);
return studentMapper.queryStudentsBySql(data);
}
- 攔截器分頁(數(shù)據(jù)量很大)
大致說明
利用JDBC對數(shù)據(jù)庫進行操作就必須要有一個對應(yīng)的Statement對象猾担,Mybatis在執(zhí)行Sql語句前也會產(chǎn)生一個包含Sql語句的Statement對象袭灯,而且對應(yīng)的Sql語句是在Statement之前產(chǎn)生的,所以我們就可以在它成Statement之前對用來生成Statement的Sql語句下手绑嘹。在Mybatis中Statement語句是通過RoutingStatementHandler對象的prepare方法生成的稽荧。所以利用攔截器實現(xiàn)Mybatis分頁的一個思路就是攔截StatementHandler接口的prepare方法,然后在攔截器方法中把Sql語句改成對應(yīng)的分頁查詢Sql語句工腋,之后再調(diào)用StatementHandler對象的prepare方法姨丈,即調(diào)用invocation.proceed()畅卓。更改Sql語句這個看起來很簡單,而事實上來說的話就沒那么直觀蟋恬,因為包括sql等其他屬性在內(nèi)的多個屬性都沒有對應(yīng)的方法可以直接取到翁潘,它們對外部都是封閉的,是對象的私有屬性歼争,所以這里就需要引入反射機制來獲取或者更改對象的私有屬性的值了唐础。對于分頁而言,在攔截器里面我們常常還需要做的一個操作就是統(tǒng)計滿足當(dāng)前條件的記錄一共有多少矾飞,這是通過獲取到了原始的Sql語句后一膨,把它改為對應(yīng)的統(tǒng)計語句再利用Mybatis封裝好的參數(shù)和設(shè)置參數(shù)的功能把Sql語句中的參數(shù)進行替換,之后再執(zhí)行查詢記錄數(shù)的Sql語句進行總記錄數(shù)的統(tǒng)計
定義對分頁操作封裝的一個實體類Page
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 對分頁的基本數(shù)據(jù)進行一個簡單的封裝
*/
public class Page<T> {
private int pageNo = 1;//頁碼洒沦,默認是第一頁
private int pageSize = 15;//每頁顯示的記錄數(shù)豹绪,默認是15
private int totalRecord;//總記錄數(shù)
private int totalPage;//總頁數(shù)
private List<T> results;//對應(yīng)的當(dāng)前頁記錄
private Map<String, Object> params = new HashMap<String, Object>();//其他的參數(shù)我們把它分裝成一個Map對象
public int getPageNo() {
return pageNo;
}
public void setPageNo(int pageNo) {
this.pageNo = pageNo;
}
public int getPageSize() {
return pageSize;
}
public void setPageSize(int pageSize) {
this.pageSize = pageSize;
}
public int getTotalRecord() {
return totalRecord;
}
public void setTotalRecord(int totalRecord) {
this.totalRecord = totalRecord;
//在設(shè)置總頁數(shù)的時候計算出對應(yīng)的總頁數(shù),在下面的三目運算中加法擁有更高的優(yōu)先級申眼,所以最后可以不加括號瞒津。
int totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : totalRecord/pageSize + 1;
this.setTotalPage(totalPage);
}
public int getTotalPage() {
return totalPage;
}
public void setTotalPage(int totalPage) {
this.totalPage = totalPage;
}
public List<T> getResults() {
return results;
}
public void setResults(List<T> results) {
this.results = results;
}
public Map<String, Object> getParams() {
return params;
}
public void setParams(Map<String, Object> params) {
this.params = params;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Page [pageNo=").append(pageNo).append(", pageSize=")
.append(pageSize).append(", results=").append(results).append(
", totalPage=").append(totalPage).append(
", totalRecord=").append(totalRecord).append("]");
return builder.toString();
}
}
定義自己的分頁攔截器PageInterceptor.java
/**
* @Intercepts 說明是一個攔截器
* @Signature 攔截器的簽名
* type 攔截的類型 四大對象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
* method 攔截的方法
* args 參數(shù),高版本需要加個Integer.class參數(shù),不然會報錯
*/
@Intercepts( {
@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}) })
public class PageInterceptor implements Interceptor {
private String databaseType;//數(shù)據(jù)庫類型,不同的數(shù)據(jù)庫有不同的分頁方法
/**
* 攔截后要執(zhí)行的方法
*/
public Object intercept(Invocation invocation) throws Throwable {
//對于StatementHandler其實只有兩個實現(xiàn)類括尸,一個是RoutingStatementHandler巷蚪,另一個是抽象類BaseStatementHandler,
//BaseStatementHandler有三個子類濒翻,分別是SimpleStatementHandler屁柏,PreparedStatementHandler和CallableStatementHandler,
//SimpleStatementHandler是用于處理Statement的有送,PreparedStatementHandler是處理PreparedStatement的淌喻,而CallableStatementHandler是
//處理CallableStatement的。Mybatis在進行Sql語句處理的時候都是建立的RoutingStatementHandler雀摘,而在RoutingStatementHandler里面擁有一個
//StatementHandler類型的delegate屬性裸删,RoutingStatementHandler會依據(jù)Statement的不同建立對應(yīng)的BaseStatementHandler,即SimpleStatementHandler阵赠、
//PreparedStatementHandler或CallableStatementHandler涯塔,在RoutingStatementHandler里面所有StatementHandler接口方法的實現(xiàn)都是調(diào)用的delegate對應(yīng)的方法。
//我們在PageInterceptor類上已經(jīng)用@Signature標(biāo)記了該Interceptor只攔截StatementHandler接口的prepare方法清蚀,又因為Mybatis只有在建立RoutingStatementHandler的時候
//是通過Interceptor的plugin方法進行包裹的匕荸,所以我們這里攔截到的目標(biāo)對象肯定是RoutingStatementHandler對象。
RoutingStatementHandler handler = (RoutingStatementHandler) invocation.getTarget();
//通過反射獲取到當(dāng)前RoutingStatementHandler對象的delegate屬性
StatementHandler delegate = (StatementHandler)ReflectUtil.getFieldValue(handler, "delegate");
//獲取到當(dāng)前StatementHandler的 boundSql轧铁,這里不管是調(diào)用handler.getBoundSql()還是直接調(diào)用delegate.getBoundSql()結(jié)果是一樣的每聪,因為之前已經(jīng)說過了
//RoutingStatementHandler實現(xiàn)的所有StatementHandler接口方法里面都是調(diào)用的delegate對應(yīng)的方法。
BoundSql boundSql = delegate.getBoundSql();
//拿到當(dāng)前綁定Sql的參數(shù)對象齿风,就是我們在調(diào)用對應(yīng)的Mapper映射語句時所傳入的參數(shù)對象
Object obj = boundSql.getParameterObject();
//這里我們簡單的通過傳入的是Page對象就認定它是需要進行分頁操作的药薯。
if (obj instanceof Page<?>) {
Page<?> page = (Page<?>) obj;
//通過反射獲取delegate父類BaseStatementHandler的mappedStatement屬性
MappedStatement mappedStatement = (MappedStatement)ReflectUtil.getFieldValue(delegate, "mappedStatement");
//攔截到的prepare方法參數(shù)是一個Connection對象
Connection connection = (Connection)invocation.getArgs()[0];
//獲取當(dāng)前要執(zhí)行的Sql語句,也就是我們直接在Mapper映射語句中寫的Sql語句
String sql = boundSql.getSql();
//給當(dāng)前的page參數(shù)對象設(shè)置總記錄數(shù)
this.setTotalRecord(page,
mappedStatement, connection);
//獲取分頁Sql語句
String pageSql = this.getPageSql(page, sql);
//利用反射設(shè)置當(dāng)前BoundSql對應(yīng)的sql屬性為我們建立好的分頁Sql語句
ReflectUtil.setFieldValue(boundSql, "sql", pageSql);
}
return invocation.proceed();
}
/**
* 攔截器對應(yīng)的封裝原始對象的方法
*/
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
/**
* 設(shè)置注冊攔截器時設(shè)定的屬性
*/
public void setProperties(Properties properties) {
this.databaseType = properties.getProperty("databaseType");
}
/**
* 根據(jù)page對象獲取對應(yīng)的分頁查詢Sql語句救斑,這里只做了兩種數(shù)據(jù)庫類型童本,Mysql和Oracle
* 其它的數(shù)據(jù)庫都 沒有進行分頁
*
* @param page 分頁對象
* @param sql 原sql語句
* @return
*/
private String getPageSql(Page<?> page, String sql) {
StringBuffer sqlBuffer = new StringBuffer(sql);
if ("mysql".equalsIgnoreCase(databaseType)) {
return getMysqlPageSql(page, sqlBuffer);
} else if ("oracle".equalsIgnoreCase(databaseType)) {
return getOraclePageSql(page, sqlBuffer);
}
return sqlBuffer.toString();
}
/**
* 獲取Mysql數(shù)據(jù)庫的分頁查詢語句
* @param page 分頁對象
* @param sqlBuffer 包含原sql語句的StringBuffer對象
* @return Mysql數(shù)據(jù)庫分頁語句
*/
private String getMysqlPageSql(Page<?> page, StringBuffer sqlBuffer) {
//計算第一條記錄的位置,Mysql中記錄的位置是從0開始的脸候。
int offset = (page.getPageNo() - 1) * page.getPageSize();
sqlBuffer.append(" limit ").append(offset).append(",").append(page.getPageSize());
return sqlBuffer.toString();
}
/**
* 獲取Oracle數(shù)據(jù)庫的分頁查詢語句
* @param page 分頁對象
* @param sqlBuffer 包含原sql語句的StringBuffer對象
* @return Oracle數(shù)據(jù)庫的分頁查詢語句
*/
private String getOraclePageSql(Page<?> page, StringBuffer sqlBuffer) {
//計算第一條記錄的位置穷娱,Oracle分頁是通過rownum進行的,而rownum是從1開始的
int offset = (page.getPageNo() - 1) * page.getPageSize() + 1;
sqlBuffer.insert(0, "select u.*, rownum r from (").append(") u where rownum < ").append(offset + page.getPageSize());
sqlBuffer.insert(0, "select * from (").append(") where r >= ").append(offset);
//上面的Sql語句拼接之后大概是這個樣子:
//select * from (select u.*, rownum r from (select * from t_user) u where rownum < 31) where r >= 16
return sqlBuffer.toString();
}
/**
* 給當(dāng)前的參數(shù)對象page設(shè)置總記錄數(shù)
*
* @param page Mapper映射語句對應(yīng)的參數(shù)對象
* @param mappedStatement Mapper映射語句
* @param connection 當(dāng)前的數(shù)據(jù)庫連接
*/
private void setTotalRecord(Page<?> page,
MappedStatement mappedStatement, Connection connection) {
//獲取對應(yīng)的BoundSql运沦,這個BoundSql其實跟我們利用StatementHandler獲取到的BoundSql是同一個對象泵额。
//delegate里面的boundSql也是通過mappedStatement.getBoundSql(paramObj)方法獲取到的。
BoundSql boundSql = mappedStatement.getBoundSql(page);
//獲取到我們自己寫在Mapper映射語句中對應(yīng)的Sql語句
String sql = boundSql.getSql();
//通過查詢Sql語句獲取到對應(yīng)的計算總記錄數(shù)的sql語句
String countSql = this.getCountSql(sql);
//通過BoundSql獲取對應(yīng)的參數(shù)映射
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//利用Configuration携添、查詢記錄數(shù)的Sql語句countSql嫁盲、參數(shù)映射關(guān)系parameterMappings和參數(shù)對象page建立查詢記錄數(shù)對應(yīng)的BoundSql對象。
BoundSql countBoundSql = new BoundSql(mappedStatement.getConfiguration(), countSql, parameterMappings, page);
//通過mappedStatement烈掠、參數(shù)對象page和BoundSql對象countBoundSql建立一個用于設(shè)定參數(shù)的ParameterHandler對象
ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, page, countBoundSql);
//通過connection建立一個countSql對應(yīng)的PreparedStatement對象羞秤。
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = connection.prepareStatement(countSql);
//通過parameterHandler給PreparedStatement對象設(shè)置參數(shù)
parameterHandler.setParameters(pstmt);
//之后就是執(zhí)行獲取總記錄數(shù)的Sql語句和獲取結(jié)果了。
rs = pstmt.executeQuery();
if (rs.next()) {
int totalRecord = rs.getInt(1);
//給當(dāng)前的參數(shù)page對象設(shè)置總記錄數(shù)
page.setTotalRecord(totalRecord);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (pstmt != null)
pstmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/**
* 根據(jù)原Sql語句獲取對應(yīng)的查詢總記錄數(shù)的Sql語句
* @param sql
* @return
*/
private String getCountSql(String sql) {
return "select count(1) from (" + sql + ")";
}
/**
* 利用反射進行操作的一個工具類
*
*/
private static class ReflectUtil {
/**
* 利用反射獲取指定對象的指定屬性
* @param obj 目標(biāo)對象
* @param fieldName 目標(biāo)屬性
* @return 目標(biāo)屬性的值
*/
public static Object getFieldValue(Object obj, String fieldName) {
Object result = null;
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
field.setAccessible(true);
try {
result = field.get(obj);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return result;
}
/**
* 利用反射獲取指定對象里面的指定屬性
* @param obj 目標(biāo)對象
* @param fieldName 目標(biāo)屬性
* @return 目標(biāo)字段
*/
private static Field getField(Object obj, String fieldName) {
Field field = null;
for (Class<?> clazz=obj.getClass(); clazz != Object.class; clazz=clazz.getSuperclass()) {
try {
field = clazz.getDeclaredField(fieldName);
break;
} catch (NoSuchFieldException e) {
//這里不用做處理左敌,子類沒有該字段可能對應(yīng)的父類有瘾蛋,都沒有就返回null。
}
}
return field;
}
/**
* 利用反射設(shè)置指定對象的指定屬性為指定的值
* @param obj 目標(biāo)對象
* @param fieldName 目標(biāo)屬性
* @param fieldValue 目標(biāo)值
*/
public static void setFieldValue(Object obj, String fieldName,
String fieldValue) {
Field field = ReflectUtil.getField(obj, fieldName);
if (field != null) {
try {
field.setAccessible(true);
field.set(obj, fieldValue);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
接著我們在Mybatis的配置文件里面注冊該攔截器
追加
<plugins>
<plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">
<property name="databaseType" value="Oracle"/> //數(shù)據(jù)庫類型
</plugin>
</plugins>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="config/jdbc.properties"></properties>
<typeAliases>
<package name="com.tiantian.mybatis.model"/>
</typeAliases>
<plugins>
<plugin interceptor="com.tiantian.mybatis.interceptor.PageInterceptor">
<property name="databaseType" value="Oracle"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>
</mappers>
這樣我們的攔截器就已經(jīng)定義并且配置好了矫限,接下來我們就來測試一下哺哼。假設(shè)在我們的UserMapper.xml
中有如下這樣一個Mapper映射信息
<select id="findPage" resultType="User" parameterType="page">
select * from t_user
</select>
測試代碼
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Page<User> page = new Page<User>();
page.setPageNo(2);
List<User> users = userMapper.findPage(page);
page.setResults(users);
System.out.println(page);
} finally {
sqlSession.close();
}
- RowBounds分頁
數(shù)據(jù)量小
dao層加入RowBounds參數(shù)
public List<UserBean> queryUsersByPage(String userName, RowBounds rowBounds);
service層
@Override
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.SUPPORTS)
public List<RoleBean> queryRolesByPage(String roleName, int start, int limit) {
return roleDao.queryRolesByPage(roleName, new RowBounds(start, limit));
}
116.mybatis 邏輯分頁和物理分頁的區(qū)別是什么?
- 物理分頁速度上并不一定快于邏輯分頁叼风,邏輯分頁速度上也并不一定快于物理分頁幸斥。
- 物理分頁總是優(yōu)于邏輯分頁:沒有必要將屬于數(shù)據(jù)庫端的壓力加到應(yīng)用端來,就算速度上存在優(yōu)勢,然而其它性能上的優(yōu)點足以彌補這個缺點咬扇。
117.mybatis 是否支持延遲加載甲葬?延遲加載的原理是什么?
什么是延遲加載?
舉個例子:如果查詢訂單并且關(guān)聯(lián)查詢用戶信息懈贺。如果先查詢訂單信息即可滿足要求经窖,當(dāng)我們需要查詢用戶信息時再查詢用戶信息。把對用戶信息的按需去查詢就是延遲加載梭灿。 所以延遲加載即先從單表查詢画侣、需要時再從關(guān)聯(lián)表去關(guān)聯(lián)查詢,大大提高數(shù)據(jù)庫性能堡妒,因為查詢單表要比關(guān)聯(lián)查詢多張表速度要快
這個出自https://blog.csdn.net/eson_15/article/details/51668523
關(guān)聯(lián)查詢:SELECT orders.*, user.username FROM orders, USER WHERE orders.user_id = user.id
延遲加載相當(dāng)于:
SELECT orders.*,
(SELECT username FROM USER WHERE orders.user_id = user.id)username FROM orders
所以這就比較直觀了配乱,也就是說,我把關(guān)聯(lián)查詢分兩次來做,而不是一次性查出所有的搬泥。
第一步只查詢單表orders桑寨,必然會查出orders中的一個user_id字段,
然后我再根據(jù)這個user_id查user表忿檩,也是單表查詢尉尾。下面來總結(jié)一下如何使用這個延遲加載
Mybatis
僅支持association
關(guān)聯(lián)對象和collection
關(guān)聯(lián)集合對象的延遲加載,association
指的就是一對一燥透,collection
指的就是一對多查詢沙咏。在Mybatis
配置文件中,可以配置是否啟用延遲加載lazyLoadingEnabled=true|false
班套。
它的原理是肢藐,使用CGLIB
創(chuàng)建目標(biāo)對象的代理對象,當(dāng)調(diào)用目標(biāo)方法時吱韭,進入攔截器方法吆豹,比如調(diào)用a.getB().getName()
,攔截器invoke()
方法發(fā)現(xiàn)a.getB()
是null值杉女,那么就會單獨發(fā)送事先保存好的查詢關(guān)聯(lián)B對象的sql瞻讽,把B查詢上來,然后調(diào)用a.setB(b)
熏挎,于是a的對象b屬性就有值了速勇,接著完成a.getB().getName()
方法的調(diào)用。這就是延遲加載的基本原理坎拐。
當(dāng)然了烦磁,不光是Mybatis
,幾乎所有的包括Hibernate
哼勇,支持延遲加載的原理都是一樣的都伪。
118.說一下 mybatis 的一級緩存和二級緩存?
一級緩存: 基于 PerpetualCache 的 HashMap 本地緩存积担,其存儲作用域為 Session陨晶,當(dāng) Session flush 或 close 之后,該 Session 中的所有 Cache 就將清空帝璧,默認打開一級緩存先誉。
二級緩存與一級緩存其機制相同,默認也是采用 PerpetualCache的烁,HashMap 存儲褐耳,不同在于其存儲作用域為 Mapper(Namespace),并且可自定義存儲源渴庆,如 Ehcache铃芦。默認不打開二級緩存雅镊,要開啟二級緩存,使用二級緩存屬性類需要實現(xiàn)Serializable序列化接口(可用來保存對象的狀態(tài)),可在它的映射文件中配置<cache/>
對于緩存數(shù)據(jù)更新機制刃滓,當(dāng)某一個作用域(一級緩存 Session/二級緩存Namespaces)的進行了C/U/D 操作后仁烹,默認該作用域下所有 select 中的緩存將被 clear。這么做的目的是避免臟讀
119.mybatis 和 hibernate 的區(qū)別有哪些注盈?
(1)Mybatis和hibernate不同晃危,它不完全是一個ORM框架叙赚,因為MyBatis需要程序員自己編寫Sql語句老客。
(2)Mybatis直接編寫原生態(tài)sql,可以嚴格控制sql執(zhí)行性能震叮,靈活度高胧砰,非常適合對關(guān)系數(shù)據(jù)模型要求不高的軟件開發(fā),因為這類軟件需求變化頻繁苇瓣,一但需求變化要求迅速輸出成果尉间。但是靈活的前提是mybatis無法做到數(shù)據(jù)庫無關(guān)性,如果需要實現(xiàn)支持多種數(shù)據(jù)庫的軟件击罪,則需要自定義多套sql映射文件哲嘲,工作量大。
(3)Hibernate對象/關(guān)系映射能力強媳禁,數(shù)據(jù)庫無關(guān)性好眠副,對于關(guān)系模型要求高的軟件,如果用hibernate開發(fā)可以節(jié)省很多代碼竣稽,提高效率囱怕。
120.mybatis 有哪些執(zhí)行器(Executor)?
Mybatis有三種基本的執(zhí)行器(Executor):
- SimpleExecutor:每執(zhí)行一次update或select毫别,就開啟一個Statement對象娃弓,用完立刻關(guān)閉Statement對象。
- ReuseExecutor:執(zhí)行update或select岛宦,以sql作為key查找Statement對象台丛,存在就使用,不存在就創(chuàng)建砾肺,用完后挽霉,不關(guān)閉Statement對象,而是放置于Map內(nèi)债沮,供下一次使用炼吴。簡言之,就是重復(fù)使用Statement對象疫衩。
- BatchExecutor:執(zhí)行update(沒有select硅蹦,JDBC批處理不支持select),將所有sql都添加到批處理中(addBatch()),等待統(tǒng)一執(zhí)行(executeBatch())童芹,它緩存了多個Statement對象涮瞻,每個Statement對象都是addBatch()完畢后,等待逐一執(zhí)行executeBatch()批處理假褪。與JDBC批處理相同署咽。
121.mybatis 如何編寫一個自定義插件?
轉(zhuǎn)自https://blog.csdn.net/qq_30051265/article/details/80266434
Mybatis自定義插件針對Mybatis四大對象(Executor生音、StatementHandler 宁否、ParameterHandler 、ResultSetHandler )進行攔截缀遍,具體攔截方式為:
Executor:攔截執(zhí)行器的方法(log記錄)
StatementHandler :攔截Sql語法構(gòu)建的處理
ParameterHandler :攔截參數(shù)的處理
ResultSetHandler :攔截結(jié)果集的處理
Mybatis自定義插件必須實現(xiàn)Interceptor接口:
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
}
/*
intercept方法:攔截器具體處理邏輯方法
plugin方法:根據(jù)簽名signatureMap生成動態(tài)代理對象
setProperties方法:設(shè)置Properties屬性
*/
自定義插件demo:
// ExamplePlugin.java
@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget(); //被代理對象
Method method = invocation.getMethod(); //代理方法
Object[] args = invocation.getArgs(); //方法參數(shù)
// do something ...... 方法攔截前執(zhí)行代碼塊
Object result = invocation.proceed();
// do something .......方法攔截后執(zhí)行代碼塊
return result;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
/*一個@Intercepts可以配置多個@Signature慕匠,@Signature中的參數(shù)定義如下:
type:表示攔截的類,這里是Executor的實現(xiàn)類域醇;
method:表示攔截的方法台谊,這里是攔截Executor的update方法;
args:表示方法參數(shù)譬挚。
*/
Kafka
122.kafka 是什么锅铅?
出自https://mp.weixin.qq.com/s/zsYDoCMLZ3yfLWJ2j2ccTw
Kafka可以讓合適的數(shù)據(jù)以合適的形式出現(xiàn)在合適的地方。Kafka的做法是提供消息隊列减宣,讓生產(chǎn)者單往隊列的末尾添加數(shù)據(jù)盐须,讓多個消費者從隊列里面依次讀取數(shù)據(jù)然后自行處理。之前連接的復(fù)雜度是O(N^2)蚪腋,而現(xiàn)在降低到O(N)
Kafka是一種高吞吐量的分布式發(fā)布訂閱消息系統(tǒng)丰歌,
先上傳說中的懵逼圖
分析相關(guān)概念
Producer:Producer即生產(chǎn)者,消息的產(chǎn)生者屉凯,是消息的入口立帖。
Broker:Broker是kafka實例,每個服務(wù)器上有一個或多個kafka的實例悠砚,
我們姑且認為每個broker對應(yīng)一臺服務(wù)器晓勇。每個kafka集群內(nèi)的broker都有一個不重復(fù)的編號,
如圖中的broker-0灌旧、broker-1等……
Topic:消息的主題绑咱,可以理解為消息的分類,kafka的數(shù)據(jù)就保存在topic枢泰。
在每個broker上都可以創(chuàng)建多個topic描融。
Partition:Topic的分區(qū),每個topic可以有多個分區(qū)衡蚂,分區(qū)的作用是做負載窿克,提高kafka的吞吐量骏庸。
同一個topic在不同的分區(qū)的數(shù)據(jù)是不重復(fù)的,partition的表現(xiàn)形式就是一個一個的文件夾年叮!
Replication:每一個分區(qū)都有多個副本具被,副本的作用是做備胎。當(dāng)主分區(qū)(Leader)故障的時候會選擇一個備胎(Follower)上位只损,成為Leader一姿。
在kafka中默認副本的最大數(shù)量是10個,且副本的數(shù)量不能大于Broker的數(shù)量跃惫,
follower和leader絕對是在不同的機器叮叹,同一機器對同一個分區(qū)也只可能存放一個副本(包括自己)。
Message:每一條發(fā)送的消息主體辈挂。
Consumer:消費者衬横,即消息的消費方裹粤,是消息的出口终蒂。
Consumer Group:我們可以將多個消費組組成一個消費者組,在kafka的設(shè)計中同一個分區(qū)的數(shù)據(jù)只能被消費者組中的某一個消費者消費遥诉。
同一個消費者組的消費者可以消費同一個topic的不同分區(qū)的數(shù)據(jù)拇泣,這也是為了提高kafka的吞吐量!
Zookeeper:kafka集群依賴zookeeper來保存集群的的元信息矮锈,來保證系統(tǒng)的可用性霉翔。
kafka的工作流程
寫數(shù)據(jù)
producer就是生產(chǎn)者,是數(shù)據(jù)的入口苞笨,Producer在寫入數(shù)據(jù)的時候永遠的找leader债朵,不會直接將數(shù)據(jù)寫入follower!
需要注意的一點是序芦,消息寫入leader后,follower是主動的去leader進行同步的粤咪!producer采用push模式將數(shù)據(jù)發(fā)布到broker谚中,每條消息追加到分區(qū)中,順序?qū)懭氪疟P寥枝,所以保證同一分區(qū)內(nèi)的數(shù)據(jù)是有序的宪塔!寫入示意圖如下:
kafka為什么要做分區(qū)?
1囊拜、 方便擴展某筐。因為一個topic可以有多個partition,所以我們可以通過擴展機器去輕松的應(yīng)對日益增長的數(shù)據(jù)量冠跷。
2南誊、 提高并發(fā)敢辩。以partition為讀寫單位,可以多個消費者同時消費數(shù)據(jù)弟疆,提高了消息的處理效率戚长。如果某個topic有多個partition,producer又怎么知道該將數(shù)據(jù)發(fā)往哪個partition呢怠苔?kafka中有幾個原則:
1同廉、 partition在寫入的時候可以指定需要寫入的partition,如果有指定柑司,則寫入對應(yīng)的partition迫肖。
2、 如果沒有指定partition攒驰,但是設(shè)置了數(shù)據(jù)的key蟆湖,則會根據(jù)key的值hash出一個partition。
3玻粪、 如果既沒指定partition隅津,又沒有設(shè)置key,則會輪詢選出一個partition劲室。保證消息不丟失是一個消息隊列中間件的基本保證:
kafka通過ACK應(yīng)答機制
伦仍!在生產(chǎn)者向隊列寫入數(shù)據(jù)的時候可以設(shè)置參數(shù)來確定是否確認kafka接收到數(shù)據(jù),這個參數(shù)可設(shè)置的值為0很洋、1充蓝、all。
0
代表producer往集群發(fā)送數(shù)據(jù)不需要等到集群的返回喉磁,不確保消息發(fā)送成功谓苟。安全性最低但是效率最高。
1
代表producer往集群發(fā)送數(shù)據(jù)只要leader應(yīng)答就可以發(fā)送下一條协怒,只確保leader發(fā)送成功涝焙。
all
代表producer往集群發(fā)送數(shù)據(jù)需要所有的follower都完成從leader的同步才會發(fā)送下一條,確保leader發(fā)送成功和所有的副本都完成備份斤讥。安全性最高纱皆,但是效率最低。
最后要注意的是芭商,如果往不存在的topic寫數(shù)據(jù)派草,kafka會自動創(chuàng)建topic,分區(qū)和副本的數(shù)量根據(jù)默認配置都是1铛楣。
保存數(shù)據(jù)
- 先了解Partition 結(jié)構(gòu)
前面說過了每個topic都可以分為一個或多個partition近迁,如果你覺得topic比較抽象,那partition就是比較具體的東西了簸州!Partition在服務(wù)器上的表現(xiàn)形式就是一個一個的文件夾鉴竭,每個partition的文件夾下面會有多組segment文件歧譬,每組segment文件又包含.index文件、.log文件搏存、.timeindex文件(早期版本中沒有)三個文件瑰步, log文件就實際是存儲message的地方,而index和timeindex文件為索引文件璧眠,用于檢索消息缩焦,看圖
如上圖桑李,這個partition有三組segment文件陕悬,每個log文件的大小是一樣的竟闪,但是存儲的message數(shù)量是不一定相等的(每條的message大小不一致)壶唤。文件的命名是以該segment最小offset來命名的,如000.index存儲offset為0~368795的消息地回,kafka就是利用分段+索引的方式來解決查找效率
的問題排宰。 - Message結(jié)構(gòu)
上面說到log文件就實際是存儲message的地方平夜,我們在producer往kafka寫入的也是一條一條的message腰鬼,那存儲在log中的message是什么樣子的呢嵌赠?消息主要包含消息體、消息大小垃喊、offset猾普、壓縮類型……等等!我們重點需要知道的是下面三個:
1本谜、 offset:offset是一個占8byte的有序id號,它可以唯一確定每條消息在parition內(nèi)的位置偎窘!
2乌助、 消息大小:消息大小占用4byte陌知,用于描述消息的大小他托。
3、 消息體:消息體存放的是實際的消息數(shù)據(jù)(被壓縮過)仆葡,占用的空間根據(jù)具體的消息而不一樣赏参。
消費數(shù)據(jù)
消息存儲在log文件后,消費者就可以進行消費了沿盅。我們知道消息隊列通信的兩種模式是點對點模式和發(fā)布訂閱模式把篓。Kafka采用的是點對點的模式,消費者主動的去kafka集群拉取消息腰涧,與producer相同的是韧掩,消費者在拉取消息的時候也是找leader去拉取。
多個消費者可以組成一個消費者組(consumer group)窖铡,每個消費者組都有一個組id疗锐!同一個消費組者的消費者可以消費同一topic下不同分區(qū)的數(shù)據(jù)坊谁,但是不會組內(nèi)多個不同的消費者消費同一分區(qū)的數(shù)據(jù)!;口芍!是不是有點繞。我們看下圖:
圖示是消費者組內(nèi)的消費者小于partition數(shù)量的情況雇卷,所以會出現(xiàn)某個消費者消費多個partition數(shù)據(jù)的情況阶界。
如果是消費者組的消費者多于partition的數(shù)量,那會不會出現(xiàn)多個消費者消費同一個partition的數(shù)據(jù)呢聋庵?上面已經(jīng)提到過
不會
出現(xiàn)這種情況膘融!多出來的消費者不消費任何partition的數(shù)據(jù)。
存儲策略
無論消息是否被消費祭玉,kafka都會保存所有的消息氧映。那對于舊數(shù)據(jù)有什么刪除策略呢?
1脱货、 基于時間岛都,默認配置是168小時(7天)。
2振峻、 基于大小臼疫,默認配置是1073741824。
需要注意的是扣孟,kafka讀取特定消息的時間復(fù)雜度是O(1)烫堤,所以這里刪除過期的文件并不會提高kafka的性能!
查找數(shù)據(jù):利用segment+offset配合查找
假如現(xiàn)在需要查找一個offset為368801的message是什么樣的過程?
1凤价、 先找到offset的368801message所在的segment文件(利用二分法查找)鸽斟,這里找到的就是在第二個segment文件。
2利诺、 打開找到的segment中的.index文件(也就是368796.index文件富蓄,該文件起始偏移量為368796+1,我們要查找的offset為368801的message在該index內(nèi)的偏移量為368796+5=368801慢逾,所以這里要查找的相對offset為5)立倍。由于該文件采用的是稀疏索引的方式存儲著相對offset及對應(yīng)message物理偏移量的關(guān)系,所以直接找相對offset為5的索引找不到侣滩,這里同樣利用二分法查找相對offset小于或者等于指定的相對offset的索引條目中最大的那個相對offset口注,所以找到的是相對offset為4的這個索引。
3胜卤、 根據(jù)找到的相對offset為4的索引確定message存儲的物理偏移位置為256疆导。打開數(shù)據(jù)文件,從位置為256的那個地方開始順序掃描直到找到offset為368801的那條Message。
這套機制是建立在offset為有序的基礎(chǔ)上澈段,利用segment+有序offset+稀疏索引+二分查找+順序查找等多種手段來高效的查找數(shù)據(jù)悠菜!至此,消費者就能拿到需要處理的數(shù)據(jù)進行處理了败富。那每個消費者又是怎么記錄自己消費的位置呢悔醋?在早期的版本中,消費者將消費到的offset維護zookeeper中兽叮,consumer每間隔一段時間上報一次芬骄,這里容易導(dǎo)致重復(fù)消費,且性能不好鹦聪!在新的版本中消費者消費到的offset已經(jīng)直接維護在kafk集群的__consumer_offsets這個topic中账阻!
123.使用 kafka 集群需要注意什么?
集群的數(shù)量不是越多越好泽本,最好不要超過 7 個淘太,因為節(jié)點越多,消息復(fù)制需要的時間就越長规丽,整個群組的吞吐量就越低蒲牧。
集群數(shù)量最好是單數(shù),因為超過一半故障集群就不能用了赌莺,設(shè)置為單數(shù)容錯率更高冰抢。
消息隊列之 RabbitMQ
124.rabbitmq介紹
官網(wǎng)https://www.rabbitmq.com/getstarted.html
RabbitMQ是實現(xiàn)AMQP(高級消息隊列協(xié)議)的消息中間件的一種,服務(wù)器端用Erlang語言編寫艘狭,支持多種客戶端挎扰,如:Python、Ruby缓升、.NET鼓鲁、Java、JMS港谊、C、PHP橙弱、ActionScript歧寺、XMPP、STOMP等棘脐,支持AJAX斜筐。用于在分布式系統(tǒng)中存儲轉(zhuǎn)發(fā)消息。
- 什么是AMQP蛀缝?
AMQP顷链,Advanced Message Queuing Protocol,翻譯成中文即高級消息隊列協(xié)議屈梁。它是一個提供統(tǒng)一消息服務(wù)的應(yīng)用層二進制協(xié)議嗤练¢涣耍基于此協(xié)議的客戶端與消息中間件可傳遞消息,并不受不同平臺煞抬、開發(fā)語言和操作系統(tǒng)的影響霜大,即可跨平臺傳輸。 -
AMQP模型
主要組件和邏輯概念
1.Publisher(生產(chǎn)者):消息生產(chǎn)者革答,AMQP定義了消息的格式战坤,所以生產(chǎn)者需要按照AMQP消息格式生產(chǎn)數(shù)據(jù)。
2.Broker(服務(wù)器):Broker是一個物理上的服務(wù)器(或虛擬機)残拐,它是部署了消息中間件并接收處理客戶端請求的實體途茫。
3.Exchange(交換機):它是一個邏輯上的概念,負責(zé)接收消息溪食,是整個消息中間件的入口囊卜。
4.Queue(隊列):它負責(zé)保存消息,并將消息轉(zhuǎn)發(fā)給消費者眠菇。
5.Binding(綁定):它是Exchange與Queue之間的虛擬連接边败,實現(xiàn)了根據(jù)不同的Routing Key(路由規(guī)則)將消息路由到對應(yīng)的Queue上。
6.Message(消息):消息捎废,本質(zhì)上就是服務(wù)器與客戶端傳輸?shù)臄?shù)據(jù)笑窜,由元信息和消息體組成。
7.Virtual Host(虛擬主機):它是一個邏輯上的概念登疗,一個Broker上可以有多個Virtual Host排截,它起到一個命名空間的作用,可以讓服務(wù)于不同業(yè)務(wù)的Exchange和Queue隔離開辐益,是實現(xiàn)權(quán)限控制的最小單位断傲。
8.Connection(連接):客戶端與服務(wù)器之間的網(wǎng)絡(luò)連接。
9.Channel(信道):一次客戶端與服務(wù)器之間的通信智政,相當(dāng)于JMS中的Session的概念认罩。
- AMQP的主要處理流程
生產(chǎn)者是將消息發(fā)送到Exchange,Exchange根據(jù)路由規(guī)則Routing Key將消息路由到不同的Queue上续捂,如果Queue上有消費者監(jiān)聽垦垂,則消費者可以獲得消息。生產(chǎn)者在生產(chǎn)消息的時候是不知道消費者的狀態(tài)的牙瓢,消費者在消費消息時也是不知道消息是從哪個生產(chǎn)者來的劫拗,即生產(chǎn)者與消費者之間的完全解耦的。
125.rabbitmq 的使用場景有哪些矾克?
你可以根據(jù)自己想需要進行應(yīng)用页慷,具體代碼可以去官網(wǎng)
六種應(yīng)用場景
在應(yīng)用場景2中描述了如何使用work queue將耗時的task分配到不同的worker中酒繁。但是滓彰,如果我們task是想在遠程的計算機上運行一個函數(shù)并等待返回結(jié)果呢。這根場景2中的描述是一個完全不同的事件欲逃。這一模式被稱為遠程過程調(diào)用
126.rabbitmq 有哪些重要的角色找蜜?
RabbitMQ 中重要的角色有:生產(chǎn)者、消費者和代理:
生產(chǎn)者:消息的創(chuàng)建者稳析,負責(zé)創(chuàng)建和推送數(shù)據(jù)到消息服務(wù)器洗做;
消費者:消息的接收方,用于處理數(shù)據(jù)和確認消息彰居;
代理:就是 RabbitMQ 本身诚纸,用于扮演“快遞”的角色,本身不生產(chǎn)消息陈惰,只是扮演“快遞”的角色
127.rabbitmq 有哪些重要的組件畦徘?
- ConnectionFactory(連接管理器):應(yīng)用程序與Rabbit之間建立連接的管理器,程序代碼中使用抬闯。
- Channel(信道):消息推送使用的通道井辆。
- Exchange(交換器):用于接受、分配消息溶握。
- Queue(隊列):用于存儲生產(chǎn)者的消息杯缺。
- RoutingKey(路由鍵):用于把生成者的數(shù)據(jù)分配到交換器上。
- BindingKey(綁定鍵):用于把交換器的消息綁定到隊列上睡榆。
128.rabbitmq 中 vhost 的作用是什么萍肆?
vhost是rabbitmq分配權(quán)限的最小細粒度。比如我們可以為一個用戶分配一個可以訪問哪個或者哪一些vhost的權(quán)限胀屿。
但是不能為用戶分配一個可以訪問哪一些exchange塘揣,或者queue的權(quán)限,因為rabbitmq的權(quán)限細粒度沒有細化到交換器和隊列宿崭,他的最小細粒度是vhost;一個broker可以開設(shè)多個vhost亲铡,用于不同用戶的權(quán)限分離
129. rabbitmq 的消息是怎么發(fā)送的?
首先客戶端必須連接到 RabbitMQ 服務(wù)器才能發(fā)布和消費消息葡兑,客戶端和 rabbit server 之間會創(chuàng)建一個 tcp 連接奴愉,一旦 tcp 打開并通過了認證(認證就是你發(fā)送給 rabbit 服務(wù)器的用戶名和密碼),你的客戶端和 RabbitMQ 就創(chuàng)建了一條 amqp 信道(channel)铁孵,信道是創(chuàng)建在“真實” tcp 上的虛擬連接,amqp 命令都是通過信道發(fā)送出去的房资,每個信道都會有一個唯一的 id蜕劝,不論是發(fā)布消息,訂閱隊列都是通過這個信道完成的。
130.rabbitmq 怎么保證消息的穩(wěn)定性岖沛?
1.提供了事務(wù)的功能暑始。(要么成功,要么失敗婴削,保持數(shù)據(jù)一致性,同步阻塞卡出等待你是成功還是失斃染怠;太耗性能會造成吞吐量的下降唉俗。)
2.通過將 channel 設(shè)置為 confirm(確認)模式嗤朴。
生產(chǎn)者將信道設(shè)置成confirm模式,一旦信道進入confirm模式虫溜,所有在該信道上面發(fā)布的消息都將會被指派一個唯一的ID(從1開始)雹姊,一旦消息被投遞到所有匹配的隊列之后,broker就會發(fā)送一個確認給生產(chǎn)者(包含消息的唯一ID)衡楞,這就使得生產(chǎn)者知道消息已經(jīng)正確到達目的隊列了吱雏,如果消息和隊列是可持久化的,那么確認消息會在將消息寫入磁盤之后發(fā)出瘾境,broker回傳給生產(chǎn)者的確認消息中delivery-tag域包含了確認消息的序列號歧杏,此外broker也可以設(shè)置basic.ack的multiple域,表示到這個序列號之前的所有消息都已經(jīng)得到了處理.
confirm模式最大的好處在于他是異步的迷守,一旦發(fā)布一條消息犬绒,生產(chǎn)者應(yīng)用程序就可以在等信道返回確認的同時繼續(xù)發(fā)送下一條消息,當(dāng)消息最終得到確認之后盒犹,生產(chǎn)者應(yīng)用便可以通過回調(diào)方法來處理該確認消息懂更,如果RabbitMQ因為自身內(nèi)部錯誤導(dǎo)致消息丟失,就會發(fā)送一條nack消息急膀,生產(chǎn)者應(yīng)用程序同樣可以在回調(diào)方法中處理該nack消息沮协;
消費者(Consumer)的Confirm模式:消費者在聲明隊列時,可以指定noAck參數(shù)卓嫂,當(dāng)noAck=false時慷暂,RabbitMQ會等待消費者顯式發(fā)回ack信號后才從內(nèi)存(和磁盤,如果是持久化消息的話)中移去消息晨雳。采用消息確認機制后行瑞,只要令noAck=false,消費者就有足夠的時間處理消息(任務(wù))餐禁,不用擔(dān)心處理消息過程中消費者進程掛掉后消息丟失的問題血久,因為RabbitMQ會一直持有消息直到消費者顯式調(diào)用basic.Ack為止。
basic.ack: 用于肯定確認帮非,multiple參數(shù)用于多個消息確認氧吐。
basic.recover:是路由不成功的消息可以使用recovery重新發(fā)送到隊列中讹蘑。
basic.reject:是接收端告訴服務(wù)器這個消息我拒絕接收,不處理,可以設(shè)置是否放回到隊列中還是丟掉,而且只能一次拒絕一個消息,官網(wǎng)中有明確說明不能批量拒絕消息筑舅,為解決批量拒絕消息才有了basicNack座慰。
basic.nack:可以一次拒絕N條消息,客戶端可以設(shè)置basicNack方法的multiple參數(shù)為true翠拣,服務(wù)器會拒絕指定了delivery_tag的所有未確認的消息(tag是一個64位的long值版仔,最大值是9223372036854775807)。
131. rabbitmq 怎么避免消息丟失误墓?
1.消息持久化
2.ACK確認機制
3.設(shè)置集群鏡像模式
4.消息補償機制
- 1.消息持久化
RabbitMQ 的消息默認存放在內(nèi)存上面蛮粮,如果不特別聲明設(shè)置,消息不會持久化保存到硬盤上面的优烧,
如果節(jié)點重啟或者意外crash掉蝉揍,消息就會丟失。
所以就要對消息進行持久化處理畦娄。如何持久化又沾,下面具體說明下:
要想做到消息持久化,必須滿足以下三個條件熙卡,缺一不可杖刷。
1)Exchange 設(shè)置持久化(durable字段設(shè)置為true)
2)Queue 設(shè)置持久化( durable 設(shè)置為 true)
3)Message持久化發(fā)送:發(fā)送消息設(shè)置發(fā)送模式deliveryMode=2,代表持久化消息
3.設(shè)置集群鏡像模式
缺點: 系統(tǒng)的吞吐量會有所下降4.消息補償機制
消息補償機制需要建立在消息要寫入DB日志驳癌,發(fā)送日志滑燃,接受日志,兩者的狀態(tài)必須記錄颓鲜。
然后根據(jù)DB日志記錄check 消息發(fā)送消費是否成功表窘,不成功,進行消息補償措施甜滨,重新發(fā)送消息處理乐严。
132.要保證消息持久化成功的條件有哪些?
聲明隊列/交換器必須設(shè)置持久化 durable 設(shè)置為 true.
消息推送投遞模式必須設(shè)置持久化衣摩,deliveryMode 設(shè)置為 2(持久)昂验。
消息已經(jīng)到達持久化交換器。
消息已經(jīng)到達持久化隊列
133.rabbitmq 持久化有什么缺點艾扮?
持久化的缺地就是降低了服務(wù)器的吞吐量既琴,因為使用的是磁盤而非內(nèi)存存儲,從而降低了吞吐量泡嘴「Χ鳎可盡量使用 ssd 硬盤來緩解吞吐量的問題。
134.rabbitmq 有幾種廣播類型酌予?
三種廣播模式:
- fanout: 所有bind到此exchange的queue都可以接收消息(純廣播填物,綁定到RabbitMQ的接受者都能收到消息)纹腌;
- direct: 通過routingKey和exchange決定的那個唯一的queue可以接收消息;
- topic:所有符合routingKey(此時可以是一個表達式)的routingKey所bind的queue可以接收消息
135.rabbitmq 怎么實現(xiàn)延遲消息隊列滞磺?
1.通過消息過期后進入死信交換器,再由交換器轉(zhuǎn)發(fā)到延遲消費隊列莱褒,實現(xiàn)延遲功能击困;
2.用 RabbitMQ-delayed-message-exchange 插件實現(xiàn)延遲功能。
136.rabbitmq 集群有什么用广凸?
集群主要有以下兩個用途:
1.高可用:某個服務(wù)器出現(xiàn)問題阅茶,整個 RabbitMQ 還可以繼續(xù)使用;
2.高容量:集群可以承載更多的消息量谅海。
137.rabbitmq 節(jié)點的類型有哪些脸哀?
磁盤節(jié)點:消息會存儲到磁盤。
內(nèi)存節(jié)點:消息都存儲在內(nèi)存中扭吁,重啟服務(wù)器消息丟失撞蜂,性能高于磁盤類型。
138.rabbitmq 集群搭建需要注意哪些問題侥袜?
1.各節(jié)點之間使用“--link”連接蝌诡,此屬性不能忽略。
2.各節(jié)點使用的 erlang cookie 值必須相同枫吧,此值相當(dāng)于“秘鑰”的功能浦旱,用于各節(jié)點的認證。
3.整個集群中必須包含一個磁盤節(jié)點九杂。
139.rabbitmq 每個節(jié)點是其他節(jié)點的完整拷貝嗎颁湖?為什么?
不是例隆,原因有以下兩個:
1.存儲空間的考慮:如果每個節(jié)點都擁有所有隊列的完全拷貝甥捺,這樣新增節(jié)點不但沒有新增存儲空間,反而增加了更多的冗余數(shù)據(jù)裳擎;
2.性能的考慮:如果每條消息都需要完整拷貝到每一個集群節(jié)點涎永,那新增節(jié)點并沒有提升處理消息的能力,最多是保持和單節(jié)點相同的性能甚至是更糟鹿响。
140.rabbitmq 集群中唯一一個磁盤節(jié)點崩潰了會發(fā)生什么情況羡微?
如果唯一磁盤的磁盤節(jié)點崩潰了,不能進行以下操作:
不能創(chuàng)建隊列
不能創(chuàng)建交換器
不能創(chuàng)建綁定
不能添加用戶
不能更改權(quán)限
不能添加和刪除集群節(jié)點
唯一磁盤節(jié)點崩潰了惶我,集群是可以保持運行的妈倔,但你不能更改任何東西。
141.rabbitmq 對集群節(jié)點停止順序有要求嗎绸贡?
RabbitMQ 對集群的停止的順序是有要求的盯蝴,應(yīng)該先關(guān)閉內(nèi)存節(jié)點毅哗,最后再關(guān)閉磁盤節(jié)點。如果順序恰好相反的話捧挺,可能會造成消息的丟失虑绵。
聲明
文章引用參考來自:https://mp.weixin.qq.com/s/q_SduX1NfyDtA-l-3tiXEA
文章只是作為自己的學(xué)習(xí)筆記,借鑒了網(wǎng)上的許多案例