后端开发知识框架汇总

后端开发知识框架汇总

Spring框架

Spring/Springboot/SpringMVC

Spring

​ 其是一个引擎,众多衍生产品例如boot、security、jpa等等;但他们的基础都是Spring的ioc和 aop,ioc 提供了依赖注入的容器, aop解决了面向切面编程,然后在此两者的基础上实现了其他延伸产品的高级功能。

Springboot

​ 其是进一步实现了auto-configuration自动配置(另外三大神器actuator监控,cli命令行接口,starter依赖),降低了项目搭建的复杂度。它主要是为了解决使用Spring框架需要进行大量的配置太麻烦的问题。

SpringMVC

​ 其是spring基础之上的一个MVC框架,主要处理web开发的路径映射和视图渲染,属于spring框架中WEB层开发的一部分,其主要分为Model(模型)、VIew(视图)、Controller(控制器)三部分,具体的工作流程如下。后端开发知识框架汇总_第1张图片

Spring的作用域

1、singleton

​ 保持容器中只存在唯一的单例。Controller亦采用的是单例模式,同时采用ThreadLocal的方式来解决可能存在的线程不安全的问题。

2、propetype

​ 为每个bean请求申请一个实例。申请之后不会再进行管理。

3、request

​ 对每一个网络请求,会申请一个对应的bean容器。

4、session

​ 会保证每个session中有一个对应的实例,在session失效后bean也会失效。

5、global-session

​ 其与portlet有关,

IOC/DI(控制反转/依赖注入)

​ IOC理论提出的观点大体是这样的:借助于“第三方”实现具有依赖关系的对象之间的解耦,简单来说,**就是对象的创建不再有程序本身去创建,而是交给IOC容器去实现。**IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。Spring中实现依赖注入的主要方式是采用@Autowired的注释,

AOP(面向切面编程)

​ AOP即面向切面编程,其最主要的实现方式是采用代理实现的。代理又主要分为两种:静态代理动态代理

静态代理主要是令代理人和被代理人共同实现一个类,实现代码如下:

interface MyInterface{
   public void dosomething();
}
class Principal implements MyInterface{
    @Override
    public void dosomething() {
        System.out.println("嗨!我是被代理人!");
    }
}
class Agent implements MyInterface{
    Principal principal;
    public Agent(Principal principal) {
        this.principal = principal;
    }
    @Override
    public void dosomething() {
        System.out.println("我是代理人!");
        principal.dosomething();
        System.out.println("我代理完成了!");
    }
}
public class StaticProxy {
    public static void main(String[] args) {
        Principal p = new Principal();
        Agent agent = new Agent(p);
        agent.dosomething();
    }
}

JDK的动态代理CGlib的动态代理

  • JDK动态代理:JDK的动态代理主要通过代理类和被代理类实现同一个接口实现,同时采用反射的机制,调用对应类的接口实现的AOP。
//JDK动态代理
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface MyInterface{
   public void dosomething();
}

class Principal implements MyInterface{
    @Override
    public void dosomething() {
        System.out.println("嗨!我是被代理人!");
    }
}

class Agent implements InvocationHandler {
    Object principal;
    public Agent(Object principal) {
        this.principal = principal;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //甚至连方法都调用对应的反射!这样连组合排列都不用了!
        System.out.println("-----------before-----------");
        method.invoke(principal, args);
        System.out.println("-----------after-----------");
        return null;
    }
}

public class StaticProxy {
    public static void main(String[] args) {
        Principal p = new Principal();
        Agent agent = new Agent(p);
        MyInterface myinterface = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),
                new Class[] {MyInterface.class}, agent);    //创建对应的被代理对象
        myinterface.dosomething();
    }
}
  • CGlib动态代理

底层实现与JDK的动态代理不同,其采用修改对应的ASM的字节码的方式实现。

@Transctional 注解:主要是通过反射获取bean的注解信息,利用AOP对编程式事务进行封装实现。?

Springboot自动装配原理

​ Springboot自动配置原理主要采用三个注解实现,其分别是:

1、EnableAutoConfiguration:该注解主要开启Springboot的自动配置功能。

​ (1) @AutoConfigurationPackage:利用register注册对应的容器,将指定的一个包下的所有组件导入进来。(默认是main包下的所有组件。)

​ (2) @Import(AutoConfigurationImportSelector.class):底层采用工厂模式从配置文件中读取对应的组件(注意并不是全部加载,会对组件采用@ConditionOnMissingBean等条件进行判断 )

2、SpringConfiguration,标示启动类是一个SpringConfiguration类,扫描并加载都容器中。

3、ComponetScan,指定对应的包扫描的路径。

img

Spring循环依赖

循环依赖–>循环引用—>即2个或以上bean 互相持有对方,最终形成闭环。eg:A依赖B,B依赖C,C又依赖A。

