赞
踩
HashMap的底层实现原理?以jdk7为例说明:
HashMap map = new HashMap();
在实例化以后,底层创建了长度是16的一维数组Entry[] table
。 可能已经执行过多次put
, map.put(key1,value1)
:
key1
所在类的hashCode()
计算key1
哈希值,此哈希值经过某种算法计算以后,得到在Entry
数组中的存放位置。
key1-value1
添加成功。 ----情况1key1
和已经存在的一个或多个数据的哈希值:
key1
的哈希值与已经存在的数据的哈希值都不相同,此时key1-value1
添加成功。----情况2key1
的哈希值和已经存在的某一个数据(key2-value2)
的哈希值相同,继续比较:调用key1
所在类的equals(key2)
方法,比较:
equals()
返回false
:此时key1-value1
添加成功。----情况3equals()
返回true
:使用value1
替换value2
。key1-value1
和原来的数据以链表的方式存储。在不断的添加过程中,会涉及到扩容问题,当超出临界值(且要存放的位置非空)时,扩容。默认的扩容方式:扩容为原来容量的2倍,并将原有的数据复制过来。
jdk8
相较于jdk7
在底层实现方面的不同:
new HashMap()
:jdk8
底层没有创建一个长度为16的数组jdk8
底层的数组是:Node[]
,而非Entry[]
put()
方法时,底层创建长度为16的数组jdk8
中底层结构:数组+链表+红黑树。jdk7
:新的元素指向旧的元素。jdk8
:旧的元素指向新的元素)jdk8
中当数组的某一个索引位置上的元素以链表形式存在的数据个数 > 8 且当前数组的长度 > 64时,此时此索引位置上的所数据改为使用红黑树存储。DEFAULT_INITIAL_CAPACITY : HashMap的默认容量,16
DEFAULT_LOAD_FACTOR:HashMap的默认加载因子:0.75
threshold:扩容的临界值,=容量*填充因子:16 * 0.75 => 12
TREEIFY_THRESHOLD:Bucket中链表长度大于该默认值,转化为红黑树:8
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64
HashMap:作为Map的主要实现类;线程不安全的,效率高;存储null的key和value,扩容的话是原来的二倍
Hashtable:作为古老的实现类;线程安全的,效率低;不能存储null的key和value
扩容的话是原来的二倍+1
Propertie:常用来处理配置文件。key和value都是String类型
HashMap的底层:数组+链表 (jdk7及之前) , 数组+链表+红黑树 (jdk 8)
相同点:
(1) 都是java.util包下的类
(2) 都实现了Map接口,存储方式都是key-value形式
(3) 同时也都实现了Serializable和Cloneable接口
(4) 负载因子都是0.75
负载因子(loadFactor):
当我们第一次创建 HashMap 的时候,就会指定其容量(如果未明确指定,默认是 16),随着我们不断的向 HashMap 中 put 元素的时候,就有可能会超过其容量,那么就需要有一个扩容机制。 所谓扩容,就是扩大 HashMap 的容量,在向 HashMap中添加元素过程中,如果 元素个数(size)超过临界值(threshold)的时候,就会进行自动扩容(resize),并且,在扩容之后,还需要对 HashMap 中原有元素进行rehash,即将原来桶中的元素重新分配到新的桶中。
在 HashMap 中,临界值(threshold) = 负载因子(loadFactor) * 容量(capacity)。 loadFactor 是装载因子(负载因子),表示 HashMap 满的程度,默认值为 0.75f,也就是说默认情况下,当 HashMap 中元素个数达到了容量的 3/4 的时候就会进行自动扩容。
不同点:
(1) HashMap
是非线程安全,效率高;HashTable
是线程安全的,效率低
(2) HashMap
允许null
作为键或值,HashTable
不允许,运行时会报NullPointerException
(3) HashMap
添加元素使用的是自定义hash算法,HashTable
使用的是key
的hashCode
(4) HashMap
在数组+链表的结构中引入了红黑树,HashTable
没有
(5) HashMap
初始容量为16,HashTable
初始容量为11
(6) HashMap
扩容是当前容量翻倍,HashTable
是当前容量翻倍+1
(7) HashMap
只支持Iterator
遍历,HashTable
支持Iterator
和Enumeration
(8) HashMap
与HashTable
的部分方法不同,比如HashTable
有contains
方法。
相同点:两者都可以在java.util包的类
不同点:
Collection接口:单列集合,用来存储一个一个的对象
List接口:存储有序的、可重复的数据。 -->“动态”数组,替换原的数组
Object[ ] elementData
存储,适用于查找频繁。ArrayList
高;底层使用双向链表存储Object[ ] elementData
存储Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”(无序互斥)
Set
接口的主要实现类;线程不安全的;可以存储null值HashSet
的子类;遍历其内部数据时,可以按照添加的顺序遍历LinkedHashSet
效率高于HashSet
。Map:双列数据,存储key-value
对的数据 —类似于高中的函数:y = f(x)
null
的key
和value
扩容的话是原来的二倍map
元素时,可以照添加的顺序实现遍历。HashMap
底层结构基础上,添加了一对指针,指向前一个和后一个元素。HashMap
。key-value
对进行排序,实现排序遍历。此时考虑key的自然排序或定制排序null
的key
和value
扩容的话是原来的二倍+1key
和value
都是String
类型ArrayList和LinkedList的异同
相同点:
不同点
(1)什么是hash冲突?
哈希算法被计算的数据是无限的,而计算后的结果范围有限,所以总会存在不同的数据经过计算后得到的值相同,这就是哈希冲突
(2)HashMap如何解决hash冲突?
String
:不可变的字符序列;底层使用char[ ]
存储StringBuffer
:可变的字符序列;线程安全的,效率低;底层使用char[]
存储StringBuilder
:可变的字符序列;jdk5.0
新增的,线程不安全的,效率高;底层使用char[]
存储(1)不可变性:当你给一个字符串重新赋值之后,老值并没有在内存中销毁,而是重新开辟一块空间存储新值。
(2)String 类中使用 final 关键字修饰字符数组来保存字符串,private final char value[ ],所以String 对象是不可变的。
(3)线程安全
在Lambda表达式中,将其划分了几块。这一行就是
lambda
表达式。
() -> System.out.println(“使用Lambda表达式”);
下面我们对lambda
的格式进行一个介绍:(1)左边括号:
lambda
的形参列表,就好比是我们定义一个接口,里面有一个抽象方法,这个抽象方法的形参列表。(2)箭头:
lambda
的操作符,所以你看见这个箭头心中知道这是一个lambda
表达式就可以了。(3)右边
lambda
体:就好比是我们实现了接口中的抽象方法。
2、接口的默认方法和静态方法
默认方法使得开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的实现类也同时实现这个新添加的方法。
简单说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其方法。
3、方法引用
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来更加紧凑简洁,减少冗余代码。
方法引用通过方法的名字来指向一个方法
方法引用使用一对冒号 ::
public class Test { public static void main(String[] args) { //传统的方式来实现MyFunction/得到一个实现接口的对象 可以使用 //匿名内部类 //MyFunction<Desk, String> hf = new MyFunction<Desk, String>() { // @Override // public String apply(Desk desk) { // return "hello desk"; // } //}; //String val = hf.apply(new Desk()); //System.out.println("val-" + val); MyFunction<Desk,String> hf2 = Desk::getBrand; String val2 = hf2.apply(new Desk()); System.out.println("val2-" + val2);//这里输出结果就是val2-北京牌 } } //定义一个函数式接口: 有且只有一个抽象方法的接口 //我们可以使用@FunctionalInterface 来标识一个函数式接口 //MyFunction是一个函数式接口 (是自定义泛型接口) @FunctionalInterface interface MyFunction<T, R> { R apply(T t); //抽象方法: 表示根据类型T的参数,获取类型R的结果 //public void hi(); //函数式接口,依然可以有多个默认实现方法 default public void ok() { System.out.println("ok"); } } @FunctionalInterface interface MyInterface { public void hi(); } class Desk { //Bean private String name = "my desk"; private String brand = "北京牌"; private Integer id = 10; //getter setter tostring 方法 }
4、重复注解
在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次,Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次;Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。
5、扩展注解的支持
Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。
6、Optional类
Optional 类是一个可以为null的容器对象。如果值存在则isPresent()
方法会返回true,调用get()方法会返回该对象。
Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Java应用中最常见的bug就是空指针异常,Optional 类的引入很好的解决空指针异常,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。
7、Stream(流)
什么是 Stream?
Stream API(java.util.stream)是把真正的函数式编程风格引入到Java库中,这是目前为止最大的一次对Java库的完善,以便开发者能够写出高效率、干净、简洁的代码。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程,它其实是一连串支持连续、并行聚集操作的元素,从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了!
Stream(流)是一个来自数据源的元素队列并支持聚合操作,Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对
Java 集合运算和表达的高阶抽象。这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选,
排序,聚合等。元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal
operation)得到前面处理的结果
8、日期时间 API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,其中有:
非线程安全 java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
设计很差 Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java8 在 java.time 包下提供了很多新的 API,新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。以下仅介绍两个比较重要的 API:
9、JavaScript引擎Nashorn
从 JDK 1.8 开始,Nashorn取代Rhino(JDK 1.6, JDK1.7) 成为 Java 的嵌入式 JavaScript 引擎。Nashorn 完全支持 ECMAScript 5.1 规范以及一些扩展,它使用基于 JSR 292 的新语言特性,其中包含在 JDK 7 中引入的 invokedynamic,将 JavaScript 编译成 Java 字节码,与先前的 Rhino 实现相比,这带来了 2 到 10倍的性能提升。
Nashorn的主要用途:允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。
10、Base64
在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。
除了这十大新特性之外,还有另外的一些新特性:
11、更好的类型推测机制:
Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。
12、编译器优化:
Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。
13、并行(parallel)数组:
支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。
14、并发(Concurrency):
在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。
15、Nashorn引擎jjs:
基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。
16、类依赖分析器jdeps:
可以显示Java类的包级别或类级别的依赖。
17、JVM的PermGen空间被移除:
取代它的是Metaspace(JEP 122)。
两个流同时只能使用一个。 使用了字节流,就不能再使用字符流,反之亦然,否则就会报错
为什么需要序列化与反序列化?
当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。当两个
Java 进程进行通信时,需要 Java 序列化与反序列化实现进程间的对象传送。换句话说,一方面,发送方需要把这个 Java
对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出 Java 对象。
优点:
多线程有两种实现方法,
同步的实现方面有两种
在线程上添加锁。Lock 或者synchronized 可以将线程不安全变为线程安全,
Lock 能完成synchronized 所实现的所有功能;
Collections.synchronizedList();
也能实现线程安全
主要不同点:
使用new
关键字
Class
对象的newInstance
()方法
Class的newInstance()方法可以在运行时创建一个类的新实例。它等效于使用new操作符,但是语法更加动态。
构造函数对象的newInstance
()方法
Constructor的newInstance()方法可以在运行时创建一个类的新实例,并且可以传入构造函数的参数。这种方式比Class的newInstance()方法更加灵活,因为可以选择不同的构造函数。
对象反序列化
反序列化是将对象从字节流中恢复的过程。通过序列化后,可以把对象存储到文件或网络中,然后再通过反序列化的方式恢复成对象。
Object
对象的clone()
方法
clone( )方法可以创建对象的一个副本,并且可以重写clone()方法来实现深克隆。
使用工厂模式
可以将对象的创建和使用解耦。通过定义一个对象工厂,可以更加灵活地产生对象。
1)死锁定义:
死锁是指两个或多个进程在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
2)产生死锁的必要条件
3)怎么避免死锁
答:Java 中的23 种设计模式:Builder( 建造模式),Factory( 工厂模式),Factory Method(工厂方法模式),Prototype(原始模型模式),Singleton(单例模式),Facade(门面模式),Adapter(适配器模式), Bridge(桥梁模式), Composite(合成模式),Decorator(装饰模式), Flyweight(享元模式), Proxy(代理模式),Command(命令模式), Interpreter(解释器模式), Visitor(访问者模式),Iterator(迭代子模式), Mediator(调停者模式), Memento(备忘录模式),Observer(观察者模式),State(状态模式),Strategy(策略模式),Template Method(模板方法模式), Chain Of Responsibleity(责任链模式)。
工厂模式:
根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。
首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
如果一段代码可以保证多个线程访问的时候正确操作共享数据,那么它是线程安全的。
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。
二者都是java多态的体现
(1) 重载:
两同一不同:同一个类里面、相同方法名
参数列表不同:参数个数不同,参数类型不同
(2)重写
子类重写父类的方法。
只传入一个参数 substring(int beginIndex)
传一个参数,含义为将字符串从索引号为beginIndex开始截取,一直到字符串末尾。注意第一个字符的索引值为零,截取时包含索引beginIndex的字符;示例代码如下:
String oldStr = "zifu截取练习ing";
String str = oldStr.substring(5);
System.out.println(str);
运行结果:
取练习ing
传入两个参数 substring(int beginIndex, int endIndex)
从索引号beginIndex开始到索引号endIndex结束,返回结果包含索引为beginIndex的字符,不包含索引endIndex的字符;
String oldStr = "zifu截取练习ing";
String str = oldStr.substring(0,5);
System.out.println(str);
运行结果:
zifu截
只传一个参数:split(String regex)
参数支持正则或普通字符,根据给定正则表达式或字符匹配拆分此字符串。示例代码如下:
String oldStr = "China,Japan,美国,俄罗斯";
String[] strs = oldStr.split(",");//根据,切分字符串
for(int i = 0;i < strs.length; i++){
System.out.println(strs[i]);
}
传入两个参数:split(String regex,int limit)
regex正则表达式分隔符。limit 分割的份数。根据正则表达式或者字符和想要分割的份数来拆分此字符串。示例代码如下:
String oldStr = "China,Japan,美国,俄罗斯";
String[] strs = oldStr.split(",",2);//根据,切分字符串;切两份
for(int i = 0;i < strs.length; i++){
System.out.println(strs[i]);
}
//运行结果:
//China
//Japan,美国,俄罗斯
借鉴文章:List元素去重的六种方式
1、借助Set的特性进行去重
由于Set的特性是无需且不可重复,我们利用这个特性进行两部操作
(1)把list放入set中
(2)把set还回list里
public static List<String> distinct(List<String> list) {
List doubleList = new ArrayList();
if(list.size()>0&&list != null){ //判断传入的list是否为空
Set set = new HashSet(); //新建一个HashSet
set.addAll(list); //list里的所有东西放入set中 进行去重
doubleList.addAll(set); //把去重完的set重新放回list里
}
return doubleList;
}
原:22 11 33 55 66 22 33 66
去重后:22 11 33 55 66
2、利用LinkedHashSet集合去重
经过验证之后发现,LinkedHashSet虽然可以去重,但是根据他的特性,他不能对数据进行排序,只能维持原来插入时的秩序
public static List<String> delRepeat1(List<String> list) {
List<String> listNew2 = new ArrayList<String>(new LinkedHashSet<String>(list));
return listNew2;
}
原:22 11 33 55 66 22 33 66
去重后:22 11 33 55 66
3、使用list.contains()对全部元素进行判断
为了探究contains()方法的作用,我特意用List和String两种类型分别尝试了一下
结果长这样
String aaa = "aaa"; //声明一个String
String aa = "aa"; //再声明一个不同的String
boolean b = aaa.contains(aa); //比较一下 结果为true
List<String> listA = new ArrayList<String>(); //新建一个list
listA.add("aaa"); //添加一个String类型的元素
boolean b = listA.contains("aa"); //比较一下 结果为false
String类型会判断字符串里是否有相同的部分
List里则会判断是否有相同的元素
有了这样的结果,我们就可以用list.contains()的方法进行判断,然后将其添加到新的list当中,元素的顺序不发生改变
public static List<String> delRepeat(List<String> list) {
List<String> listNew = new ArrayList<String>();
for (String str : list) {
if (!listNew.contains(str)) {
listNew.add(str);
}
}
return listNew ;
}
原:22 11 33 55 66 22 33 66
去重后:22 11 33 55 66
4、使用Java8特性去重
这部分没有深究,大概意思就是把list集合->Stream流,然后对流用distinct()去重,再用collect()收集
public static List<String> delRepeat(List<String> list) {
List<String> myList = list.stream().distinct().collect(Collectors.toList());
return myList ;
}
原:22 11 33 55 66 22 33 66
去重后:22 11 33 55 66
5、使用list自身方法remove()–>不推荐
如果你的list里存的比较复杂,是一个List<Map<String,Object>>格式的情况,最无奈之举就是这种方法
主要操作是将同一个list用两层for循环配合.equals()方法,有相同的就用remove()方法剔除掉,然后得到一个没有重复数据的list
public static List<Map<String, Object>> distinct(List<Map<String, Object>> list) {
if (null != list && list.size() > 0) {
//循环list集合
for ( int i = 0 ; i < list.size() - 1 ; i ++ ) {
for ( int j = list.size() - 1 ; j > i; j -- ) {
// 这里是对象的比较,如果去重条件不一样,在这里修改即可
if (list.get(j).equals(list.get(i))) {
list.remove(j);
}
}
}
}
//得到最新移除重复元素的list
return list;
}
SSM学习笔记—AOP详解
AOP作用:
AOP(Aspect-Oriented Programming,面向切面编程)能在不改变原来代码的情况下对程序的某些功能进行增强。降低模块间的耦合度,提升代码的可读性和可维护性。在Spring中事务处理、日志管理、权限控制等场景用到了AOP原理。
Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ应该算得上是Java生态系统中最完整的AOP框架了。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。
Spring的缺点
SpringBoot的优点
1)在类的上面两个注解:
@RestController 复合注解
@RestController注解= @ResponseBody+@Controller,效果是将方法返回的对象直接在浏览器上展示成json格式。
@RequestMapping
将 HTTP 请求映射到 MVC 和 REST 控制器的处理方法上,提供路由信息,负责URL到Controller中的具体函数的映射。
2)四中REST风格的请求方式
@PostMapping (增加)
将HTTP post请求映射到特定处理程序的方法注解
@DeleteMapping (删除)
将HTTP post请求映射到特定处理程序的方法注解
@PutMapping (修改)
将HTTP post请求映射到特定处理程序的方法注解
@GetMapping (查询)
将HTTP get请求映射到特定处理程序的方法注解
3)几个请求参数注解
1)启动注解 @SpringBootApplication
@SpringBootConfiguration 注解,继承@Configuration注解,主要用于加载配置文件
@EnableAutoConfiguration 注解,开启自动配置功能
@ComponentScan 注解,主要用于组件扫描和自动装配
2)Controller 相关注解
@Controller
@RestController 复合注解
@RequestBody
@RequestMapping
@PostMapping用于将HTTP post请求映射到特定处理程序的方法注解
@DeleteMapping用于将HTTP post请求映射到特定处理程序的方法注解
@PutMapping用于将HTTP post请求映射到特定处理程序的方法注解
@GetMapping用于将HTTP get请求映射到特定处理程序的方法注解
3)取请求参数值
@PathVariable:获取url中的数据
@RequestParam:获取请求参数的值
@RequestHeader 把Request请求header部分的值绑定到方法的参数上
@CookieValue 把Request header中关于cookie的值绑定到方法的参数上
4)注入bean相关
@Service
@Controller
@Component
@Repository
@Scope作用域注解
@Entity实体类注解
@Bean产生一个bean的方法
@Autowired 自动导入
5)导入配置文件
@PropertySource注解
@ImportResource导入xml配置文件
@Import 导入额外的配置信息
6)事务注解
@Transactional(rollbackFor=Exception.class)
7)全局异常处理
@ControllerAdvice 统一处理异常
@ExceptionHandler 注解声明异常处理方法
exception
SVN
SVN:集中式版本控制器(版本控制器),基于C/S架构并且严重依赖服务器端,它将数据存放在SVN的中央仓库,当服务器端无法使用的时候,版本控制也就无法再使用了。
GIT
Git是目前世界上最先进的分布式版本控制系统(没有之一)。当这个系统的任何一个客户端出现问题的时候,都可以从另外的客户端(即使服务器挂了)获取所有的代码。
SVN与GIT的区别:
Pom、jar、war包
pom:用在父级工程或聚合工程中,用来做jar包的版本控制,必须指明这个聚合工程的打包方式为pom。
聚合工程只是用来帮助其他模块构建的工具,本身并没有实质的内容。具体每个工程代码的编写还是在生成的工程中去写。
对于在父工程中导的依赖工程也可享有。
jar:工程的默认打包方式,打包成jar用作jar包使用。存放一些其他工程都会使用的类,工具类。我们可以在其他工程的pom文件中去引用它
war:打包成war,发布在服务器上,如网站或服务。用户可以通过浏览器直接访问,或者是通过发布服务被别的工程调用
1)级联属性
联合查询:级联属性封装结果集
eg:dept.id
eg:dept.departmentName
部门表结构:
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/ >
<result column="gender" property="gender"/>
<result column="did" property="dept.id"/>
<result column="dept_name" property="dept.departmentName"/>
</resultMap>
<!-- public Employee getEmpAndDept(Integer id);-->
<select id="getEmpAndDept" resultMap="MyDifEmp">
SELECT e.id id,e.last_name last_name,e.gender gender,e.d_id d_id, d.id did,d.dept_name dept_name
FROM tbl_employee e,tbl_dept d
HERE e.d_id=d.id AND e.id=#{id}
</select>
2)association
<resultMap type="com.atguigu.mybatis.bean.Employee" id="MyDifEmp2">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="gender" property="gender"/>
<!--association可以指定联合的javaBean对象
property="dept":指定哪个属性是联合的对象
javaType:指定这个属性对象的类型[不能省略]
-->
<!--定义的association 的封装规则 下面的id是 dept的返回值主键-->
<association property="dept" javaType="com.atguigu.mybatis.bean.Department">
<id column="did" property="id"/>
<result column="dept_name" property="departmentName"/>
</association>
</resultMap>
(1)先按照员工id查询员工信息
(2)根据查询员工信息中的d_id值去部门表查出部门信息
(3)部门设置到员工中;
<!-- id last_name email gender d_id --> <resultMap type="com.atguigu.mybatis.bean.Employee" id="MyEmpByStep"> <id column="id" property="id"/> <result column="last_name" property="lastName"/> <result column="email" property="email"/> <result column="gender" property="gender"/> <!-- association定义关联对象的封装规则 select:表明当前属性是调用select指定的方法查出的结果 column:指定将哪一列的值传给这个方法 流程:使用select指定的方法(传入column指定的这列参数的值)查出对象,并封装给property指定的属性 --> <association property="dept" select="com.atguigu.mybatis.dao.DepartmentMapper.getDeptById" column="d_id"> </association> </resultMap> <!-- public Employee getEmpByIdStep(Integer id);--> <select id="getEmpByIdStep" resultMap="MyEmpByStep"> select * from tbl_employee where id=#{id} <if test="_parameter!=null"> and 1=1 </if> </select>
测试:
运行结果
反向代理、负载均衡、动静分离
可以共用一个80端口详细配置如下:
做负载均衡、反向代理时使用
方案一:多个不同端口服务共用80端口
#管理端转发 server { listen 80; server_name admin-xxxxx.xxx.xxx; location / { proxy_pass http://localhost:10003; } } #商家端转发 server { listen 80; server_name store-xxxxx.xxx.xxx; location / { proxy_pass http://localhost:10002; } }
方案二:多个服务共用80端口
// nginx.conf # nginx 80端口配置 (监听demo二级域名) server { listen 80; server_name demo.test.com; location / { root /home/www/demo; index index.html index.htm; } } # nginx 80端口配置 (监听product二级域名) server { listen 80; server_name product.test.com; location / { root /home/www/product; index index.html index.htm; } }
配置完成后保存,重启nginx服务,访问测试
# 服务器1 server { listen 9001; server_name localhost; default_type text/html; location / { return 200 '<h1>server:9001</h1>'; } } # 服务器2 server { listen 9002; server_name localhost; default_type text/html; location / { return 200 '<h1>server:9002</h1>'; } } # 服务器3 server { listen 9003; server_name localhost; default_type text/html; location / { return 200 '<h1>server:9003</h1>'; } } # 代理服务器 # 设置服务器组 upstream backend { server localhost:9001; server localhost:9002; server localhost:9003; } server { listen 8080; server_name localhost; location / { # backend 就是服务器组的名称 proxy_pass http://backend/; } }
Redis,英文全称是Remote Dictionary Server(远程字典服务),是一个开源的使用ANSIC语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
与MySQL数据库不同的是,Redis的数据是存在内存中的。它的读写速度非常快,每秒可以处理超过10万次读写操作。因此redis被广泛应用于缓存。
另外,Redis也经常用来做分布式锁。
除此之外,Redis支持事务、持久化、LUA 脚本、LRU 驱动事件、多种集群方案
Redis通过MULTI、EXEC、WATCH等一组命令集合,来实现事务机制。事务支持一次执行多个命令,一个事务中所有命令都会被序列化。在事务执行过程,会按照顺序串行化执行队列中的命令,其他客户端提交的命令请求不会插入到事务执行命令序列中。
简言之,Redis事务就是顺序性、一次性、排他性的执行一个队列中的一系列命令。
Redis执行事务的流程如下:
top
如下输出,找到这个%CPU表示cpu占用率,默认是降序,因此先定位到进程id 也就是PID
找到进程后,我们接着细致的找进程中的哪一个线程占用cpu过高
jps -l | grep 进程pid
这行命令的作用是通过jps -l
打印进程pid
对应的java
程序是哪一个,至于grep只是为了过滤
ps -mp 进程pid -o THREAD,tid,time
第四步 根据tid得到16进制的线程id
下面是为了获取16进制的线程id的命令格式
利用Linux命令,windows环境则通过计算器转换,自己想办法转换一下
printf "%x\n" 上面得到的线程tid
也可以自己通过进制转换得到对应的16机制的tid
最后通过jstack定位到具体的代码
jstack
可以打印堆栈信息,因为堆栈信息内容比较多,所以我们需要知道对应的线程id才可以快速的找到问题所在。
jstack 进程id | grep 16进制的tid -A60
这行命令就是打印指定进程的堆栈信息,然后通过 grep 对应的tid进行过滤(找到cpu占用最高的线程),-A60是打印前面60行堆栈信息。
这个时候就会发现报错的信息,就好比在写程序中控制台打印出来的异常一样,然后找到对应的类第几行代码就可以了。一般先根据项目包名找,不是自己项目中的包肯定不是你写的代码,暂时认为其它jar包的代码时没有问题的,如果有问题再回来看这些信息
MyBatis详解:SSM学习笔记-------MyBatis,里面有详细的介绍
一级缓存:(本地缓存)
sqlSession级别的缓存。一级缓存是一直开启的;SqlSession级别的一个Map
与数据库同一次会话期间查询到的数据会放在本地缓存中。
以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;
一级缓存失效情况:
1、sqlSession不同。
2、sqlSession相同,查询条件不同.(当前一级缓存中还没有这个数据)
3、sqlSession相同,两次查询之间执行了增删改操作(这次增删改可能对当前数据有影响)
4、sqlSession相同,手动清除了一级缓存(缓存清空)
#占位符的特点
MyBatis
处理 #{ }
占位符,使用的 JDBC
对象是PreparedStatement
对象,执行sql语句的效率更高。
使用PreparedStatement
对象,能够避免 sql
注入,使得sql
语句的执行更加安全。
#{ }
常常作为列值使用,位于sql语句中等号的右侧;#{ }
位置的值与数据类型是相关的。
$占位符的特点
MyBatis
处理 ${ }
占位符,使用的 JDBC
对象是 Statement
对象,执行sql语句的效率相对于 #{ }
占位符要更低。
${ }
占位符的值,使用的是字符串连接的方式,有 sql
注入的风险,同时也存在代码安全的问题。
${ }
占位符中的数据是原模原样的,不会区分数据类型。
${ }
占位符常用作表名或列名,这里推荐在能保证数据安全的情况下使用 ${ }
。
二者区别:
1.#
和$
两者含义不同
2.两者的实现方式不同
(1) $
作用相等于是字符串拼接
(2) #
作用相当于变量值替换
3、#和$使用场景不同
(1)在sql
语句中,如果要接收传递过来的变量的值的话,必须使用#
。因为使用#
是通过PreparedStement
接口来操作,可以防止sql
注入,并且在多次执行sql
语句时可以提高效率。
(2) 注意sql
注入
$
只是简单的字符串拼接而已,所以要特别小心sql
注入问题。对于sql
语句中非变量部分,那就可以使用$
,比如$
方式一般用于传入数据库对象(如传入表名)。
例如:
select * from ${tableName},
$ 对于不同的表执行统一的查询操作时,就可以使用$来完成。
(3)如果在sql语句中能同时使用#和$的时候,最好使用#。
delete | truncate | drop |
---|---|---|
删除表中数据(表结构保留 可回滚rollback) | 删除表,再以原表结构新建表。(如果之前的表有自增auto_increment 那么清空计数器) | 删表 |
DML语言(数据处理语言), 在开启事务时,delete删除的数据可以回滚 | DDL (数据定义语言)删除的数据无法回滚 | DDL (数据定义语言)删除的数据无法回滚 |
1)相同点:
2)不同点:
truncate 和 delete 只删除数据不删除表的结构(定义)
drop 语句将删除表的结构被依赖的约束(constrain)、触发器(trigger)、索引(index);依赖于该表的存储过程/函数将保留,但是变为 invalid 状态。
速度,一般来说: drop> truncate > delete
delete 语句是数据库操作语言(dml),如果开启事务,不会自动提交,能够回滚
truncate、drop 是数据库定义语言(ddl),操作立即生效,不能回滚。
delete 语句不影响表所占用的 extent,高水线(high watermark)保持原位置不动
drop 语句将表所占用的空间全部释放。
truncate 语句缺省情况下见空间释放到 minextents个 extent,除非使用reuse storage;truncate 会将高水线复位(回到最开始)。
安全性:小心使用 drop 和 truncate,尤其没有备份的时候.否则哭都来不及
使用上,想删除部分数据行用 delete,注意带上where子句. 回滚段要足够大.
想删除表,当然用 drop
想保留表而将所有数据删除,如果和事务无关,用truncate即可。如果和事务有关,用delete。
如果是整理表内部的碎片,可以用truncate跟上reuse stroage,再重新导入/插入数据。
对于由 FOREIGN KEY 约束引用的表,不能使用 truncate,而应使用不带 WHERE 子句的 delete语句。
由于 TRUNCATE TABLE 不记录在日志中,所以它不能激活触发器。
TRUNCATE TABLE 不能用于参与了索引视图的表
视图是已经编译好的sql语句。而表不是。
视图没有实际的物理记录。而表有。
表是内容,视图是窗口。
表只用物理空间而视图不占用物理空间,视图只是逻辑概念的存在,表可以及时对它进行修改,但视图只能有创建的语句来修改。
表是内模式,视图是外模式。
视图是查看数据表的一种方法,可以查询数据表中某些字段构成的数据,只是一些SQL语句的集合。从安全的角度说,视图可以不给用户接触数据表,从而不知道表结构。
表属于全局模式中的表,是实表;视图属于局部模式的表,是虚表。
视图的建立和删除只影响视图本身,不影响对应的基本表。
原子性(atomicity)
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作,这就是事务的原子性
一致性(consistency)
事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。指数据库事务不能破坏关系数据的完整性以及业务逻辑上的一致性。例如对银行转帐事务,不管事务成功还是失败,应该保证事务结束后ACCOUNTS表中Tom和Jack的存款总额为2000元。
隔离性(isolation)
事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。不同的事务并发操作相同的数据时,每个事务都有各自完成的数据空间,即一个事务内部的操作及使用的数据对其他并发事务时隔离的,并发执行的各个事务之间不能相互干扰。
持久性(durability)
一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态
查询条件中有or,即使有部分条件带索引也会失效
索引本身失效
like查询是以%开头
违背最左匹配原则
比如一个表有a,b,c 三个字段,然后建立联合索引 index(a,b,c) 注意这里索引字段的顺序
//因为建立索引树的时候,a是第一个,就好像树干一样。
//没有最左边的字段,即使后面的字段建立了索引,也无法命中。
select * from table where c = "3"; //不会走索引
select * from table where b = 2 and c = "3"; //不会走索引
如果列类型是字符串,那在查询条件中需要将数据用引号引用起来,否则不走索引
索引列上参与计算会导致索引失效
如果mysql估计全表扫描要比使用索引要快,会不适用索引
没有查询条件,或者查询条件没有建立索引
在查询条件上没有使用引导列
查询的数量是大表的大部分,应该是30%以上。
表的记录太少
因为这个会提高查询速度,但是却降低了更新表的速度
经常增删改的表
因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件数据重复且分布平均的表字段,因此应该只为经常查询和经常排序的数据列建立索引。
不要定义冗余或重复的索引
不建议用无序的值作为索引;
删除不再使用或者很少使用的索引;
数据量小的表最好不要使用索引;
有大量重复数据的列上不要建立索引;
避免对经常更新的表创建过多的索引;
在 WHERE 中使用不到的字段,不要设置索引;
学生表S(学号Sno,学生姓名Sname,性别Ssex,系别Sdept)
课程表C(课程号cno,课程名cname,学分ccredit)
选课表SC(学号sno,课程号cno,成绩grade)
# 1、查询“CS”系学生的基本信息; SELECT * FROM s WHERE Sdept="CS"; # 2、统计各系学生的人数,结果按升序排列; SELECT Sdept,COUNT(*) '人数' FROM s GROUP BY Sdept ORDER BY '人数'; # 3、查询选修了“1”或“2”号课程的学生学号和姓名; SELECT s.Sno,Sname,grade FROM s ,sc where s.`Sno`=sc.`Sno` AND Cno IN ('1','2'); # 4、查询选修了课程名为“数据库”且成绩在60分以下的学生的学号、姓名和成绩; SELECT s.Sno,Sname FROM s JOIN sc ON s.`Sno`=sc.`Sno` JOIN c ON sc.`Cno`=c.`Cno` WHERE Cname ="数据库" AND grade<60; # 5、查询选修了3门以上课程的学生学号; SELECT sno FROM sc GROUP BY sc.Sno HAVING COUNT(*)>=3; # 6、查询选修课程成绩至少有一门在80分以上的学生学号; SELECT sno FROM sc GROUP BY sc.Sno HAVING MAX(grade)>80; # 7、查询选修课程成绩均在80分以上的学生学号; SELECT sno FROM sc GROUP BY sc.Sno HAVING MIN(grade)>80; # 8、查询选修课程平均成绩在80分以上的学生学号 SELECT sno FROM sc GROUP BY sc.Sno HAVING AVG(grade)>80;
创建数据库,设置字符集
CREATE DATABASE IF NOT EXISTS test DEFAULT CHARSET utf8;
use test;
#创建学生表:包括学号,姓名,年龄,性别,院系 CREATE TABLE s ( Sno VARCHAR(7)PRIMARY KEY, Sname VARCHAR(10)NOT NULL, Sage INT, Ssex VARCHAR(2), Sdept VARCHAR(20) DEFAULT '计算机系' ); #创建课程表:包括课程号,课程名,选修课课程号,学分 CREATE TABLE c ( Cno VARCHAR(10)PRIMARY KEY, Cname VARCHAR (20)NOT NULL, Cpno VARCHAR(10), Ccredit INT ); #创建选课表 CREATE TABLE sc ( Sno VARCHAR(7), Cno VARCHAR(10), grade INT, FOREIGN KEY (sno) REFERENCES s(Sno), FOREIGN KEY (cno) REFERENCES c(cno) ); # 向学生表S中插入数据 INSERT INTO s (Sno,Sname,Sage,Ssex,Sdept) VALUE ("10001","张三",20,'男','计算机'), ("10002","李梅",19,'女','计算机'), ("10003","王五",18,'男','CS'), ("10004","小明",21,'男','计算机'), ("10006","黎明",18,'男','艺术表演'), ("10008","杰克",21,'男','计算机'), ("10005","小红",22,'女','CS'); # 向课程表C中插入数据 INSERT INTO c (Cno,Cname,Cpno,Ccredit) VALUE ("1","离散数学",NULL,5), ("2","线性代数",'3',6), ("3","高等数学",NULL,4), ("4","数据结构",'3',6), ("5","操作系统",'1',4), ("6","数据库",'4',5); # 向选课表SC中插入数据 INSERT INTO sc (Sno,Cno,grade) VALUE ("10001","1",70), ("10001","6",56), ("10003","4",90), ("10003","5",83), ("10004","1",75), ("10004","3",90), ("10008","1",70), ("10008","5",70), ("10008","6",88), ("10002","1",85), ("10002","6",89);
问题:在学生表中查询每个班级中的成绩前五名
数据准备:
DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(40) NOT NULL AUTO_INCREMENT, `name` varchar(40) DEFAULT NULL, `age` int(20) DEFAULT NULL, `class_id` int(50) DEFAULT NULL, `class_name` varchar(40) DEFAULT NULL, `grad` int(30) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=29 DEFAULT CHARSET=utf8; -- ---------------------------- -- Records of student -- ---------------------------- INSERT INTO `student` VALUES ('1', 'Tom', '19', '1', '计算机系', '80'); INSERT INTO `student` VALUES ('2', 'Jun', '20', '1', '计算机系', '90'); INSERT INTO `student` VALUES ('3', 'Tim', '23', '2', '数学系', '78'); INSERT INTO `student` VALUES ('4', 'Pom', '21', '1', '计算机系', '87'); INSERT INTO `student` VALUES ('5', 'wom', '23', '1', '计算机系', '70'); INSERT INTO `student` VALUES ('6', 'Jury', '23', '1', '计算机系', '78'); INSERT INTO `student` VALUES ('7', 'Tim', '22', '1', '计算机系', '74'); INSERT INTO `student` VALUES ('8', 'Tom', '20', '1', '计算机系', '75'); INSERT INTO `student` VALUES ('9', 'ERT', '13', '1', '计算机系', '76'); INSERT INTO `student` VALUES ('10', 'RYn', '18', '1', '计算机系', '71'); INSERT INTO `student` VALUES ('11', 'Qom', '20', '2', '数学系', '78'); INSERT INTO `student` VALUES ('12', 'Jury', '23', '2', '数学系', '98'); INSERT INTO `student` VALUES ('13', 'Jim', '22', '3', '中文系', '99'); INSERT INTO `student` VALUES ('14', 'Kom', '20', '3', '中文系', '77'); INSERT INTO `student` VALUES ('15', 'ORT', '13', '3', '中文系', '32'); INSERT INTO `student` VALUES ('16', 'TTYn', '18', '3', '中文系', '56'); INSERT INTO `student` VALUES ('17', 'SM', '18', '3', '外语系', '78'); INSERT INTO `student` VALUES ('18', 'Hid', '23', '3', '外语系', '98'); INSERT INTO `student` VALUES ('19', 'Wed', '22', '3', '外语系', '58'); INSERT INTO `student` VALUES ('20', 'Uim', '20', '3', '外语系', '65'); INSERT INTO `student` VALUES ('21', 'Dom', '13', '3', '外语系', '32'); INSERT INTO `student` VALUES ('22', 'Kfg', '18', '3', '外语系', '56'); INSERT INTO `student` VALUES ('23', 'SM', '18', '2', '数学系', '96'); INSERT INTO `student` VALUES ('24', 'Hid', '23', '2', '数学系', '95'); INSERT INTO `student` VALUES ('25', 'Wed', '22', '2', '数学系', '94'); INSERT INTO `student` VALUES ('26', 'Uim', '20', '2', '数学系', '93'); INSERT INTO `student` VALUES ('27', 'Dom', '13', '2', '数学系', '92'); INSERT INTO `student` VALUES ('28', 'Kfg', '18', '2', '数学系', '91');
查询:
select a.*
from student a
where 5 > (
select count(*)
from student b
where b.class_id = a.class_id
and b.grad > a.grad
)
order by a.class_id, a.grad DESC
效果展示:
悲观锁:
就是比较悲观的锁,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。
传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。
乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和
CAS
算法实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition
机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
Mybatis-Plus实现乐观锁
1、在表中添加version字段。
2、修改实体
3、添加乐观锁插件配置
<!--配置乐观锁插件-->
<bean id="optimi sti cLockerInnerInterceptor"
class="com. baomi dou . mybatisplus . extension. plugins. inner . optimisticlockerInnerInt
erceptor"> </bean>
4、SpringBoot实现:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//防止全表更新插件
interceptor.addInnerInterceptor(new BLockAttackInnerInterceptor());
//乐观锁插件
interceptor.addInnerInterceptor(new optimisticLocker InnerInterceptorp;
return interceptor ;
}
}
存储过程:是事先经过编译并存储在数据库中的一段SQL语句的集合,就是数据库 SQL 语言层面的代码封装与重用。
存储过程参数:
IN
类型的参数表示接受调用者传入的数据;OUT
类型的参数表示向调用者返回数据;INOUT
类型的参数即可以接受调用者传入的参数,也可以向调用者返回数据。优点
存储过程是通过处理封装在容易使用的单元中,简化了复杂的操作。
简化对变动的管理。如果表名、列名、或业务逻辑有了变化。只需要更改存储过程的代码。使用它的人不用更改自己的代码。
通常存储过程是有助于提高应用程序的性能。当创建的存储过程被编译之后,就存储在数据库中。
但是,MySQL实现的存储过程略有所不同。
MySQL存储过程是按需编译。在编译存储过程之后,MySQL将其放入缓存中。
MySQL为每个连接维护自己的存储过程高速缓存。如果应用程序在单个连接中多次使用存储过程,则使用编译版本,否则存储过程的工作方式类似于查询。
存储过程有助于减少应用程序和数据库服务器之间的流量。
因为应运程序不必发送多个冗长的SQL语句,只用发送存储过程中的名称和参数即可。
存储过程度任何应用程序都是可重用的和透明的。存储过程将数据库接口暴露给所有的应用程序,以方便开发人员不必开发存储过程中已支持的功能。
存储的程序是安全的。数据库管理员是可以向访问数据库中存储过程的应用程序授予适当的权限,而不是向基础数据库表提供任何权限。
缺点
内存占用量大,APU利用率增加
如果使用大量的存储过程,那么使用这些存储过程的每个连接的内存使用量将大大增加。
此外,如果在存储过程中过度使用大量的逻辑操作,那么CPU的使用率也在增加,因为MySQL数据库最初的设计就侧重于高效的查询,而不是逻辑运算。
存储过程的构造使得开发具有了复杂的业务逻辑的存储过程变得困难。
很难调试存储过程。只有少数数据库管理系统允许调试存储过程,MySQL不提供调试存储过程的功能。
开发和维护存储过程都不容易。
开发和维护存储过程通常需要一个不是所有应用程序开发人员拥有的专业技能。这可能导致应用程序开发和维护阶段的问题。
对数据库依赖程度较高,移值性差。
定义一个有参数的存储过程:
先定义一个student数据库表:
现在要查询这个student
表中的sex
为男的有多少个人。
DELIMITER $$
CREATE
PROCEDURE `demo`.`demo2`(IN s_sex CHAR(1),OUT s_count INT)
-- 存储过程体
BEGIN
-- 把SQL中查询的结果通过INTO赋给变量
SELECT COUNT(*) INTO s_count FROM student WHERE sex= s_sex;
SELECT s_count;
END$$
DELIMITER ;
调用这个存储过程
-- @s_count表示测试出输出的参数
CALL demo2 ('男',@s_count);
详细请看:MySQL中的存储过程(详细篇)
锁:
悲观锁:认为在修改数据库数据的这段时间里存在着也想修改此数据的事务;
乐观锁:认为在短暂的时间里不会有事务来修改此数据库的数据;
我们一般意义上讲的锁其实是指悲观锁,在数据处理过程中,将数据置于锁定状态(由数据库实现)。
事务
事务就是并发控制的单位,是用户定义的一个操作序列,满足ACID
属性(原子性、一致性、隔离性和持久性)。
锁是用于解决隔离性的一种机制。事务的隔离级别通过锁的机制来实现。另外锁有不同的粒度,同时事务的隔离级别一般有四种:
Read uncommitted
,Read committed
,Repeatable read
,Serializable
。重点:一般事务使用的是悲观锁(具有排他性)。
常用的是MyISAM
和InnoDB
。
InnoDB
:支持事务、支持外键、支持行级锁、不支持全文索引、
MyISAM
:不支持事务、不支持外键、不支持行级锁、支持全文索引
可以打开系统数据表 performance_schema
的相关表:
USE performance_schema;
然后,输入以下命令可以查看所有执行过的语句的历史执行时间:
SELECT * FROM events_statements_history;
如果只想查看最近一段时间的执行时间,可以输入以下命令:
SELECT * FROM events_statements_history
WHERE event_time >NOW() - INTERVAL 1 DAY;
select
查询列表 ⑦
from
表1 别名 ①
连接类型 join
表2 ②
on
连接条件 ③
where
筛选 ④
group by
分组列表 ⑤
having
分组后的筛选 ⑥
order by
排序列表 ⑧
limit
起始条目索引,条目数; ⑨
对象关系映射(Object Relational Mapping,简称ORM
)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM
框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM
框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。
ORM
框架:为了解决面型对象与关系数据库存在的互不匹配的现象的框架。
Hibernate
全自动 需要写hql
语句iBATIS
半自动 自己写sql
语句,可操作性强,小巧mybatis
eclipseLink
JFinal
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。