Spring循环依赖场景

①:构造器的循环依赖。【这个Spring解决不了】

StudentA有参构造是StudentB。StudentB的有参构造是StudentC,StudentC的有参构造是StudentA ,这样就产生了一个循环依赖的情况,我们都把这三个Bean交给Spring管理,并用有参构造实例化。

②【setter循环依赖】field属性的循环依赖【setter方式 单例,默认方式–>通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【放入三层缓存中】。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】Spring是先将Bean对象实例化【依赖无参构造函数】—>再设置对象属性的

原因:Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。

Spring 三级缓存

​ Spring在启动过程中,使用到了三个map,称为三级缓存。第一级缓存主要保存Bean到指定的容器中,而第二第三级的缓存主要用于解决循环依赖。

singletonFactories : 单例对象工厂的cache

earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】

singletonObjects:单例对象的cache

img

​ 假设初始化A bean时,发现A bean依赖B bean,而B还没有初始化的话,需要暂停A的注入先注入B容器。那么此时new出来的A对象放哪里,直接放在容器Map里显然不合适,半残品怎么能用,所以需要提供一个可以标记创建中bean(A)的Map(即二级缓存),可以提前暴露正在创建的bean供其他bean依赖。而如果初始化A所依赖的bean B时,发现B也需要注入一个A的依赖(即发生循环依赖),则B可以从创建中的beanMap中直接获取A对象(创建中)注入A,然后完成B的初始化,返回给正在注入属性的A,最终A也完成初始化,皆大欢喜。这块的实现主要由第二级的缓存实现。

​ 三级缓存中提到出现循环依赖才去解决,也就是说出现循环依赖时,才会执行工厂的getObject生成(获取)早期依赖,这个时候就需要给它挪个窝了,因为真正暴露的不是工厂,而是对象,所以需要使用一个二级缓存保存暴露的早期对象(earlySingletonObjects),同时移除提前暴露的工厂。

//具体解决依赖的代码
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);

Spring启动过程

(1)创建beanFactory,加载xml配置文件。
(2)解析配置文件转化beanDefination,获取到bean的所有属性、依赖及初始化用到的各类处理器等。
(3)刷新beanFactory容器,初始化所有单例bean。
(4)注册所有的单例bean并返回可用的容器,一般为扩展的applicationContext。

Bean生存周期

1、实例化Bean

​ 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。 对于ApplicationContext容器,当容器启动结束后,便实例化所有的bean。 容器通过获取BeanDefinition对象中的信息进行实例化。并且这一步仅仅是简单的实例化,并未进行依赖注入。 实例化对象被包装在BeanWrapper对象中,BeanWrapper提供了设置对象属性的接口,从而避免了使用反射机制设置属性。

2、依赖注入

​ 实例化后的对象被封装在BeanWrapper对象中,并且此时对象仍然是一个原生的状态,并没有进行依赖注入。
紧接着,Spring根据BeanDefinition中的信息进行依赖注入。并且通过BeanWrapper提供的设置属性的接口完成依赖注入。

3、 注入Aware接口

​ 紧接着,Spring会检测该对象是否实现了xxxAware接口,并将相关的xxxAware实例注入给bean。

4、BeanPostProcessor

​ 当经过上述几个步骤后,bean对象已经被正确构造,但如果你想要对象被使用前再进行一些自定义的处理,就可以通过BeanPostProcessor接口实现。 该接口提供了两个函数:

  • postProcessBeforeInitialzation( Object bean, String beanName )
    当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
    这个函数会先于InitialzationBean执行,因此称为前置处理。
    所有Aware接口的注入就是在这一步完成的。
  • postProcessAfterInitialzation( Object bean, String beanName )
    当前正在初始化的bean对象会被传递进来,我们就可以对这个bean作任何处理。
    这个函数会在InitialzationBean完成后执行,因此称为后置处理。

5. InitializingBean与init-method

​ 当BeanPostProcessor的前置处理完成后就会进入本阶段。 InitializingBean接口只有一个函数:

  • afterPropertiesSet()

这一阶段也可以在bean正式构造完成前增加我们自定义的逻辑,但它与前置处理不同,由于该函数并不会把当前bean对象传进来,因此在这一步没办法处理对象本身,只能增加一些额外的逻辑。
若要使用它,我们需要让bean实现该接口,并把要增加的逻辑写在该函数中。然后Spring会在前置处理完成后检测当前bean是否实现了该接口,并执行afterPropertiesSet函数。

当然,Spring为了降低对客户代码的侵入性,给bean的配置提供了init-method属性,该属性指定了在这一阶段需要执行的函数名。Spring便会在初始化阶段执行我们设置的函数。init-method本质上仍然使用了InitializingBean接口。

6. DisposableBean和destroy-method

​ 和init-method一样,通过给destroy-method指定函数,就可以在bean销毁前执行指定的逻辑。

后端开发知识框架汇总_第2张图片

mysql数据库

数据库引擎

数据库引擎主要分为InnodbMyIsam。其主要的不同有以下几点:

1、锁粒度,Innodb采用行锁,且仅仅在索引生效的时候起作用,其余时候退化为表锁。MyIsam采用表锁。

2、事务性,Innodb支持事务性,而MyIsam不支持事务。

3、外键,Innodb支持外键,而MyIsam不支持外键。

4、聚簇索引,Innodb支持聚簇索引,即索引和数据放在一起,不需要进行二次回表搜索。而MyIsam不支持。

MyIsam结构如下:

img

Innodb结构如下:

img

Mysql语句查询过程

以SELECT name,COUNT(name) AS num FROM student WHERE grade < 60 GROUP BY name HAVING num >= 2 ORDER BY num DESC,name ASC LIMIT 0,2 为例子。

1、加载内存,一条查询的sql语句先执行的是 FROM student 负责把数据库的表文件加载到内存中去,如图1.0中所示。(mysql数据库在计算机上也是一个进程,cpu会给该进程分配一块内存空间,在计算机‘服务’中可以看到,该进程的状态)

2、数据过滤,WHERE grade < 60,对数据进行过滤,取出符合条件的记录行,生成一张临时表。

3、临时表划分,GROUP BY name会把数据切分成若干临时表。

4、数据挑选,SELECT 的执行读取规则分为sql语句中有无GROUP BY两种情况。

(1)当没有GROUP BY时,SELECT 会根据后面的字段名称对内存中的一张临时表整列读取。

(2)当查询sql中有GROUP BY时,会对内存中的若干临时表分别执行SELECT,而且只取各临时表中的第一条记录,然后再形成新的临时表。这就决定了查询sql使用GROUP BY的场景下,SELECT后面跟的一般是参与分组的字段和聚合函数,否则查询出的数据要是情况而定。另外聚合函数中的字段可以是表中的任意字段,需要注意的是聚合函数会自动忽略空值。

5,二次过滤,HAVING num >= 2对数据再次过滤,与WHERE语句不同的是HAVING 用在GROUP BY之后,WHERE是对FROM student从数据库表文件加载到内存中的原生数据过滤,而HAVING 是对SELECT 语句执行之后的临时表中的数据过滤,所以说column AS otherName ,otherName这样的字段在WHERE后不能使用,但在HAVING 后可以使用。但HAVING的后使用的字段只能是SELECT 后的字段,SELECT后没有的字段HAVING之后不能使用。

6,数据排序,ORDER BY num DESC,name ASC对以上的临时表按照num,name进行排序。

7,限制获取,LIMIT 0,2取排序后的前两个。

连接语句

1、左外连接:是A和B的交集再并上A的所有数据。

2、右外连接:是A和B的交集再并上B的所有数据。

后端开发知识框架汇总_第3张图片

3、内连接,也就是返回两个表的交集(阴影)部分。

删除语句:

Delete :删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行)。

Delete from table_name where col_name = XXX //删除某一行
Delete * from table_name

Drop:删除数据表或数据库,或删除数据表字段。

drop database db_name	//删除数据库
drop table table_name // 删除特定表
alter table db_name drop column col_name	//删除数据库表特定字段

Truncate:删除数据表中的数据(仅数据表中的数据,不删除表)。

truncate table table_name //删除表中数据

区别:删除数据的速度,一般来说: drop> truncate > delete。

  • DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。执行 DELETE 语句后,表仍会包含空页。
  • truncate和delete只删除表中的数据,drop会连表的结构一起删除。
  • delete是DML语句,操作会被放到 rollback segment中,事务提交后才生效,以便进行进行回滚操作(可恢复)。truncate、drop是DLL语句,操作立即生效,原数据不放到 rollback segment中,不能回滚(不可恢复)。
  • delete操作之后表或索引所占的空间不会减少,truncate操作之后表和索引所占用的空间会恢复到初始大小,drop语句将表所占用的空间全部释放。

删除表内部分数据用delete,删除表内所有数据并且保留表的结构用truncate,删除表或者库用drop

Explain SQL主要包含的信息如下(https://www.cnblogs.com/yhtboke/p/9467763.html):

这里写图片描述

id:select查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序 .

Select_type:查询的类型,主要是用于区分普通查询、联合查询、子查询等复杂的查询

  1. SIMPLE:简单的select查询,查询中不包含子查询或者union
  2. PRIMARY:查询中包含任何复杂的子部分,最外层查询则被标记为primary
  3. SUBQUERY:在select 或 where列表中包含了子查询
  4. DERIVED:在from列表中包含的子查询被标记为derived(衍生),mysql或递归执行这些子查询,把结果放在零时表里
  5. UNION:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived
  6. UNION RESULT:从union表获取结果的select

Table:指明查询语句是查询的哪一张表。

Type:访问类型,sql查询优化中一个很重要的指标,结果值从好到坏依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL,一般来说,好的sql查询至少达到range级别,最好能达到ref。

  1. system:表只有一行记录(等于系统表),这是const类型的特例,平时不会出现,可以忽略不计
  2. const:表示通过索引一次就找到了,const用于比较primary key 或者 unique索引。因为只需匹配一行数据,所以速度很快。如果将主键置于where列表中,mysql就能将该查询转换为一个const。
  3. eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键 或 唯一索引扫描。
  4. ref:非唯一性索引扫描,返回匹配某个单独值的所有行。本质是也是一种索引访问,它返回所有匹配某个单独值的行,然而他可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体
  5. range:只检索给定范围的行,使用一个索引来选择行。key列显示使用了那个索引。一般就是在where语句中出现了bettween、<、>、in等的查询。这种索引列上的范围扫描比全索引扫描要好。只需要开始于某个点,结束于另一个点,不用扫描全部索引。
  6. index:Full Index Scan,index与ALL区别为index类型只遍历索引树。这通常为ALL块,应为索引文件通常比数据文件小。(Index与ALL虽然都是读全表,但index是从索引中读取,而ALL是从硬盘读取)
  7. ALL:Full Table Scan,遍历全表以找到匹配的行。

possible_keys:查询涉及到的字段上存在索引,则该索引将被列出,但不一定被查询实际使用。

key:实际使用的索引,如果为NULL,则没有使用索引。 查询中如果使用了覆盖索引,则该索引仅出现在key列表中。

Key_len:表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len是根据表定义计算而得的,不是通过表内检索出的。

ref:显示索引的那一列被使用了,如果可能,是一个常量const。

row:根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数

Extra,不适合在其他字段中显示,但是十分重要的额外信息

1、Using filesort :
mysql对数据使用一个外部的索引排序,而不是按照表内的索引进行排序读取。也就是说mysql无法利用索引完成的排序操作成为“文件排序” 由于索引是先按email排序、再按address排序,所以查询时如果直接按address排序,索引就不能满足要求了,mysql内部必须再实现一次“文件排序”

2、Using temporary:
使用临时表保存中间结果,也就是说mysql在对查询结果排序时使用了临时表,常见于order by 和 group by

3、Using index:
表示相应的select操作中使用了覆盖索引(Covering Index),避免了访问表的数据行,效率高
如果同时出现Using where,表明索引被用来执行索引键值的查找
如果没用同时出现Using where,表明索引用来读取数据而非执行查找动作 覆盖索引(Covering Index):也叫索引覆盖。就是select列表中的字段,只用从索引中就能获取,不必根据索引再次读取数据文件,换句话说查询列要被所建的索引覆盖。
注意:
a、如需使用覆盖索引,select列表中的字段只取出需要的列,不要使用select *
b、如果将所有字段都建索引会导致索引文件过大,反而降低crud性能

4、Using where :
使用了where过滤

5、Using join buffer :
使用了链接缓存

6、Impossible WHERE:
where子句的值总是false,不能用来获取任何元祖

7、select tables optimized away:
在没有group by子句的情况下,基于索引优化MIN/MAX操作或者对于MyISAM存储引擎优化COUNT(*)操作,不必等到执行阶段在进行计算,查询执行计划生成的阶段即可完成优化

8、distinct:
优化distinct操作,在找到第一个匹配的元祖后即停止找同样值得动作

索引

​ 索引可以极大的提高查询访问速度,但是会降低插入,删除,更新表的速度,因为在执行写操作的时候还要操作索引文件。

单列索引

一个单列索引只包含一列,其又具体分为如下三个索引。

  • 普通索引:最基本的索引,其构造的基本SQL语句如下。
CREATE INDEX index_name
ON table_name (column_name)
  • 唯一索引:唯一索引,要求值不重复,可以为空。
CREATE UNIQUE INDEX index_name
ON table_name (column_name)
  • 主键索引:与唯一索引类似,但不允许有空值。

组合索引

​ 一个组合索引包含多列值,其创建的SQL语句如下:

CREATE INDEX index_name
ON table_name (column_name1, column_name2)

​ 组合索引的匹配服从“最左匹配”原则。如果你建立了组合索引(nickname,account,createdTime_Index),那么他实际包含的是3个索引 (nickname)、 (nickname,account) 、(nickname,account,created_time)。

聚簇索引/非聚簇索引:

1、聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。

2、在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。

索引数据结构

Mysql中采用的索引有B+树索引和Hash索引两种。

一、B+树索引,底层基于B+树实现,其是一种多叉的平衡树,之所以采用B+树,有以下几点原因:

​ 1、B+树支持范围索引,相比较B树在范围搜索的时候速度更快,同时由于信息只保存在叶子节点,因此搜索的效率比较稳定。(但需要注意B树单次的搜索平均是优于B+树的)。

​ 2、B+树树深较低大致为Logm(n),比起二叉树,红黑树深度要低,搜索速度更快。相较于B树,因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少,指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低;

二、哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。对于hash相同的,采用链表的方式解决冲突。在Mysql中InnoDB引擎有一个特殊的功能叫做自适应哈希索引,它会在内存中基于B-Tree索引的基础上面创建一个哈希索引,这让B-Tree索引具备了一些哈希索引的优点。

**两者区别:**B+树索引查询效率稳定,且支持范围查询。而哈希索引的查询一次只能查询一行数据,且不支持范围查询,但查询速度要优于B+树索引。

索引优化

最左匹配:组合索引的匹配服从“最左匹配”原则。如果你建立了组合索引(nickname,account,createdTime_Index),那么他实际包含的是3个索引 (nickname)、 (nickname,account) 、(nickname,account,created_time)。因此在实际使用中,尽可能按照最左匹配的顺序使用索引。

回表:回表的意思是首先根据普通索引数,找出对应的主键值,再根据这个主键的值去对应的聚簇索引的树上进行搜索,并找到对应的行信息。因此,非聚簇索引查询了两次树,而聚簇索引只查询了一次。

后端开发知识框架汇总_第4张图片

索引覆盖:如果索引的叶子节点包含了要查询的数据,那么就不用回表查询了,也就是说这种索引包含(亦称覆盖)所有需要查询的字段的值,我们称这种索引为覆盖索引

**索引下推:**当使用索引条件下推优化时,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。

索引失效

  1. 有or必全有索引;
  2. 复合索引未用左列字段,最左匹配原则。
  3. like以%开头,最左匹配原则。
  4. 需要类型转换;
  5. where中索引列有运算或函数;
  6. 如果mysql觉得全表扫描更快时(数据少);
  7. 如果索引存在空数据,则会失效。

事务及四大特性

事务:是作为一个单元的一组有序的数据库操作。如果组中的所有 操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功,并进行回滚撤销对数据库造成的影响。

1、原子性(Atomicity),mysql采用undo log实现,对没有完成的事务进行回滚。undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。

2、一致性(Consistency),一致性由其余三大属性一起形成。

3、隔离性(Isolation),锁+MVCC多版本并发控制

4、持久性(Durability)。mysql提供了一个缓冲池buffer用以加快数据的检索,当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。

Redo log

​ 于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。innodb通过force log at commit机制实现事务的持久性,即在事务提交的时候,必须先将该事务的所有事务日志写入到磁盘上的redo log file中进行持久化。这样可以通过读取redo log来避免在宕机(即红色线数据丢失)的情况下,损失一些已经提交的事物。(先执行第七步再执行红线部分)

img

为什么写入redo log会比将redo log buffer写入磁盘要快呢?

(1)刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
(2)刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。

Binlog:Mysql binlog是二进制日志文件,用于记录mysql的所有的数据更新或者潜在更新(比如DELETE语句执行删除而实际并没有符合条件的数据),在mysql主从复制中就是依靠的binlog。binlog有以下三种模式:

  • row模式:日志中会记录每一行数据被修改的形式,然后在 slave 端再对相同的数据进行修改。row 模式只记录要修改的数据,只有 value,不会有 sql 多表关联的情况。
  • statement模式:每一条修改数据的 sql 都会记录到 master 的 binlog 中,slave 在复制的时候,sql 进程会解析成和原来在 master 端执行时的相同的 sql 再执行。(优势是:只记录进行修改的SQL语句,而不是记录修改后对应的行,因此保存的数据量比row少。)
  • mixed模式:Mixed,实际上就是前两种模式的结合。在 Mixed 模式下,MySQL 会根据执行的每一条具体的 SQL 语句来区分对待记录的日志形式,也就是在 statement 和 row 之间选择一种。

Undo log:

​ undo log和redo log记录物理日志不一样,它是逻辑日志。**可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。**当执行rollback时,就可以从undo log中的逻辑记录读取到相应的内容并进行回滚。有时候应用到行版本控制的时候,也是通过undo log来实现的:当读取的某一行被其他事务锁定时,它可以从undo log中分析出该行记录以前的数据是什么,从而提供该行版本信息,让用户实现非锁定一致性读取。

undo log是采用段(segment)的方式来记录的,每个undo操作在记录的时候占用一个undo log segment。innodb存储引擎对undo的管理采用段的方式。rollback segment称为回滚段,每个回滚段中有1024个undo log segment

隔离级别

未提交读

解决了更新丢失的问题,如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。但可能出现脏读

已提交读(Oracle默认)

如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,已提交读通过对数据的版本进行校验解决了脏读问题,但是已提交读存在不可重复读

可重复读(mysql默认级别)

可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。可重复读的主要实现方式是采用MVCC+Next-Key-Lock

可序列化

提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低。可序列化的主要实现方式是将所有的查询语句修改成对应的select in share mode,来使得事务串型执行。

在这里插入图片描述

Mysql锁相关

1、**记录锁(行锁):**记录锁是 封锁记录,记录锁也叫行锁,例如:

SELECT * FROM `test` WHERE `id`=1 FOR UPDATE;

它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。for update是一种行级锁,又叫排它锁,一旦用户对某个行施加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。只有当出现如下之一的条件,才会释放共享更新锁:

  • 1、执行提交(COMMIT)语句
  • 2、退出数据库(LOG OFF)
  • 3、程序停止运行

2、间隙锁(Gap Locks),是封锁索引记录中的间隔,或者第一条索引记录之前的范围,又或者最后一条索引记录之后的范围。但是间隙锁不是排他锁,有可能出现死锁的情况。

3、Next Key Lock(行锁+间隙锁),每个 next-key lock 是前开后闭区间。即包含了记录锁和间隙锁,即锁定一个范围,并且锁定记录本身。

1、当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。

2、快照读

​ 像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

Mysql死锁情况

在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)共享锁(Share Locks,即S锁)。当数据对象被加上排它锁时,其他的事务不能对它读取和修改。加了共享锁的数据对象可以被其他事务读取,但不能修改。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。

死锁的第一种情况(访问表顺序造成死锁),一个用户A访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。

解决方法:这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

死锁的第二种情况(共享锁升级成独占锁),用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操作,很容易就出现这种死锁的情况。

解决方法:

  • 对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
  • 使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造 成脏数据被更新到数据库中。
  • 使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统, 当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读 出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。

死锁的第三种情况(行锁上升为表锁)

如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞死锁

**解决方法:**SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

MVCC(多版本并发控制)

MVCC(多版本并发控制),是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 它的实现原理主要是依赖记录中的 隐式字段,undo日志 ,Read View 来实现的。

一、隐式字段:

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • TRX_ID,6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • ROLL_PTR,7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • ROW_ID,6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了
在这里插入图片描述

查询一条记录的流程大致如下:

  1. 如果被访问版本的trx_id=creator_id,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
  2. 如果被访问版本的trx_id
  3. 被访问版本的trx_id>=max_trx_id,表明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可以被当前事务访问。如果数据不可以被访问,则会按照roll_pointer查找上一个版本的记录。
  4. 被访问版本的trx_id是否在m_ids列表中。
    4.1 是,创建ReadView时,该版本还是活跃的,该版本不可以被访问。顺着版本链找下一个版本的数据,继续执行上面的步骤判断可见性,如果最后一个版本还不可见,意味着记录对当前事务完全不可见
    4.2 否,创建ReadView时,生成该版本的事务已经被提交,该版本可以被访问

二、undo日志

undo log主要分为两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log,只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log
    事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

三、Read View(读视图)

​ Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。主要是用来做可见性判断的,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

数据库三大范式

第一范式(1NF)必须不包含重复组的关系,即每一列都是不可拆分的原子项

缺点:存在数据冗余、更新异常、插入异常、删除异常。因此需要进一步采用更高级的范式。

img

第二范式(2NF):关系模式必须满足第一范式,并且所有非主属性都完全依赖于主码,但是可以有传递依赖。注意,符合第二范式的关系模型可能还存在数据冗余、更新异常等问题。举例如**关系模型(职工号,姓名,职称,项目号,项目名称)**中,职工号->姓名,职工号->职称,而项目号->项目名称。显然依赖关系不满足第二范式,常用的解决办法是差分表格,比如拆分为职工信息表和项目信息表。

第三范式(3NF)关系模型满足第二范式,所有非主属性对任何候选关键字都不存在传递依赖。即每个属性都跟主键有直接关系而不是间接关系,像:a–>b–>c。

​ 比如**Student表(学号,姓名,年龄,性别,所在院校,院校地址,院校电话)**这样一个表结构,就存在上述关系。 学号–> 所在院校 --> (院校地址,院校电话)。我们应该拆开来,如下:(学号,姓名,年龄,性别,所在院校)–(所在院校,院校地址,院校电话)

BC范式(BCNF)是指在满足第三范式的前提下,每一个决定因素都包含码。 根据定义我们可以得到结论,一个满足BC范式的关系模式有:

  1. 所有非主属性对每一个码都是完全函数依赖;
  2. 所有主属性对每一个不包含它的码也是完全函数依赖;
  3. 没有任何属性完全函数依赖于非码的任何一组属性。

第四范式(4NF)

定义: 限制关系模式的属性之间不允许有非平凡且非函数依赖的多值依赖。

理解: 显然一个关系模式是4NF,则必为BCNF。也就是说,当一个表中的非主属性互相独立时(3NF),这些非主属性不应该有多值,若有多值就违反了4NF。

第五范式(5NF)投影联合范式(PJ / NF) 。 它旨在通过以多种格式分隔语义连接的关系来存储多值事实,以最大程度地减少关系数据库中的冗余。

设计模式

单例模式(懒汉型线程安全)

//懒汉单例模式,线程安全,但是性能很低。
public class SingletonModel {
    private static SingletonModel single ;
    private SingletonModel(){ } //虚化从而使得
    public synchronized static SingletonModel GetSingletonModel() {
        if (single == null){
            single = new SingletonModel();
        }
        return single;
    }
}

//懒汉单例模式,双重校验模式DCL。 保持高并发且是Lazy Loading。
public class SingletonModel {
    private static SingletonModel single ;
    private SingletonModel(){ } //虚化从而使得
    public  static SingletonModel GetSingletonModel() {
        if (single == null){
            synchronized (SingletonModel.class){
                if (single == null){
                    single = new SingletonModel();
                }
            }
        }
        return single;
    }
}

单例模式(饿汉型线程安全)

//饿汉直接是线程安全的。
public class SingletonModel {
    private static SingletonModel single = new SingletonModel();
    private SingletonModel(){ } //虚化从而使得
    public static SingletonModel GetSingletonModel() {
        return single;
    }
}

工厂模式:

//工厂模式代码,主要的思想是采用接口来接受对应的对象。
interface Shape{
    public void draw();
}

class Rectangle implements Shape{
    @Override
    public void draw() {
        //实现对应的画形状接口
        System.out.println("画矩形");
    }
}

class Cycle implements Shape{
    @Override
    public void draw() {
        //画圆形
        System.out.println("画圆形");
    }
}

class Square implements Shape{
    @Override
    public void draw() {
        //画正方形
        System.out.println("画正方形");
    }
}

class ShapeFactory{
    public Shape getShape(String str){
        switch (str) {
            case "Cycle":
                return new Cycle();
            case "Square":
                return new Square();
            case "Rectangle":
                return new Rectangle();
        }
        return null;
    }
}
public class Factory{
    public static void main(String[] args) {
        ShapeFactory sf = new ShapeFactory();
        Shape s = sf.getShape("Cycle");
        Shape s1 = sf.getShape("Rectangle");
        Shape s2 = sf.getShape("Square");
        s.draw();
        s1.draw();
        s2.draw();
    }
}

抽象工厂模式:

观察者模式:?

//观察者模式代码


策略模式:

里氏替换原则

定义:子类对象(object ofsubtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

​ 子类在设计的时候,要遵守父类的行为约定。父类定义了函数的行为约定,那子类可以改变函数的内部实现逻辑,但不能改变函数原有的行为约定。这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。实际上,定义中父类和子类之间的关系,也可以替换成接口和实现类之间的关系。

多线程实例

消费者生产者模型

具体代码如下:(需要学会秒写)

//生产者消费者模型,编写思路1、首先需要写对应的资源互斥区storage;2、紧接着需要写资源互斥区中get和set方法。
//3、紧接着再写Producer线程和Customer线程即可.
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;

class storage{
    //资源互斥区域
    Deque<Integer> items= new LinkedList<>();
    int size= 10;
    int i=0;
    public synchronized int get(){
            while(items.size()<=0){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            notifyAll();
            return items.pollFirst();
    }

    public synchronized int set(){
            while (items.size()>=size){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            items.addLast(i++);
            notifyAll();
            return i-1;
    }
}

class producer extends Thread{
    storage s;
    producer(storage s) {
        this.s = s;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" puts "+ s.set());
        }
    }
}

class customer extends Thread{
    storage s;
    customer(storage s){
        this.s = s;
    }

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+" gets "+s.get());
            //睡一段时间防止多次读取
        }
    }
}

public class ProducerAndCustomer {
    public static void main(String[] args) {
        storage s = new storage();
        customer c= new customer(s);
        producer p = new producer(s);
        customer c1 = new customer(s);
        producer p1 = new producer(s);
        c.start();
        c1.start();
        p.start();
        p1.start();
    }
}

线程交替打印

import java.util.concurrent.*;
public class ThreadABC {
    public static class ThreadPrinter implements Runnable {
        private String name;
        private Object prev;
        private Object self;

        ThreadPrinter(String name,Object prev,Object self){
            this.name = name;
            this.prev = prev;
            this.self = self;
        }
        @Override
        public void run() {
            int count=10;
            while(count>0){
                synchronized (prev){
                    synchronized (self){
                        System.out.print(name);
                        count--;
                        self.notifyAll();
                    }
                    if(count==0){
                        prev.notifyAll();
                    }else{
                        try {
                            if (count == 0) {// 如果count==0,表示这是最后一次打印操作,通过notifyAll操作释放对象锁。
                                prev.notifyAll();
                            } else {
                                prev.wait(); // 立即释放 prev锁,当前线程休眠,等待唤醒
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        Thread pa = new Thread(new ThreadPrinter("A", c, a));
        Thread pb = new Thread(new ThreadPrinter("B", a, b));
        Thread pc = new Thread(new ThreadPrinter("C", b, c));
        pa.start();
        Thread.sleep(10);
        pb.start();
        Thread.sleep(10);
        pc.start();
        Thread.sleep(10);
    }
}

线程交替打印变形(一)

线程一打印1,2,3,4…

线程二打印A,B,C,D…

要求最后的输出结果为:1A2B3C4D…

import java.util.Scanner;
class storage{
    boolean produce = true;
    public synchronized void producenums(int i){
        while(!produce){
            try {
                wait();
            }
            catch(Exception ignored){
            }
        }
        System.out.println(i);
        produce = false;
        notifyAll();
    }

    public synchronized void produceletters(char c){
        while(produce){
            try {
                wait();
            }
            catch(Exception ignored){
            }
        }
        System.out.println(c);
        produce = true;
        notifyAll();
    }
}

class Threadnums extends Thread{
    storage s;
    Threadnums(storage s){
        this.s = s;
    }

    @Override
    public void run(){
        for(int i=0;i<100;i++){
            s.producenums(i+1);
        }
    }
}

class Threadletters extends Thread{
    storage s;
    char a = 'A';
    Threadletters(storage s){
        this.s = s;
    }

    @Override
    public void run(){
        for(int i=0;i<100;i++){
            s.produceletters((char) (a+i%26));
        }
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        storage s = new storage();
        Threadnums n = new Threadnums(s);
        Threadletters l = new Threadletters(s);
        n.start();
        Thread.sleep(3);
        l.start();
    }
}

Linux

常见命令:

-wc :统计指定文件中的字节数、字数、行数,并将统计结果显示输出。

-ps:用于报告当前系统的进程状态。使用该命令可以确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等,总之大部分信息都是可以通过执行该命令得到的。命令参数的选项如下:

  • a:显示现行终端机下的所有程序,包括其他用户的程序。
  • u:以用户为主的格式来显示系统状况。
  • x:显示所有程序,包括历史进程。
  • -e:显示所有进程(同a)
  • -f:显示UID、PPIP、C与STIME栏
  • -l:显示进程详细信息

-lsof:用于查看你进程开打的文件,打开文件的进程,进程打开的端口(TCP、UDP)。找回/恢复删除的文件。是十分方便的系统监视工具,因为 lsof 需要访问核心内存和各种文件,所以需要root用户执行。

git

常见命令:

-git pull:命令用于从远程获取代码并合并本地的版本。

-git fetch :命令用于从远程获取代码库。但是不与当前的代码进行合并。

-git push:命令用于从将本地的分支版本上传到远程并合并。

-git add . :他会监控工作区的状态树,使用它会把工作时的所有变化提交到暂存区,包括文件内容修改(modified)以及新文件(new),但不包括被删除的文件。

Git 与 SVN 区别

  • 1、Git 是分布式的,SVN 不是:这是 Git 和其它非分布式的版本控制系统,例如 SVN,CVS 等,最核心的区别。
  • **2、Git 把内容按元数据方式存储,而 SVN 是按文件:**所有的资源控制系统都是把文件的元信息隐藏在一个类似 .svn、.cvs 等的文件夹里。
  • **3、Git 分支和 SVN 的分支不同:**分支在 SVN 中一点都不特别,其实它就是版本库中的另外一个目录。
  • **4、Git 没有一个全局的版本号,而 SVN 有:**目前为止这是跟 SVN 相比 Git 缺少的最大的一个特征。
  • **5、Git 的内容完整性要优于 SVN:**Git 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

Mybatis框架

Mybatis原理

​ 1、mybatis应用程序通过SqlSessionFactoryBuilder从mybatis-config.xml配置文件(也可以用Java文件配置的方式,需要添加@Configuration)来构建SqlSessionFactory(SqlSessionFactory是线程安全的);

​ 2、然后,SqlSessionFactory的实例直接开启一个SqlSession,再通过SqlSession实例获得Mapper对象并运行Mapper映射的SQL语句,完成对数据库的CRUD和事务提交,之后关闭SqlSession。

详细流程如下:

1、加载mybatis全局配置文件(mybatis-config.xml),解析配置文件,MyBatis基于XML配置文件生成Configuration,和一个个MappedStatement(包括了参数映射配置、动态SQL语句、结果映射配置),其对应着