
【zxiaofan.com】Life is all about choices!生命不息,学习不止。
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
今天用反射给对象赋值,有一个属性始终报错,主要错误信息如下: java.lang.NoSuchFieldException: otherFeatures at java.lang.Class.getDeclaredField(Unknown Source) at com.zxiaofan.MedicalAssistanObtainService.business.CrawlDataBusiness.setSpecialAttribute(CrawlDataBusiness.java:163) at com.zxiaofan.MedicalAssistanObtainService.business.CrawlDataBusiness.crawDrmedData(CrawlDataBusiness.java:106) at com.zxiaofan.MedicalAssistanObtainService.business.CrawlDataBusinessTest.test(CrawlDataBusinessTest.java:33) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) 网上说NoSuchFieldException错误是由于没有对应字段造成的,或者当属性为私有时获取Field用的方法不是getDeclaredField。 Field field = bo.getClass().getDeclaredField(fieldName); // 正解 但是我的对象内确实是有otherFeatures属性的,而且用的是上述方法,查了好久,终于找到原因: otherFeatures曾经从文本读取过,而文本里的otherFeatures多了空格,如果仔细查看,上面的异常也多了空格的!!! So:NoSuchFieldException异常原因:①没有对应字段;②属性为私有时获取Field用的方法不是getDeclaredField。 死死往这两个方面去找原因吧。
zxiaofan.cn域名备案中,所有zxiaofan.cn相关服务暂停解析(未备案域名不能使用,这是规定,我也没法),为了日后能正常访问,只有暂时忍痛了。备案期间,所有服务均可通过zxiaofan.com进行访问。 ---------------------------------------------------- zxiaofan.cn 备案成功,现已能正常访问 2016-04-11 ----------------------------------------------------
Thrift是什么,有什么优势?这里就不阐述了,百度即可。本文旨在于展现Thrift服务搭建和调用的过程,让初学者少走弯路。本文提供完整代码及所需jar和thrift-0.9.3.exe。点击此处下载完整工程:https://github.com/zxiaofan/OpenSource_Study/tree/master/Thrift 先来张目录结构图 1、下载所需jar和thrift-0.9.3.exe 下载地址:thrift-0.9.3.exe、Thrift开发所需jar。 2、解压thrift-0.9.3.exe到本地目录(如:E:\Thrift ),建议全英文目录,并修改名字为thrift.exe(方便敲命令)。 3、添加thrift.exe目录到系统环境变量 在系统变量path后添加“ ;E:\Thrift ”,注意分号哦。 4、下载ThriftTest.thrift 到thrift.exe目录。点击下载 这里提供了现成的Thrift文件,相关的编写方法,网络很多,就不赘述。亦可参照我收集的(点击访问我的个人有道云笔记)。 5、CMD定位到E:\Thrift ,执行命令 cd /d E:\Thrift 即可。 6、生成java文件 接着在命令行输入: thrift.exe -r -genjava ./ThriftTest.thrift ,此时会在thrift.exe目录生成gen-java文件夹,里面的就是我们所需的ThriftHelloWorld.java文件了。 7、接下来就是编写服务端了。 ①首先实现我们的接口,注意要实现Iface接口哦。 package service; import thrift.ThriftHelloWorld.Iface; /** * 服务端实现类 * */ public classHelloServiceImpl implements Iface { public String sayHello(String username) { return "hello " + username; } public String getRandom() { return "random"; } } ②在src目录下新建thrift包,将先前生成的ThriftHelloWorld.java拷贝进去; ③新建startServer用于启动服务, package service; import org.apache.thrift.protocol.TBinaryProtocol.Factory; import org.apache.thrift.server.TServer; import org.apache.thrift.server.TThreadPoolServer; import org.apache.thrift.server.TThreadPoolServer.Args; import org.apache.thrift.transport.TServerSocket; import org.apache.thrift.transport.TTransportException; import thrift.ThriftHelloWorld; import thrift.ThriftHelloWorld.Processor; /** * 启动服务 * */ public classStartServer { /** * 启动Thrift服务器 */ public void startServer() { try { // 定义传输的socket,设置服务端口为6789 TServerSocket serverTransport = newTServerSocket(6789); // 设置协议工厂为 TBinaryProtocol.Factory Factory proFactory = newFactory(true, true); // 关联处理器与 Hello服务的实现 ThriftHelloWorld.Processorprocessor = new Processor(new HelloServiceImpl()); // 定义服务端的参数值 Args args = newArgs(serverTransport); args.processor(processor); args.protocolFactory(proFactory); TServer server = newTThreadPoolServer(args); // 服务端开启服务s server.serve(); } catch (TTransportException e) { e.printStackTrace(); } } public static void main(String[] args) { System.out.println("ServerStart!"); StartServer server = new StartServer(); server.startServer(); } } 服务端就此完成,启动main函数,控制台输出Server Start!。 发布的时候只需将此工程导出为可执行jar或将工程稍作修改用tomcat发布。 8、编写客户端,完成服务调用 ①同样在src目录下新建thrift包,将先前生成的ThriftHelloWorld.java拷贝进去; ②创建ClientTest类用于调用服务 package client; import org.apache.thrift.TException; import org.apache.thrift.protocol.TBinaryProtocol; import org.apache.thrift.protocol.TProtocol; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import org.junit.Test; import thrift.ThriftHelloWorld; /** * 客户端实现 * */ public classClientTest { /** * 调用Hello服务 */ @Test public void startClient() { try { // 设置调用的服务地址为本地,端口为6789 TTransport transport = newTSocket("localhost", 6789); transport.open(); // 数据传输协议有:二进制协议、压缩协议、JSON格式协议 // 这里使用的是二进制协议 // 协议要和服务端一致 TProtocol protocol = new TBinaryProtocol(transport); ThriftHelloWorld.Client client =new ThriftHelloWorld.Client(protocol); // 调用服务器端的服务方法 System.out.println(client.sayHello("zxiaofan")); // 关闭 transport.close(); } catch (TTransportException e) { e.printStackTrace(); } catch (TException e) { e.printStackTrace(); } } } 启动startClient,控制台输出hello zxiaofan,至此客户端完成。
1、String常量池 当使用new String(“hello”)时,JVM会先使用常量池来管理“hello”直接量,再调用String类的构造器来创建一个新的String对象,新创建的对象被保存在堆内存中。即new String(“hello”)一共产生了两个字符串对象。 【常量池constant pool】管理在编译时被确定并保存在已编译的.class文件中的一些数据,包括关于类、方法、接口中的常量,和字符串常量。 【字符串常量池(String pool, String intern pool, String保留池)】 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 理解了下面这段代码就把常量池理解的七七八八了吧。 public void test(){ String a ="张三"; String b ="张"; String c ="三"; String d = b + c; System.out.println(a == d);// false String e ="张"+"三"; System.out.println(a == e);// true } d=b+c:先执行StringBuilder的拼接,相当于new了一下,虽然值相等,但内存地址已变。 当Java能直接使用字符串直接量(包括在编译时就计算出来的字符串值时,如String e = "张" + "三";),JVM就会使用常量池来管理这些字符串。 2、String为什么是不可变的? 注:【以下内容来于网络,并结合自己的理解】 答案一: 最流行的Java面试题之一就是:什么是不可变对象(immutable object),不可变对象有什么好处,在什么情况下应该用,或者更具体一些,Java的String类为什么要设成immutable类型? 不可变对象,顾名思义就是创建后不可以改变的对象,典型的例子就是Java中的String类。 String s = "ABC"; s.toLowerCase(); 如上s.toLowerCase()并没有改变“ABC“的值,而是创建了一个新的String类“abc”,然后将新的实例的指向变量s。 相对于可变对象,不可变对象有很多优势: 1).不可变对象可以提高String Pool的效率和安全性。如果你知道一个对象是不可变的,那么需要拷贝这个对象的内容时,就不用复制它的本身而只是复制它的地址,复制地址(通常一个指针的大小)需要很小的内存效率也很高。对于同时引用这个“ABC”的其他变量也不会造成影响。 2).不可变对象对于多线程是安全的,因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况。 当然也有其他方面原因,但是Java把String设成immutable最大的原因应该是效率和安全。 答案二: 这是一个老生常谈的话题(This is an old yet still popular question). 在Java中将String设计成不可变的是综合考虑到各种因素的结果,想要理解这个问题,需要综合内存,同步,数据结构以及安全等方面的考虑. 在下文中,我将为各种原因做一个小结。 1. 字符串常量池的需要 字符串常量池(String pool, String intern pool, String保留池) 是Java堆内存中一个特殊的存储区域, 当创建一个String对象时,假如此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 如下面的代码所示,将会在堆内存中只创建一个实际String对象. String s1 = "abcd"; String s2 = "abcd"; 示意图如下所示: 假若字符串对象允许改变,那么将会导致各种逻辑错误,比如改变一个对象会影响到另一个独立对象. 严格来说,这种常量池的思想,是一种优化手段. 请思考: 假若代码如下所示,s1和s2还会指向同一个实际的String对象吗? 代码如下: public class Test { public static void main(String[] args) { String s1 = "ab" + "cd"; String s2 = "abc" + "d"; System.out.println("s1==s2:" + s1 == s2); } } // output:false (注意和下面代码对比) 也许这个问题违反新手的直觉, 但是考虑到现代编译器会进行常规的优化, 所以他们都会指向常量池中的同一个对象. 或者,你可以用 jd-gui 之类的工具查看一下编译后的class文件.(自带javap -c 命令) 编译后文件如下: public class Test { public Test(); Code: 0: aload_0 1: invokespecial #8 //Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #16 //String abcd 2: astore_1 3: ldc #16 //String abcd 5: astore_2 6: getstatic #18 //Field java/lang/System.out:Ljava/io/PrintStream; 9: new #24 //class java/lang/StringBuilder 12: dup 13: ldc #26 //String s1==s2: 15: invokespecial #28 //Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V 18: aload_1 19: invokevirtual #31 //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 22: invokevirtual #35 //Method java/lang/StringBuilder.toString:()Ljava/lang/String; 25: aload_2 26: if_acmpne 33 29: iconst_1 30: goto 34 33: iconst_0 34: invokevirtual #39 //Method java/io/PrintStream.println:(Z)V 37: return } public class Test { public Test(); Code: 0: aload_0 1: invokespecial #8 //Method java/lang/ // Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #16 // String abcd 2: astore_1 3: ldc #16 // String abcd 5: astore_2 6: getstatic #18 // Field java/lang/ // System.out:Ljava/io/PrintStream; 9: aload_1 10: aload_2 11: if_acmpne 18 14: iconst_1 15: goto 19 18: iconst_0 19: invokevirtual #24 //Method java/io/ // PrintStream.println:(Z)V 22: return } // 字节码指令2 两个“abcd”都是#16,多定义了一个S0=“123”后发现两个“abcd”都是#30【这就是代表指向同一个对象?】 接下来高潮来了,上面的代码稍稍改一下: public class Test { public static void main(String[] args) { String s1 = "ab" + "cd"; String s2 = "abc" + "d"; System.out.println(s1 == s2); } } // output:true // 代码仅仅少了一个字符串而已 编译一下:见【字节码指令2】左边有底色的是前者比后者的多的字节码指令部分。 两者有什么区别呢? 最初还以为是在底层实现有区别,后经导师指点,仅仅是运算符优先级【+优先级高于==,详见笔记《运算符优先级表》】的区别,前者是先计算+号,执行拼接,再和后面的s2比较,肯定是false啊。当然可以把后面加个括号,输出就变为true了,而且是 "s1==s2:"true 。 理解了这个,再稍微改动一下; String s1 = "ab"; String s2 = "abc" + "d"; System.out.println(s1 + "cd" == s2); 输出为:false 【只有false,没有前面"s1==s2:"这一串】 // 先执行StringBuilder的拼接,相当于new了一下,虽然值相等,但内存地址已变。 但如果这样写: String s1 = "1234"; String s01 = "123"; String s02 = "4"; String s2 = s01 + s02; System.out.println(s1 == s2); false // s2取了s01、s02的引用地址。肯定和s1不同了额。 引: hashCode()返回的是JVM中地址的哈希码,而不是JVM中的地址,要想得到str在物理内存中的真实地存,那只有用JNI技术调用c/c++去实现,否则无能为力,因为java超不出JVM,而JVM对物理内存地址是“不可见”的,否则java中不就有了指针,而去直接操作内存了,当然这是与java语言相违背的。这些只是我个人见解,说不定还真有高手直接用java语言得到了物理内存中的地址了呢。s1.getBytes()也不行。 2. 允许String对象缓存HashCode Java中String对象的哈希码被频繁地使用, 比如在hashMap 等容器中。 字符串不变性保证了hash码的唯一性,因此可以放心地进行缓存.这也是一种性能优化手段,意味着不必每次都去计算新的哈希码. 在String类的定义中有如下代码: private int hash;//用来缓存HashCode 3. 安全性 String被许多的Java类(库)用来当做参数,例如 网络连接地址URL,文件路径path,还有反射机制所需要的String参数等, 假若String不是固定不变的,将会引起各种安全隐患。 假如有如下的代码: 代码如下: boolean connect(string s){ if (!isSecure(s)) { throw new SecurityException(); } // 如果在其他地方可以修改String,那么此处就会引起各种预料不到的问题/错误 causeProblem(s); } 总体来说, String不可变的原因包括 设计考虑,效率优化问题,以及安全性这三大方面. 事实上,这也是Java面试中的许多 "为什么" 的答案。 答案三:String类不可变性的好处 String是所有语言中最常用的一个类。我们知道在Java中,String是不可变的、final的。Java在运行时也保存了一个字符串池(String pool),这使得String成为了一个特别的类。 String类不可变性的好处 1.只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap(堆)空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么String interning将不能实现(译者注:String interning(拘留)是指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串。),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。 2.如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。 3.因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的。 4.类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。 5.因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。 以上就是我总结的字符串不可变性的好处。 什么是不可变对象? String对象是不可变的,但这仅意味着你无法通过调用它的公有方法来改变它的值。 众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。 区分对象和对象的引用 对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码: String s = "ABCabc"; System.out.println("s = "+ s); s = "123456"; System.out.println("s = "+ s); 打印结果为: s = ABCabc s = 123456 首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。 也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个新的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。 Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。 为什么String对象是不可变的? 要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个: public final class String implementsjava.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** The offset is the first index of the storage that is used. */ private final int offset; /** The count is the number of characters in the String. */ private final int count; /** Cache the hash code for the string */ private int hash; // Default to 0 在JDK1.8中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.8中String类的主要成员变量就剩下了两个: public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final charvalue[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** * Class String is special cased within the Serialization Stream Protocol. * * A String instance is written into an ObjectOutputStream according to * <a href="{@docRoot}/../platform/serialization/spec/output.html"> * Object Serialization Specification, Section 6.2, "Stream Elements"</a> */ // 声明序列化时要包含的域,仅仅声明String中未使用 private static final ObjectStreamField[] serialPersistentFields = new ObjectStreamField[0]; } 由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的: value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。 那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码: String a = "ABCabc"; System.out.println("a = "+ a); a = a.replace('A', 'a'); System.out.println("a = "+ a); 打印结果为: a = ABCabc a = aBCabc 那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace(‘A', ‘a')时, 方法内部创建了一个新的String对象,并把这个心的对象重新赋给了引用a。String中replace方法的源码可以说明问题: public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; } 读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值: String ss = "123456"; System.out.println("ss = "+ ss); ss.replace('1', '0'); System.out.println("ss = "+ ss); 打印结果: ss = 123456 ss = 123456 String对象真的不可变吗? 从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。 那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而通过改变获得的value引用改变数组的结构。下面是实例代码: import java.lang.reflect.Field; public class Reflection { public static void main(String[] args) throws Exception { // 创建字符串"Hello World",并赋给引用s String s = "Hello World"; System.out.println("原 s= " + s + " ,hash:" + s.hashCode()); // 原s= Hello World 地址:-862545276 commonChange(s); System.out.println("======反射修改======"); // 获取String类中的value字段 Field valueField = String.class.getDeclaredField("value"); // 改变value属性的访问权限 valueField.setAccessible(true); // 获取s对象上的value属性的值 char[] value = (char[]) valueField.get(s); // 改变value所引用的数组中的第5个字符 // value[5] = '_'; valueField.set(s, newchar[]{'h', 'a', 'p', 'p', 'y'}); System.out.println("反射s= " + s + " ,hash:" + s.hashCode()); // 新s= happy 地址:-862545276 } private static void commonChange(String s) { System.out.println("======普通修改======"); s = "Hello--World"; System.out.println("改变s= " + s + " ,hash:" + s.hashCode()); // 改变s= Hello--World ,hash:753841376 String newStr = new String("Hello--World"); System.out.println("新 s= " + newStr + " ,hash:" + newStr.hashCode()); // 新 s= Hello--World ,hash:753841376【内存中已存在】 System.out.println(s == newStr); // 依旧 false 【虽然hash相同】 } } 在这个过程中,s始终引用的同一个String对象,但是在反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。 参考:http://www.jb51.net/article/73243.htm
自Java8以来,HashMap是高效的。应用HashMap完成枚举类型到值的映射也是我们常用的方式,但是EnumMap将更加高效。EnumMap顾名思义,是为枚举类服务的。 key必须为枚举类(Enum),且创建EnumMap时必须指定key的类型。 key不能为null,NullPointerException,但value允许null。 底层结构均为数组,大小为Enum成员数量,创建EnumMap时会缓存所有枚举达到key数组。 元素顺序为Enum的顺序,与put顺序无关。 与HashMap类似,不保证线程安全。 注意: 迭代时不会抛出ConcurrentModificationException。 NULL和null的区别。 1、重要属性 private final Class<K> keyType; // EnumMap的key的类型,在keyType中的enumConstants存放key的所有枚举值。 private transient K[] keyUniverse; //存放key的所有枚举值 private transient Object[] vals; // EnumMap的value值 private transient int size =0; 注意:key和value的底层机构均为数组。 常用方法: private Object maskNull(Object value) { return (value == null ? NULL : value); //null变NULL,避免内部空指针异常} private V unmaskNull(Object value) { return (V)(value == NULL ? null : value);} private static final Object NULL = new Object() { public int hashCode() { return 0; } public String toString() { return "java.util.EnumMap.NULL"; }}; 2、put public V put(K key, V value) { typeCheck(key); // key必须是keyType类型或keyType子类型,否则ClassCastException异常 int index = key.ordinal(); // 以key的ordinal作为数组下标 Object oldValue = vals[index]; vals[index] = maskNull(value); //maskNull: value为空则返回自定义的空对象NULL if (oldValue == null) size++; return unmaskNull(oldValue); //返回oldValue或null } 3、get public V get(Object key) { return (isValidKey(key) ? unmaskNull(vals[((Enum<?>)key).ordinal()]) : null);} private boolean isValidKey(Object key) { // 检验key的有效性(不为null且类型合法) if (key == null) return false;// Cheaper than instanceof Enum followed by getDeclaringClass Class<?> keyClass = key.getClass(); return keyClass == keyType || keyClass.getSuperclass() == keyType;} get方法先检验key的有效性,有效则以ordinal为下标返回vals[ordinal],否则返回null,So,和HashMap一样,不可以用get来判断EnumMap是否包含某一元素,因为某元素的value可能本就为null。 4、contains相关 public boolean containsKey(Object key) { return isValidKey(key) && vals[((Enum<?>)key).ordinal()] != null;} 注意containsKey和get的区别(仅仅未调用unmaskNull方法),以及EnumMap自定义的NULL和null的区别。 public boolean containsValue(Object value) { value = maskNull(value); //null则返回NULL,否则返回value本身 for (Object val : vals) // 遍历vals if (value.equals(val)) return true; // 只要有一个相等即返回true return false;} private boolean containsMapping(Object key, Object value) { return isValidKey(key) && maskNull(value).equals(vals[((Enum<?>)key).ordinal()]);} // containsMapping方法是私有的,仅供EntrySet的contains方法调用。 5、遍历相关 // 遍历KeySet Set keySet = enumMap.keySet(); terator iteKey = keySet.iterator(); while(iteKey.hasNext()){ Object object =(Object) iteKey.next(); System.out.print(object +"="+ enumMap.get(object)+"; "); } ------------------ // 遍历values Collection<Object> vals = enumMap.values(); Iterator iteVal = vals.iterator(); while(iteVal.hasNext()){ Object object =(Object) iteVal.next(); System.out.print(object +"; "); // ((Iterator) object).remove();// ClassCastException: String cannot be cast to Iterator } -------------------- // 遍历Entry Set<Entry<Season, Object>> entrySet = enumMap.entrySet(); Iterator iteEn = entrySet.iterator(); while(iteEn.hasNext()){ Entry object =(Entry) iteEn.next(); System.out.print(object.getKey()+"; "); iteEn.remove(); } 注意:和HashMap不同的是,EnumMap迭代不会抛ConcurrentModificationException异常。 for (Object entry : enumMap.entrySet()) { enumMap.remove(Season.summer); // 【--不抛--ConcurrentModificationException异常】 } EnumMap中没有mouCount,相应Iterator中也没有expectedModCount,也就是说没有fast-fail机制。 相关分析参阅另一篇云笔记《HashMap迭代时不抛出ConcurrentModificationException的特例》。 6、其他方法 public voidclear(){ Arrays.fill(vals,null); size =0; } public static void fill(Object[] a, Object val){ for(int i =0, len = a.length; i < len; i++) a[i]= val; // 逐个遍历,好在vals[]元素也不会有太多 } public V remove(Object key){ if(!isValidKey(key)) return null; int index =((Enum<?>)key).ordinal(); Object oldValue = vals[index]; vals[index]=null; // 直接置null if(oldValue !=null) size--; return unmaskNull(oldValue); // 返回oldValue(包含null) } 相关资料: EnumMap介绍 EnumMap源代码阅读器
众所周知,HashMap在迭代时remove会抛出异常,ConcurrentModificationException,但事实真的是这样的吗?的确会抛异常,但也有特例。废话少说,上代码: public class ConcurrentModificationException { public static void main(String[] args) { HashMap<Integer, Integer> map = new HashMap<>(); map.put(1, 2); for (Map.Entry<Integer, Integer> entry : map.entrySet()) { map.remove(1); } System.out.println(map); } }运行以上代码,我们将会发现,程序并没有报ConcurrentModificationException异常,而且输出:{}。 这与常识不服啊?想要弄懂其中缘由,得理解程序何时会抛ConcurrentModificationException异常。通过HashMap的源码可知,当modCount != expectedModCount时,此异常将会被抛出。 if (modCount != expectedModCount) throw new ConcurrentModificationException(); 而上面的代码在执行remove()后,接着遍历map.entrySet(),执行到 public final boolean hasNext() { return next != null; } 但会false,并没有检测modCount != expectedModCount,也就不会抛异常了。 但是,如果我们在上面的代码中再加一行添加键值对的语句:map.put(3, 4);也就是说map中有两个键值对,这时你会发现程序将抛出ConcurrentModificationException异常,这又是为什么呢?还是来看源码就知道了。 执行remove之后, 接着遍历map.entrySet(),执行到 public final boolean hasNext() { return next != null; } 返回true,接着执行 final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } } 跳转到nextNode()中,执行 final Node<K,V> nextNode() { Node<K,V>[] t; Node<K,V> e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } 看到了吧,上面的 if (modCount != expectedModCount) throw new ConcurrentModificationException();异常就在这里抛出了。 如果进一步深究,你会发现一个更有趣的事,如果你迭代的代码是这样的,Java8的Lambda表达式 map.forEach((key, value) -> { System.out.println(key + "==" + value); map.remove(1); // ConcurrentModificationException });你会发现,即使你的map中只有一个键值对,程序也将抛出异常,这与foeEach和lambda迭代的内部原理有关了,同样,调试源码将找到答案。 在执行remove后,程序将进入 @Override public void forEach(BiConsumer<? super K, ? super V> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (int i = 0; i < tab.length; ++i) { for (Node<K,V> e = tab[i]; e != null; e = e.next) action.accept(e.key, e.value); } if (modCount != mc) throw new ConcurrentModificationException(); } } 在这里if (modCount != mc)将抛出异常。 总结: HashMap迭代时Remove掉map中包含的键值对,从而改变结构(modCount加1),接下来程序若再有检测modCount的fast-fail机制,程序便将抛出ConcurrentModificationException异常。(注:如果上面程序,remove(6) 将不会引发HashMap结构性改变,也就不会抛异常了) 如果你还了解HashMap的更多源码分析,请移步HashMap源码分析(jdk1.8)。
核心: long(long数组) 和 位运算, 其存储结构elements并未直接存枚举本身,而是位标识,枚举存于elementType中的enumConstants中. 1、内部元素为枚举; 2、内部表示为位向量,使用“位标志”的替换形式。(类似0x1000000,按bit存储,用位运算进行相关操作); 3、全部是静态方法static; 4、根据传入的枚举类型判断组成长度,小于等于64则返回RegularEnumSet,否则JumboEnumSet。 /** * Creates an empty enum set with the specified element type. */public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { Enum<?>[] universe = getUniverse(elementType);if (universe == null) throw new ClassCastException(elementType + " not an enum");if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe);else return new JumboEnumSet<>(elementType, universe);} ------------------------------------------------------------------------------ RegularEnumSet 5、add源码 /** * Adds the specified element to this set if it is not already present. * @throws NullPointerException if <tt>e</tt> is null */public boolean add(E e) { typeCheck(e); // long oldElements = elements; elements |= (1L << ((Enum<?>)e).ordinal()); return elements != oldElements;} 每个枚举值都对应着自己的ordinal,第一个枚举值的ordinal为0,第二个为1,以此类推。 看看RegularEnumSet中elements的定义吧。关键点:位向量、2^k bit则说明存在该元素(每位都为1)。 /** * Bit vector representation of this set. The 2^k bit indicates the * presence of universe[k] in this set. */private long elements = 0L; 1、取枚举值的ordinal,初始为0; 2、1 << ordinal ; 3、与elements做 |= 运算。 例: a.添加ordinal为0的枚举值,则计算后elements为1 b.添加ordinal为1的枚举值,则计算后elements为( 01|10 ) = 11(B) c.添加ordinal为2的枚举值,则计算后elements为(011 | 100) = 111 So,EnumSet就是用long型来存储枚举,支持64位(long64位)。 而ordinal是个什么鬼呢? 是Enum抽象类的一个属性,源码如下: private final int ordinal;/** * Returns the ordinal of this enumeration constant (its position * in its enum declaration, where the initial constant is assigned * an ordinal of zero). */public final int ordinal() { return ordinal; } 注意看:这里被调用的是ordinal()方法,以便于后续版本更新时,保持良好的兼容性(即使计算方式改变了,也不会影响原来的工程)。 6、contains 源码 public boolean contains(Object e) { if (e == null) return false; Class<?> eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) return false; return (elements & (1L << ((Enum<?>)e).ordinal())) != 0;} 1、1L << ((Enum)e).ordinal() ; 2、与elements做&运算。 例: ordinal为2,则elements为111(B); 1L << ((Enum<?>)e).ordinal() 值为 100(B); elements & (1L << ((Enum<?>)e).ordinal()) = 111(B) 111不等于0,说明包含该元素。 利用 long 和 位运算 实现EnumSet的快速存储和判断。 7、size源码 public int size() { return Long.bitCount(elements);} bitCount返回的是参数二进制码中1的个数。如110(B)返回2。 源码如下,太过高深,在此就不剖析了。 public static int bitCount(long i) { i -= i >>> 1 & 6148914691236517205L; i = (i & 3689348814741910323L) + (i >>> 2 & 3689348814741910323L); i = i + (i >>> 4) & 1085102592571150095L; i += i >>> 8; i += i >>> 16; i += i >>> 32; return (int)i & 127; } JumboEnumSet /** * Bit vector representation of this set. The ith bit of the jth * element of this array represents the presence of universe[64*j +i] * in this set. */private long elements[]; 由RegularEnumSet中的long型转为long型数组。 public boolean add(E e) { typeCheck(e); int eOrdinal = e.ordinal(); int eWordNum = eOrdinal >>> 6; // 无符号右移,高位补0,去掉低位 // eWordNum 表示 eOrdinal 的高位 // 左移乘2,右移除2,移位位数对32取模( i>>33 等价于 i>>1) long oldElements = elements[eWordNum]; elements[eWordNum] |= (1L << eOrdinal); // 核心,Enum前64个元素全部放在elements[0]中 // elements[0]的值的二进制 第i位为1,则表示Enum的第i个元素存在于该集合, // 引申[64*j +i],elements[j]的第i个元素为1。 boolean result = (elements[eWordNum] != oldElements); if (result) size++; return result;} public boolean contains(Object e) { if (e == null) return false; Class<?> eClass = e.getClass(); if (eClass != elementType && eClass.getSuperclass() != elementType) return false; int eOrdinal = ((Enum<?>)e).ordinal(); return (elements[eOrdinal >>> 6] & (1L << eOrdinal)) != 0;} NOte:正数一直无符号右移,将变为0。 例: "eOrdinal" 65 "1L << eOrdinal" 2 "eOrdinal >>> 6" 1 // 待查元素的高位 "elements[eOrdinal >>> 6]" 35 // elements第j号位所有元素 "elements[eOrdinal >>> 6] & (1L << eOrdinal)" 2 // Redundant - maintained for performance private int size = 0; public int size() { return size;}
1、Spring容器成功启动的条件 Spring框架的类包都已放在应用程序的类路径; 应用程序为Spring提供完备的Bean信息; Bean的类都已放到应用程序的类路径下。 2、Bean配置信息是Bean的元素据信息,由4个方面组成: Bean的实现类; Bean的属性信息(如数据源的连接数、用户名、密码); bean的依赖关系,Spring根据依赖关系配置完成Bean间的装配; Bean的行为配置(如生命周期范围及生命周期各过程的回调函数)。 2.3.1 Bean基本配置 Bean命名 id为Bean的名称,通过容器的getBean("foo")获取对应的Bean,在容器中起到定位查找的作用,是外部程序和Spring IoC容器进行交互的桥梁,class指定了Bean对应的实现类。 id(XML规定的特殊属性)在IoC容器中必须唯一,还需满足XML对id的命名规范:字母开始,后接字母、数字、连字符、下划线、句号、冒号等完整结束符。逗号、空格这些非完整结束符是非法的。 name属性几乎可用任何字符, <bean name="#car" class="com.Car"/> id和name都可指定多个名字,名字间用逗号、分号或空格分隔。用户可用getBean("123")或另外两种名字获取IoC容器中的CarBean。 <bean name="#car,123,$Car" class="com.Car"/> Spring配置文件不允许两个相同id的<bean>;但允许相同name的<bean>,getBean时会返回最后声明的那个Bean,后面的Bean覆盖了前面同名的Bean。So,尽量使用id而不是name命名的Bean。 如果id、name都未指定,如①<bean class="com.Car"/>,Spring自动将全限定类名作为Bean的名称,可通过②getBean("com.Car")获取CarBean,如果存在多个实现类相同的匿名<bean>即多个①处的Bean,第一个Bean通过②获得,第二个Bean通过getBean("com.Car#1")获得,以此类推。 2.3.2 依赖注入 1)属性注入 setXxx()方法,可选择性、灵活性高,最常用。 要求Bean提供默认的构造函数,并未需要注入的属性提供对应的Setter方法。Spring先调用Bean的默认 构造函数实例化Bean对象,然后通过反射调用Setter方法注入属性值。 【默认构造函数】 默认构造函数不带参。Java规定如果类中没有定义任何构造函数,JVM自动生成一个默认构造函数。反之,如果类中显示定义了构造函数,JVM将不会自动生成。So,类中显示定义了一个带参构造函数,则需同时提供一个默认构造函数,否则属性注入时将抛出异常。 Spring只会检查Bean中是否有对应的Setter,是否有对应属性变量则不作要求。但一般约定俗成在Bean中提供同名的属性变量。 【Java属性变量】 一般以小写字母起头,JavaBean允许大写字母开头,但必须满足“变量的前两个字母要么全部大写,要么全部小写”,brand、IDCode合法,iC、IDCode非法。 2)构造函数注入 保证一些必要属性在Bean实例化时就得到设置,并确保Bean实例在实例化后就可以使用。使用构造函数的前提是Bean必须提供带参的构造函数。 Spring中构造函数注入的配置方式 注入时若匹配的构造函数不止一个,默认匹配最后一个构造函数。 Bean存在多个构造函数时,尽可能显示指定index、type。 ①ByType: <!--构造注入-类型匹配入参--> <bean id="car1" class="com.smart.injection.Car"> <constructor-arg type="java.lang.String"> <value>红旗CA72</value> </constructor-arg> <constructor-arg type="double"> <value>20000</value> </constructor-arg> <!--Car Bean中必须有和此处一致的构造函数(String、double参数的构造函数)--></bean> ②ByIndex: <!--构造注入-索引匹配入参--><bean id="carIndex" class="com.smart.injection.Car"> <!--索引从0开始--> <constructor-arg index="0" value="红旗CA72"/> <constructor-arg index="1" value="中国一汽"/> <constructor-arg index="2" value="20000"/> <!-- 必须有一个3个入参的构造函数,如果不止一个,将以最后一个构造函数为准 --></bean> ③ByTypeIndex: <!--构造注入-类型、索引 联合匹配入参--><bean id="carTypeIndex" class="com.smart.injection.Car"><constructor-arg index="0" type="java.lang.String"><value>红旗CA72</value></constructor-arg><constructor-arg index="1" type="java.lang.String" value="中国一汽"/><!-- constructor-arg两种格式均可 --> <!-- int、double直接写,不加java.lang,否则异常Could not resolve matching constructor (hint: specify index/type/name arguments for simple parameters to avoid type ambiguities) --><constructor-arg index="2" type="double"><value>20000</value></constructor-arg></bean> ④ByReflection: <!-- 构造注入-自身类型反射reflection --><bean id="office" class="com.smart.injection.Office"/><bean id="boss" class="com.smart.injection.Boss"><!--未设置type、index,通过入参值的类型完后匹配映射--><constructor-arg><value>xiaofan</value></constructor-arg><!--ref引用现有bean--><constructor-arg><ref bean="car"/></constructor-arg><constructor-arg><ref bean="office"/></constructor-arg></bean> 3)循环依赖问题 Spring容器顺利实例化以构造函数方式配置的Bean的前提是:Bean构造函数引用的对象已经就绪。 如果两个Bean均通过构造函数注入,且通过构造函数入参引用对方,就会发生类似线程死锁的循环依赖问题。 <bean id="boss" ……ref="car"> <bean id="car" ……ref="boss"> 因为循环依赖问题,Spring容器无法启动,将构造函数注入方式调整为属性注入即可。 2.3.3 注入参数详解 ①字面值--可通过<value>元素标签注入 字面值一般指可用字符串表示的值,如基本数据类型及其封装类、String。Spring内部编辑器可将以字符串表示的字面值转化为内部变量的相应类型。 特殊处理标签:<![CDATA[ str ]]>,作用是让XML解析器将标签中的字符串当做普通文本对待,防止某些字符串对XML格式造成破坏。 <value><![CDATA[红旗CA72]]></value> ②引用其他Bean--<ref> <ref bean="car"/> <ref>元素引用Bean的3个属性: bean:引用同一容器或父容器的Bean,最常见。 local:只能引用同一配置文件的Bean,可利用XML解析器自动检验引用的合法性。 parent:引用父容器的Bean。 ③集合类型属性 主要包括List、Set、Map、Properties。 <bean id="boss_list" class="com.smart.injection.Boss"><!--List属性既可通过value注入字符串,也可ref引用其他bean--><property name="list"><list><value>Java</value><value>Pathon</value></list></property></bean> <bean id="boss_map" class="com.smart.injection.Boss"><property name="mapWeek"><map><entry><key><value>Mon</value></key><value>Reading</value></entry><entry><key><value>Tues</value></key><value>Sporting</value></entry><!-- 如果Map元素的键和值都是对象,可以ref <entry> <key><ref bean="keyBean"/></key> <ref bean="valueBean"/> </entry> --></map></property></bean><bean id="boss_pro" class="com.smart.injection.Boss"><!--Properties键值只能是字符串,值的配置没有value标签--><property name="type"><props><prop key="IDE">IDEA</prop><prop key="SQL">MySQL</prop></props></property></bean> ④通过util命名空间配置集合类型的Bean <!--需在Spring配置文件引入util命名空间--><util:list id="list_util" list-class="java.util.LinkedList"><value>Coding</value><value>Walk</value><!--Set类似--></util:list><util:map id="map_util"><entry key="Wed" value="Java"/><entry key="Thu" value="Spring"/></util:map><!-- <util:list>和Set支持value-type属性;Map支持key-type和value-type属性,指定键值类型 --> ⑤简化配置方式 <!--简化XML配置:采用属性而非子元素配置信息--><!--Spring从2.5开始引入p命名空间,使用前需声明xmlns:p="http:~schema/p"--><bean id="car" class="com.smart.injection.Car"p:brand="红旗CA72"p:maxSpeed="260"/><bean id="boss" class="com.smart.injection.Boss"p:car-ref="car"/> ⑤自动装配 autowire=“<自动装配类型>” Spring IoC容器了解容器中所有的Bean信息,通过Java反射机制获知实现类的结构信息,开发人员可按照规则进行Bean的字段装配。 Spring提供了4种自动装配类型。<beans>元素标签中的default-autowired属性可配置为全局自动匹配,默认为0,表示不启用自动装配。其他可选配置如下图,byName,byType,constructor,autodetect。 减轻配置工作量,但造成配置文件Bean间关系不清楚,易引发潜在错误,因此实际项目建议少用。 2.3.4 Bean作用域 Spring2.0前仅有2个作用域:singleton和prototype。2.0开始针对WebApplicationContext新添了3个作用域。 Spring低版本由于只有2个作用域,So采用singleton=“true|false”的配置方式,为向后兼容,现依旧可用。 Spring新配置方式:scope=“<作用域类型>”<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/> 除以上5种,Spring还允许用户自定义Bean的作用域。org.springframework.beans.factory.config.Scope接口定义新作用域,再通过org。springframework.beans.factory.config.CustomScopeConfigurer这个BeanFactoryPostProcessor注册自定义的Bean作用域。【很少需要用户自定义作用域,自带的足以满足大部分需求】 2.3.5 基于注解的配置 1)使用注解定义Bean基于XML的配置,Bean定义信息和Bean实现类本身分离; 基于注解的配置,Bean定义信息通过在Bean实现类上标注注解实现。 @Component("userDao") public class UserDao{……}等价于一下XML配置: <bean id="userDao" class="com.smart.UserDao"/> 除@Component外,Spring还提供3个功能基本和其等效的注解,也称为Bean的衍型注解。 @Repository:标注Dao实现类 @Service:标注Service实现类 @Controller:标注Web层的Controller实现类衍型注解让注解本身用途清晰化,也被Spring赋予了一些特殊的功能。【推荐使用】 2)使用注解配置信息启动Spring容器Spring2.5后提供了context命名空间,提供了提供扫描包以应用注解定义Bean的方式。 使用方式: a、声明context命名空间,xmlns:context="http://www.springframework.org/schema/context" b、扫描类包以应用注解定义的Bean,<context:component-scanbase-package="com.smart.anno"> context的命名空间的component-scan的base-package属性指定一个需要扫描的基类包,Spring将扫描此基类包的所有类,并从注解信息中获取Bean信息。 c、resource-pattern:过滤出特定类,仅扫描特定类而非基类包下的所有类。 <context:component-scanbase-package="com.smart" resource-pattern="anno/*.class"> resource-pattern属性默认值为“**/*.class”,即基类包里的所有类。此处设置为"anno/*.class",Spring仅会扫描基类包anno子包中的类。 d、<context:component-scan>过滤子元素,如仅过滤基类包中实现了XXXService接口的类或标注了某个特定注解的类等,而resource-pattern仅可按资源名称过滤。 <context:component-scan base-package="com.smart"> <context:include-filter type="regex" expression="com\.smart\.anno.*"/> <context:exclude-filter type="aspectj" expression="com.smart..*Controller+"/></context:component-scan> <context:include-filter>表示要包含的类,<context:exclude-filter>表示要排除的类;一个component-scan下可包含若干个exclude和include。且这两个过滤元素均支持多种类型的过滤表达式。 过滤类型中,除custom外,aspectj过滤表达能力最强,可轻易实现其他类型所表达的过滤规则。 3)自动装配Bean【@Autowired】 @Service //定义一个Service,将UserService标注为一个Beanpublic class UserService {// 注入UserDao这个Dao层的Bean@Autowired private UserDao userDao; } @Autowired:默认按类型匹配,当有且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中。 ①@Autowired的requried属性 若容器中没有和按标注类型匹配的Bean,Spring将抛出NoSuchBeanDefinitionException异常,如果希望即使找不到匹配的Bean也不要抛出异常,可使用@Autowired(required = false)进行标注。【required默认值为true】 ②@Qualifier指定注入Bean的名称 如果有超过一个匹配的Bean,可通过@Qualifier注解限定Bean的名称。 @Autowired @Qualifier("userDao") //① private UserDao userDao; 假设容器有两个类型UserDao的Bean,一个名为UserDao,一个名为UserDao2,则①处会注入名为UserDao的Bean。 ③标注类方法 @Autowired 可标注类成员变量及方法的入参,亦可标注类。 @Autowired //自动将LogDao传给方法入参 public void setLogDao(LogDao logDao) { this.logDao = logDao; } @Autowired // 自动将,名为UserDao的Bean传给方法入参 @Qualifier("userDao") public void setUserDao(UserDao userDao) { this.userDao = userDao; } 如果一个方法拥有多个入参,默认情况下,Spring自动选择匹配入参类型的Bean进行注入,Spring允许使用@Qualifier指定注入入参的Bean的名称。 @Autowired public void init(@Qualifier("userDao")UserDao userDao,LogDao logDao){ this.userDao = userDao; this.logDao =logDao; } 以上例子,UserDao的入参注入名为UserDao的Bean,而LogDao的入参注入LogDao类型的Bean。 Note: 通常,Spring容器的大部分Bean是单实例,So无需通过@Repository、@Service等注解的value属性指定名称,也无需使用@Qualifier按名称注入。 ④标注集合类 Spring将容器中所有类型为Plugin的Bean注入这个变量中。 @Componentpublic class MyComponent {@Autowired(required = false)private List<Plugin> plugins;public List<Plugin> getPlugins() {return plugins; }}此处,Plugin为接口,有两个实现类且两个实现类都通过@Component标注为Bean,So Spring会将这两个Bean都注入plugins。 2.3.6 基于Java类的配置 1)使用Java类提供Bean定义信息 @Configuration// 此注解说明此类可用于为Spring提供Bean的定义信息 public class AppConf { @Bean // 将一个POJO标注为定义Bean的配置类 public UserDao userDao() { return new UserDao(); } @Bean // Bean类型由方法返回值类型决定,名称默认和方法名相同,亦可显示指定。 public LogDao logDao() { return new LogDao(); } @Bean(name="LogonService") public LogonService logonService() { LogonService logonService = new LogonService(); logonService.setLogDao(logDao()); // 注入上面定义的Bean logonService.setUserDao(userDao()); return logonService; } } 以上配置等价于 <bean id="userDao" class="com.smart.anno.UserDao"/> <bean id="logDao" class="com.smart.anno.LogDao"/> <bean id="logonService" class="com.smart.conf.LogonService" p:logDao-ref="userDao" p:userDao-ref="logDao"/> 对比: 基于Java类的配置方式:更灵活地实现Bean的实例化及Bean之间的装配; XML或基于注解: 通过配置声明,灵活性略逊,但配置更简单。 @Configuration public class DaoConfig { @Bean public UserDao userDao(){ return new UserDao(); } @Scope("prototype") @Bean public LogDao logDao(){ return new LogDao(); } } ------------------------ @Configuration public class ServiceConfig { @Autowired private DaoConfig daoConfig; @Bean public LogonService logonService() { LogonService logonService = new LogonService(); System.out.println(daoConfig.logDao() == daoConfig.logDao()); logonService.setLogDao(daoConfig.logDao()); //A、像普通Bean一样调用其他Bean方法 logonService.setUserDao(daoConfig.userDao()); return logonService; } } @Configuration注解类本身相当于标注了@Component,即可像普通Bean一样被注入其他Bean中(可直接调用@Configuration中标注@Bean的方法,如上)。 Spring对配置类所有标注@Bean的方法进行AOP增强,将对声明周期的逻辑植入进来。 A处调用daoConfig.logDao()逻辑:从Spring容器返回相应Bean的单例,即多次调用daoConfig.logDao()返回的都是相同Bean。Bean若标注@Scope("prototype"),则每次返回新的Bean。 2)直接通过@Configuration类启动Spring容器 // 使用@Configuration类中提供的Bean定义信息启动Spring容器 ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConf.class); //直接传入@Configuration标注的Java类 LogonService logonService = ctx.getBean(LogonService.class); 注: AnnotationConfigApplicationContext支持加载多个配置类 //2.通过编码方式注册配置类 AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext(); ctx2.register(DaoConfig.class); ctx2.register(ServiceConfig.class); ctx2.refresh(); //刷新容器以应用这些注册的配置类 //3.@Import将多个配置类组装到一个配置类中,仅需注册这个组装好的配置类即可启动容器。 @Configuration @Import(DaoConfig.class) public class ServiceConfig { @Autowired private DaoConfig daoConfig; @Bean public LogonService logonService() { LogonService logonService = new LogonService(); return logonService; } } 3)XML配置文件引用@Configuration的配置 // 通过上下文扫描加载到AppConf的配置类 <context:component-scan base-package="com.smart.conf" resource-pattern="AppConf.class" /> 4)通过Configuration配置类引用XML配置信息 beans3.xml: <bean id="userDao" class="com.smart.conf.UserDao"/> ----------------- @Configuration@ImportResource("classpath:com/smart/conf/beans3.xml") //自动注入XML文件中定义的Beanpublic class LogonAppConfig {@Bean // 定义一个LogonService的Bean @Autowired // 通过入参自动注入UserDao和LogDao的Beanpublic LogonService logonService(UserDao userDao, LogDao logDao) { LogonService logonService = new LogonService(); logonService.setUserDao(userDao); logonService.setLogDao(logDao);return logonService; }} 2.3.7 基于XML、注解、Java类三种配置方式的比较 通常基于Java类的方式使用较少。
今儿同事调接口时,发现对方返回的HttpResponse是经GZIP加密的,调用一个现成的解压Util总是失败。经查阅资料,个人封装了一个自带解压功能的post方法,适用于返回结果经GZIP加密的post请求,Get请求请自行更改。废话少说,上代码。 private String postGZIP(String url, String requestJson, String authorization)throws Exception { HttpClient httpClient = HttpClients.createDefault(); HttpPost httppost =null; String postUrl =null; HttpResponse response =null; String json =null; try{ postUrl = url +"?param="+ requestJson; httppost =new HttpPost(postUrl); List<NameValuePair> params =new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("param", requestJson)); // httppost.setEntity(new UrlEncodedFormEntity(params)); httppost.addHeader("Content-Type","text/plain;charset=utf-8"); // httppost.addHeader("Accept-Encoding", "gzip"); if(null!= authorization){ httppost.addHeader("Authorization", authorization); } response = httpClient.execute(httppost); InputStream is = response.getEntity().getContent(); is =new GZIPInputStream(is); BufferedReader br =new BufferedReader(new InputStreamReader(is)); String line =null; StringBuffer sb =new StringBuffer(); while((line = br.readLine())!=null){ sb.append(line); } json = sb.toString(); }catch(Exception e){ throw e; }finally{ if(null!= httppost){ httppost.releaseConnection(); } } if(json ==null){ thrownew Exception("接口无结果返回"); } return json; } 测试方法: @Test public void testGZIP()throws Exception { String url ="http://api.pkfare.com/test_api/shopping"; String req ="JTdCJTIyYXV0aGVudGljYXRpb24lMjIlM0ElN0IlMjJwYXJ0bmVySWQlMjIlM0ElMjJSa3NWU1g3UGZabTF5RjA0YWRCV1lzQ0Q3TTQlM0QlMjIlMkMlMjJzaWduJTIyJTNBJTIyMTdlNzA3YjVkOWRhZTg0NTkwNjVkYmIxMzllNWYxYmQlMjIlN0QlMkMlMjJzZWFyY2glMjIlM0ElN0IlMjJhZHVsdHMlMjIlM0ExJTJDJTIyYWlybGluZSUyMiUzQSUyMkNBJTIyJTJDJTIyY2hpbGRyZW4lMjIlM0EwJTJDJTIyaW5mYW50cyUyMiUzQTAlMkMlMjJub25zdG9wJTIyJTNBMSUyQyUyMnNlYXJjaEFpckxlZ3MlMjIlM0ElNUIlN0IlMjJjYWJpbkNsYXNzJTIyJTNBJTIyRWNvbm9teSUyMiUyQyUyMmRlcGFydHVyZURhdGUlMjIlM0ElMjIyMDE2LTAzLTE2JTIyJTJDJTIyZGVzdGluYXRpb24lMjIlM0ElMjJIS0clMjIlMkMlMjJvcmlnaW4lMjIlM0ElMjJCS0slMjIlN0QlNUQlMkMlMjJzb2x1dGlvbnMlMjIlM0EyMCU3RCU3RA=="; System.out.println(pkfareBusiness.postGZIP(url, req, null)); }
Spring是分层的Java SE/EE应用一站式的轻量开源框架,以 反转控制(Inverse of Control,IoC)、面向切面编程(Aspect Oriented Programming,AOP)为内核,提供了展现层Spring MVC、持久层Spring JDBC以及业务层事务管理等众多的企业级应用技术。此外,Spring整合了众多著名第三方框架和类库。 1、发展史--Rod Johnson Spring的框架首次在2003年6月的Apache 2.0的使用许可中发布; 第一个具有里程碑意义的版本是2004年3月发布的1.0; 2004年9月和2005年3月先后又有重要的版本面世; Spring Framework 首个版本是在 2004 年发布的,其后包括几个重大版本改进。Spring 2.0 提供了 XML 命名空间和 AspectJ 支持;Spring 2.5 包含注解驱动的配置;Spring 3.0 引入加强的 Java 5+ 基础和 @configuration 模型。 而 Spring 4.0 是最新的主要版本,这是首个完全支持 Java 8 特性的框架,该版本最低要求 Java SE 6 的支持。删除了废弃的方法和类。 2、优点 方便解耦,简化开发; AOP编程的支持; 声明式事物的支持; 方便程序测试; 方便集成各种优秀框架; 降低Java EE API使用难度; 源码设计精妙、结构清晰,Java技术的最佳时间范例。 3、Spring 4.0 体系结构图: 可对比旧体系结构,如下: 4、Spring 4.0的新特性: Java 8诸多特性的支持; 使用Groovy DSL定义外部的Bean配置,这类似于XML Bean声明,但是语法更为简洁。使用Groovy还能够在启动代码中直接嵌入Bean的声明。 核心容器功能的改进(支持泛型依赖注入;使用meta-annoation方式定义Annotation时, 该Annotation可以访问源Annotation的部分属性,以更加方便的定制自己想要的Annotation;Bean依赖注入到Map和List,Array中, 即提供了一种方式获取到某个类型的所有Bean,当注入到Map中时, Key为Bean的名字,value为Bean实例。) Web开发改进(增加了@RestController annotation、AsyncRestTemplate类;Spring4.0基于Servlet3.0+版本开发;为Spring MVC应用增加了Timezone的支持,可以在RequestContext获取,设置TimeZone信息,Spring还提供Datetime的转换功能;提供了 WebSocket, SockJS, and STOMP Messaging的支持;支持STOMP Message协议) 测试框架改进 (几乎所有spring-test模块下的annotation都可以做元annoation, 开发者就可以更方便得定制自己的annotation,以增强代码表现力和减少多个Test之间的重复代码;增加了一种更灵活的ActiveProfiles的决定方式;添加了SocketUtils类帮忙扫描本地机器上的可用Socket端口;org.springframework.mock.web包下的Mock类都与Servlet 3.0兼容了;删除了所有废弃的方法和类) 参考:http://ningandjiao.iteye.com/blog/1993481 5、结构阐述 IoC 将类和类之间的依赖从代码中脱离,用配置的方式进行依赖关系描述,由IOC容器负责依赖类之间的创建、拼接、管理、获取等。
1、IoC概述 控制反转(Inverse of IoC)是Spring容器的内核,AOP、声明式事务都基于此。 IOC,即通过容器来控制业务对象间的依赖关系,而非传统的由代码直接操控。此即控制反转的关键所在:控制权由应用代码中转到了外部容器,控制权的转移,就是反转。 控制权转移好处:降低业务对象间的依赖程度。 2、BeanFactory 和 ApplicationContext Spring通过配置文件描述了Bean及Bean间的依赖关系,利用Java反射功能实例化Bean并建立Bean间的依赖关系。 Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,提供了高级IoC的配置机制。使管理不同类型的Java对象成为可能。 应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能以及国际化支持和框架事件体系。 一般称BeanFactory为IoC容器,而称ApplicationContext为应用上下文。有时也称ApplicationContext为Spring容器。 用途划分:BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向Spring框架的开发者,几乎所有场合直接使用后者而非底层的BeanFactory。 2.1、ApplicationContext的初始化: 1)、配置文件在类路径,优先使用ClassPathXMLApplicationContext: ApplicationContext ctx=new ClassPathXmlApplicationContext("com/smart/context/beans.xml"); 对于ClassPathXMLApplicationContext, "com/smart/context/beans.xml"等同于"classpath:com/smart/context/beans.xml" 2)、配置文件在文件系统路径,优先使用FileSystemXMLApplicationContext ApplicationContext ctx=new FileSystemXMLApplicationContext("com/smart/context/beans.xml"); "com/smart/context/beans.xml"等同于"file:com/smart/context/beans.xml" 3)、配置文件整合 Spring会自动将多个配置文件在内存中“整合”成一个配置文件。 new ClassPathXMLApplicationContext(new String[] {"conf/beans1.xml","conf/beans2.xml"}) FileSystem~和ClassPath~都可显示使用带资源类型前缀的路径。 区别:如果不显示指定资源类型前缀,就分别将路径解析为文件系统路径和类路径。 获取ApplicationContext实例后,就可以向beanFactory一样调用getBean(beanname)返回Bean了。 2.2、ApplicationContext和BeanFactory初始化的区别: BeanFactory初始化容器时,并未实例化Bean,直到第一次访问某个Bean时才实例化目标Bean; ApplicationContext初始化应用上下文时就实例化所有单例的Bean。 So,ApplicationContext初始化时间比BeanFactory稍长,不过稍后调用则不会再耗费时间初始化了。 3、资源加载 Spring支持的资源类型的地址前缀, 3.1、classpath 和 classpath*的区别: 有多个JAR包或文件系统类路径都拥有一个相同的包名(如com.smart), classpath:只会在第一个加载的com.smart包下查找。 classpath*:扫描所有这些JAR包及类路径下出现的com.smart类路径。 3.2、 Ant风格资源路径 支持3种通配符: ?:匹配文件名中的一个字符; *:文件名中的任意个字符; **:匹配多层路径。 Ant风格资源路径示例:
IDEA的Spring配置文件异常:checks references injected by intellilang plugin 纠结这个问题了好久,大概是点了小灯泡后,也不记得接着点了些啥。 最后在官网找到了只言片语,根据错误信息,将上图圈出的插件禁用就OK了。 http://www.jetbrains.com/idea/help/using-language-injections.html 操作: File--Settings【或直接CTRL+ALT+S】--Plugins中输入intellilang,将后面的勾去掉,再点击OK并按提示重启IDEA就可以了。
Spring属性注入异常 错误信息: Caused by: org.springframework.beans.NotWritablePropertyException: Invalid property 'brand ' of bean class [com.smart.injection.Car]: Bean property 'brand ' is not writable or has an invalid setter method. Did you mean 'brand'? 相关配置文件: <?xml version="1.0"encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--属性注入--> <bean id="car"class="com.smart.injection.Car"> <property name="brand "><value>红旗CA72</value></property> <property name="maxSpeed"><value>200</value></property> </bean> </beans> 排查过程: 检查配置文件是否对应、属性名是否正确、是否有网上所说的setter方法。结果均正确,brand是String类型,将其value值“红旗CA72”加上引号依旧报错。 最终解决方案: 眼尖一点,就会发现,配置文件中<property name="brand ">的brand多了一个空格,删掉多余空格就OK。泪崩啊
最近在看《Spring3.0就这么简单》这本书,开发环境为IDEA+Maven,今儿写代码时,Spring加载配置文件总是失败,相当郁闷,不过还是解决了。 最初的写法是 Resource res=new ClassPathResource("classpath:com/smart/beanfactory/beans.xml"); 或者 ApplicationContext factory = new ClassPathXmlApplicationContext("beans.xml"); 当然,括号里的路径诸如直接写beans.xml,或在前面加路径,或加classpath,或将beans.xml换在src、main、java等目录下,改了好多次,运行结果均是找不到配置文件。 目录结构如下: 解决方案:将配置文件放在resources目录下,这是Maven项目放置配置文件的专用目录。 附 ApplicationContext的初始化: 1、配置文件在类路径,优先使用ClassPathXMLApplicationContext: ApplicationContext ctx=new ClassPathXmlApplicationContext("com/smart/context/beans.xml"); 对于ClassPathXMLApplicationContext, "com/smart/context/beans.xml"等同于"classpath:com/smart/context/beans.xml" 2、配置文件在文件系统路径,优先使用FileSystemXMLApplicationContext ApplicationContext ctx=new FileSystemXMLApplicationContext("com/smart/context/beans.xml"); "com/smart/context/beans.xml"等同于"file:com/smart/context/beans.xml" 3、配置文件整合 Spring会自动将多个配置文件在内存中“整合”成一个配置文件。 new ClassPathXMLApplicationContext(new String[] {"conf/beans1.xml","conf/beans2.xml"}) FileSystem~和ClassPath~都可显示使用带资源类型前缀的路径。 区别:如果不显示指定资源类型前缀,就分别将路径解析为文件系统路径和类路径。 相关代码:可访问我的github的chapter仓库chapter2目录。
今儿用Maven时,奇慢无比,实在不能忍,网上搜了下国内可用的Maven库,还真发现个好去处 开源中国 Maven 库:http://maven.oschina.net/home.html 很贴心的提供了帮助文档,http://maven.oschina.net/help.html。使用方法也非常简单,直接搜索你想要的包,选中jar,复制右边的XML到配置文件中,再更新即可。
报错信息: { "ErrorCode" : 2, "ErrorContent" : "java.util.LinkedHashMap cannot be cast to com.better517na.gwCommunicateJavaService.wordJiujiu.model.vo.PayVo", "ResponseTime" : 1455851510156 } 解决思路: 1、服务调用方 传参正确,调用方和服务提供方的对象model均未使用LinkedHashMap。2、入参 public class RequestVo<T> { @NotNull private T body; } body是泛型。 3、调用方传入参数RequestVo,提供方只是直接PayVo payVo = requestVo.getBody()了,这里出现了异常。 解决方案: 将传入的参数转为PayVo即可。添加如下两行代码: // 将泛型body转json再转PayVo对象。 PayVo payvo = GSON.fromJson(GSON.toJson(requestVo.getBody()), PayVo.class); requestVo.setBody(payvo); 后面再PayVo payVo = requestVo.getBody()即正常了。
每次保存项目时总是报错:Errors occurred during the build.Errors running builder 'Checkstyle Builder' on project 。CheckStyle也无法运行。 问题原因: 当前项目CheckStyle配置文件的check-config-name和eclipse所安装的CheckStyle的名字不一样。 解决方案: 全局搜索【Ctrl+H--》File Search】“check-config-name”,复制其value,此处是checkStyle_517na; Windows--Preferences--CheckStyle,选中你所安装的CheckStyle,点击Properties; 修改其name为刚刚在配置文件复制的value。 在Used in projects里看到你的项目就不会报错了(可能需要重启Eclipse)。
svn只能checkout,不能commit、update等。重装、换版本均无效。 解决方案: CheckOut时Revision不要选择HEAD revision,而是选择Revision,点击show log选择最新版本或者你需要的版本即可。
SVN提交代码失败,而且就只有一个文件提交失败,其他提交正常,报错信息如下: org.apache.subversion.javahl.ClientException: svn: E200007: Commit failed (details follow): svn: E200007: CHECKOUT can only be performed on a version resource [at this time]. svn: E200007: Commit failed (details follow): svn: E200007: Commit failed (details follow): svn: E200007: CHECKOUT can only be performed on a version resource [at this time]. svn: E175002: CHECKOUT request failed on '/svn/GWCommunicateJavaService/!svn/rvr/1198/branches/GWCommunicateJavaService(20160216_V6527)/src/com/better517na/gwCommunicateJavaService/base/model/vo/InCreateOrderVo.java' svn: E200007: Commit failed (details follow): svn: E200007: CHECKOUT can only be performed on a version resource [at this time]. svn: E175002: CHECKOUT request failed on '/svn/GWCommunicateJavaService/!svn/rvr/1198/branches/GWCommunicateJavaService(20160216_V6527)/src/com/better517na/gwCommunicateJavaService/base/model/vo/InCreateOrderVo.java' 解决方案: 清除本地的缓存, 项目-- Team--Referesh/Clearup
今天从cmd命令行运行MySQL脚本.sql,参照网上教程,大致如下两种方法: 未登录:D:\mysql\bin\mysql –uroot –p123456 -Dtest<d:\test\ss.sql 登陆:Mysql>source d:\test\ss.sql 各种不要引号、单引号、双引号均报错, ERROR: Failed to open file ''E:\t_user.sql'', error: 22 事实上该如何操作呢? 未登录状态: mysql -uroot -p -P3307 -D sampledb<E:/t_user.sql 注意: ①如果在sql脚本文件中使用了use 数据库,则-D数据库选项可以忽略 ②如果【Mysql的bin目录】中包含空格,则需要使用""包含。 –u用户名 –p密码 –D数据库<【sql脚本文件路径全名】 ③命令末尾千万不要有分号,大坑啊 登陆状态: mysql> use 数据库名; mysql>source 【sql脚本文件的路径全名】,如: mysql> source E:/t_user.sql; 注意:windows路径为E:\t_user.sql,但这里要用正斜杠/ Note:没有进入MySQL环境,执行命令不需要有分号,进入MySQL环境后则需要分号。 Mark一下,希望能帮到遇到类似问题的朋友。
过了一个春节,公司电脑的Eclipse出了问题。昨日安装SVN插件在Preferences里没找到(本地复制安装、在线安装均无效),因为不急着用,就想着过几天再处理。哪想今天发现反编译插件也不能用了,安装时出现同样的问题。网上搜罗了一堆,如下,各位如果有更好的解决方案,望留言,谢谢。 重装Eclipse;【暴力有效】 把eclipse安装目录下的configuration/org.eclipse.update和runtime的目录整个删除,重启eclipse。(org.eclipse.update 文件夹下记录了插件的历史更新情况,它只记忆了以前的插件更新情况,而新安装的插件它并不记录,所以删除掉这个文件夹就可以解决这个问题了,不过删除掉这个文件夹后, eclipse 会重新扫描所有的插件,此时再重新启动 eclipse 时可能会比刚才稍微慢点) 创建一个 Eclipse 快捷启动方式,在目标栏中加入一个“ -clean ”参数(前面有个空格),如果启动 eclipse 后找到你所安装的新插件后,在下次启动之前把参数 clean 去掉就可以了。 如果 Eclipse 启动还是找不到插件的话,在 eclipse/configuration 目录下的 config.ini 文件中加入一行 : osgi.checkConfiguration=true 这样它就会寻找并安装插件。注意找到插件后可以把该行注释掉 ( 去掉 ), 这样以后每次启动就不会因为寻找插件而显得慢了。 以上来源于网络收集,2016.02.15测试3、4无效,2方案只删除update无效,不知道同时删除Runtime是否有效,当然1是绝对有效的。 由于同一个bug可能由很多问题引起,我未成功的几种方案有很多网友却成功了,So一并附上了,遇到此问题的同学,可以挨个试试,如果有效的话,忘留言反馈,以便给更多的人提供参考。
新年上班第一天,Eclipse就闹别扭了,打开时报错(以管理员权限运行依旧报错) A Java RunTime Environment (JRE) or Java Development Kit (JDK) must be available in order to run Eclipse. No java virtual machine was found after searching the following locations:E:eclipse\jre\bin\jacaw.exe javaw.exe in your current Path。 解决方案: 系统变量里设置下面: 变量名:JAVA_HOME 变量值:C:\Program Files\Java\jdk1.8.0_60【本机JDK目录】 变量名:CLASSPATH 变量值:.;%JAVA_HOME%\lib;【注意前面有个小数点】 变量名:Path 变量值:%JAVA_HOME%\bin;
grant 权限 on 数据库名.表名 用户@‘登录主机’ identified by "用户密码"; Note: 登陆主机、密码必须加引号,单双皆可。 1、grant 普通数据用户,查询、插入、更新、删除 数据库中所有表数据的权利 grant select, insert, update, delete on testdb.* to common_user@'%'; 2、grant 数据库开发人员,创建表、索引、视图、存储过程、函数。。。等权限 create、alter、drop、reference(外键)、create temporary tables(操作临时表)、index、create view、show view(操作、查看视图)、 create routine、alter routine(储存过程)、execute函数; 3、grant 普通 DBA 管理某个 MySQL 数据库的权限 grant all privilegeson testdb to dba@'localhost' 关键字 “privileges” 可以省略。 4、grant 高级 DBA 管理 MySQL 中所有数据库的权限 grant all on *.* to dba@'localhost' 5、对特定列授权 grant select(id, se, rank) on testdb.apache_log to dba@localhost; 6、grant 作用在存储过程、函数上 grant execute on procedure testdb.pr_add to 'dba'@'localhost'grant execute on functiontestdb.fn_add to 'dba'@'localhost' 7、查看用户的权限 当前用户:show grants; 其他用户:show grants for dba@localhost; 8、撤销已经赋予给 MySQL 用户权限的权限revoke revoke all on *.* from dba@localhost; 和grant类似,只需将关键字to换成from,同样可以针对特定用户特定库撤销特定权限。 revoke insert on *.* from root@'%'; #仅不允许插入操作 Note: 1. grant, revoke 用户权限后,该用户只有重新连接 MySQL 数据库,权限才能生效。 2. 如果想让授权的用户,也可以将这些权限 grant 给其他用户,需要选项 “grant option“ grant select on testdb.*to dba@localhost with grant option; 实际中,数据库权限最好由 DBA 来统一管理。 授权表 mysql授权表共有5个,位与“mysql”库中:user、db、host、tables_priv和columns_priv。 每个授权表中包含类似于:Select_priv、Insert_priv、Alter_priv等列。多数列的参数类型是enum。 授权表的内容有如下用途:user表 user表列出可以连接服务器的用户及其口令,并且它指定他们有哪种全局(超级用户)权限。在user表启用的任何权限均是全局权限,并适用于所有数据库。例如,如果你启用了DELETE权限,在这里列出的用户可以从任何表中删除记录,所以在你这样做之前要认真考虑。 db表 db表列出数据库,而用户有权限访问它们。在这里指定的权限适用于一个数据库中的所有表。 host表 host表与db表结合使用在一个较好层次上控制特定主机对数据库的访问权限,这可能比单独使用db好些。这个表不受GRANT和REVOKE语句的影响,所以,你可能发觉你根本不是用它。 tables_priv表 tables_priv表指定表级权限,在这里指定的一个权限适用于一个表的所有列。 columns_priv表 columns_priv表指定列级权限。这里指定的权限适用于一个表的特定列。 参考资料:http://www.cnblogs.com/hcbin/archive/2010/04/23/1718379.html
MySQL默认情况下用户只允许在本地登录,如果需要远程登录该如何操作呢? 1、创建新用户 格式: grant 权限 on 数据库名.表名 用户@登录主机 identified by "用户密码"; grant select,update,insert,delete on *.* to name@IP identified by "password"; 创建一个用户名为name,密码是password的用户,且只允许IP地址为“IP”的主机访问,该用户具有增删改查四种权限。 name@'%':允许任意IP地址的主机访问。all PRIVILEGES 表示赋予所有的权限给指定用户,这里也可以替换为赋予某一具体的权限,例如:select,insert,update,delete,create,drop 等,具体权限间用“,”半角逗号分隔。 grant用法详见keyword目录 2、对现有用户授权 GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY "password" WITH GRANT OPTION; 授予所有权限。 Note: 授权完毕可执行flush privileges;重载授权表,即可立即使用。 3、改表法 mysql>use mysql Database changed mysql>updateusersethost='%'whereuser='mysql'; Query OK,1row affected (0.00 sec) Rows matched:1 Changed:1 Warnings:0 mysql> flush privileges; Query OK,0rows affected (0.00 sec) 进入”mysql“库--更新权限信息--重载授权表。 MYSQL权限详细分类: 全局管理权限: FILE: 在MySQL服务器上读写文件。 PROCESS: 显示或杀死属于其它用户的服务线程。 RELOAD: 重载访问控制表,刷新日志等。 SHUTDOWN: 关闭MySQL服务。 数据库/数据表/数据列权限: ALTER: 修改已存在的数据表(例如增加/删除列)和索引。 CREATE: 建立新的数据库或数据表。 DELETE: 删除表的记录。 DROP: 删除数据表或数据库。 INDEX: 建立或删除索引。 INSERT: 增加表的记录。 SELECT: 显示/搜索表的记录。 UPDATE: 修改表中已存在的记录。 特别的权限: ALL: 允许做任何事(和root一样)。 USAGE: 只允许登录--其它什么也不允许做。
SELECT expr,... FROM table [WHERE where_definition] [GROUP BY~[ASC|DESC],...] [HAVING where_definition] [ORDER BY~[ASC|DESC],...] [LIMIT~] 每个分组只返回一行数据;欲返回所有数据用order by。 where指定范围; having限定条件; order by指定排序方式。 +----+--------+-----+-----+-------+------+-------+------------+ | id | name | age | sex | grade | math | level | date | +----+--------+-----+-----+-------+------+-------+------------+ | 1 | 红红 | 12 | 女 | 66 | 87 | 3 | 2016-01-04 | | 3 | 小王王 | 22 | 男 | 67 | 59 | 1 | 2016-01-13 | | 4 | 小_ | 20 | 男 | 77 | 72 | 3 | 2016-01-07 | | 5 | 张三 | 19 | 男 | 88 | 65 | 3 | 2016-01-21 | | 6 | 溜溜 | 25 | 女 | 66 | 68 | 2 | 2016-01-08 | | 7 | 可可 | 22 | 女 | 65 | 93 | 1 | 2016-01-28 | | 8 | 糊糊 | 12 | 女 | 90 | 88 | 2 | 2016-01-19 | | 9 | Mary | 21 | 男 | 88 | 79 | 1 | 2016-01-06 | +----+--------+-----+-----+-------+------+-------+------------+ 1、每个等级level的math最高分: SELECT `level`,MAX(math)FROM student GROUP BY `level`; +-------+-----------+ | level | MAX(math) | +-------+-----------+ | 1 | 93 | | 2 | 88 | | 3 | 87 | +-------+-----------+ 3 rows in set # 每个等级的总数学成绩 mysql> SELECT `level`,sum(math) FROM student GROUP BY `level`; +-------+-----------+ | level | sum(math) | +-------+-----------+ | 1 | 231 | | 2 | 156 | | 3 | 224 | +-------+-----------+ 3 rows in set 2、按两个字段排序 SELECT `level`,sex,MAX(math)FROM student GROUP BY `level`,sex; 先level后sex。 +-------+-----+-----------+ | level | sex | MAX(math) | +-------+-----+-----------+ | 1 | 女 | 93 | | 1 | 男 | 79 | | 2 | 女 | 88 | | 3 | 女 | 87 | | 3 | 男 | 72 | +-------+-----+-----------+ 5 rows in set 为何 女nv 在 男nan 的前面???order by亦如此。 3、having限定条件 SELECT `level`,MAX(math)FROM student GROUP BY `level` HAVING COUNT(*)>2; 和2对比。按level分组,且要求每个level中的人数大于2. +-------+-----------+ | level | MAX(math) | +-------+-----------+ | 1 | 93 | | 3 | 87 | +-------+-----------+ 2 rows in set 4、having、order by SELECT id,`level`,MAX(math),AVG(grade)FROM student GROUPBY `level` HAVINGAVG(grade)>76ORDERBY id;grade平均成绩大于76,按id排序。 +----+-------+-----------+------------+ | id | level | MAX(math) | AVG(grade) | +----+-------+-----------+------------+ | 1 | 3 | 87 | 77 | | 6 | 2 | 88 | 78 | +----+-------+-----------+------------+ 2 rows in set
① Select * from table LIMIT m; # 从0开始,m条记录 ② Select * from table LIMIT m,n; # 从m+1条记录开始,n条记录【查询语句偏移量offset很大的时候,效率较低】 ③ Select * from table LIMIT rows OFFECT offset; 第一个自变量指定:返回的第一行的偏移量offset,第二个自变量指定:返回的行数的最大值。初始行的偏移量为0(不是1)。 Select*from sakila.rental LIMIT 14036,10;#① 6ms Select*from sakila.rental LIMIT 10 OFFSET 14036;#② 6ms ,兼容PostgreSQL ,【结果与 ① 等价】 Select*from sakila.rental WHERE rental_id>=14036 LIMIT 10; #③ 1ms Select*from sakila.rental WHERE rental_id>=(SELECT rental_id from sakila.rental limit 14306,1) LIMIT 10; #④ 3ms offset偏移量较大时,可先获取到offset的id后,再直接使用limit size来获取数据。(实际SQL中偏移量对应字段应该where字段不一致才有效果,和SQL①对比) Select*from sakila.rental LIMIT 0; #⑤ 迅速返回一个空集。这可以用于检查一个查询的有效性 SELECT*FROM rental WHERE rental_date ='2005-08-01 09:45:58'LIMIT 1; #⑥ 无limit耗时0.018s,# 有limit耗时0.002s Select*from sakila.rental INNER JOIN(SELECT rental_id from sakila.rental limit14306,10) aa USING(rental_id); # 3ms,必须给子查询的表加一个别名aa;效果和④等价 Note: where...limit....性能基本稳定,受偏移量offset和行数rows的影响不大; 而单纯采用limit的话,受偏移量的影响很大,当偏移量大到一定后性能开始大幅下降。
昨天远程连接我服务器的SQLServer数据库,总是失败,设置方法网上有很多,我就不重复了。说一下我的失败原因吧。 报错信息: [SQL Server Native Client 10.0]命名管道提供程序: 无法打开与 SQL Server 的连接 [5 3]. [SQL Server Native Client 10.0]登录超时已过期 [SQL Server Native Client 10.0]与 SQL Server 建立连接时,发生了与网络相关的或特定于实例的错误。找不到或无法访问服务器。请检查 实例名称是否正确以及 SQL Server 是否已配置为允许远程连接。有关详细信息,请参阅 SQL Server 联机丛书。 注意事项: 端口可能要自己新建,默认1433,注意防火墙; 个人启用了IP1、IP2、IP3、IPAll,已启用选择”是“; 设置完后重启SqlServer服务和SqlServer browser服务; 命令行连接语句osql -S IP -U sa -P 123456【我在这里被坑惨了,用ipconfig查出来的IP地址是内网IP,而显然我们应该用外网IP,So后面我用域名连接一下就成功了】。 个人电脑Win10,用Navicat连接SQLServer时,提示安装SQLServer的一个插件sql server native client,按照他的提示安装,却提示与当前操作系统不匹配,实际上在Navicat的安装目录就有我们所需的安装包了 sqlncli_x64.msi (sqlncli.msi),直接安装这个就Ok了。 相关文章: SQL Server命令行 SQL Server安装使用报错及解决方案:http://blog.csdn.net/u010887744/article/details/50637398
本文来源于网络收集,Mark一下。 1、登陆 osql -S localhost -U sa -P 123456 T-SQL 即 Transact-SQL,是 SQL 在 Microsoft SQL Server 上的增强版,它是用来让应用程式与 SQL Server 沟通的主要语言。T-SQL 提供标准 SQL 的 DDL 和 DML 功能,加上延伸的函数、系统预存程序以及程式设计结构(例如 IF 和 WHILE)让程式设计更有弹性。 C:\Windows\system32>osql ? Microsoft (R) SQL Server 命令行工具 版本 10.50.1600.1 NT x64 版权所有 (c) Microsoft Corporation。保留所有权利。 注意: osql 并不支持 SQL Server 2008 R2的所有功能。 请使用 sqlcmd。有关详细信息,请参阅 SQL Server 联机丛书。 用法: osql [-U 登录 ID] [-P 密码] [-S 服务器] [-H 主机名] [-E 可信连接] [-d 使用数据库名称] [-l 登录超时值] [-t 查询超时值] [-h 标题] [-s 列分隔符] [-w 列宽] [-a 数据包大小] [-e 回显输入] [-I 允许带引号的标识符] [-L 列出服务器] [-c 命令结束] [-D ODBC DSN 名称] [-q "命令行查询"] [-Q "命令行查询" 并退出] [-n 删除编号方式] [-m 错误级别] [-r 发送到 stderr 的消息] [-V 严重级别] [-i 输入文件] [-o 输出文件] [-p 打印统计信息] [-b 出错时中止批处理] [-X[1] 禁用命令,[退出的同时显示警告]] [-O 使用旧 ISQL 行为禁用下列项] <EOF> 批处理 自动调整控制台宽度 宽消息 默认错误级别为 -1 和 1 [-? 显示语法摘要] 启动:Net Start MSSqlServer 暂停:Net Pause MSSqlServer 重新启动暂停的sql server :Net Continue MSSqlServer 停止:Net stop MSSqlServer ◆ 信任连接: >isql -E 或 >osql -E ◆ 察看所有数据库: use master exec sp_helpdb GO ◆ 察看数据库 pubs: use master exec sp_helpdb pubs GO ◆ 察看数据库 pubs 中的对象: USE pubs EXEC sp_help GO 相当于 Oracle 的 SELECT table_name FROM user_objects; ◆ 察看数据库 pubs 中的表 employee 结构: USE pubs EXEC sp_help employee GO 相当于 Oracle 的 SQL*PLUS 中的 DESC employees ◆ SELECT 语句: USE pubs SELECT * FROM employee GO ◆ 当使用单引号分隔一个包括嵌入单引号的字符常量时,用两个单引号表示嵌入单引号,例如: SELECT 'O''Leary' GO ◆ 用7.个双引号表示嵌入双引号,例如: SELECT 'O"Leary' GO ◆ SQL Server 数据库信息查询 use master exec sp_helpdb pubs GO 或: use master SELECT name, dbid FROM sysdatabases GO ◆ 查数据库对象 (相当于 Oracle 的 SELECT * FROM user_tables;) USE pubs EXEC sp_help GO 或 use master SELECT name, id FROM pubs.dbo.sysobjects WHERE type='U' GO ◆ 查字段 (相当于 Oracle 的 SQL*PLUS 中的 DESC employees ) USE pubs EXEC sp_help employee GO ◆ 查看指定 USE pubs SELECT name, id, xtype, length FROM syscolumns WHERE id=277576027 GO USE pubs SELECT * FROM syscolumns WHERE id=277576027 GO ◆ 查看数据类型名字的定义: SELECT name, xtype FROM systypes GO ◆ 从命令行启动“查询分析器” >isqlw ◆ isql命令 描述 GO 执行最后一个 GO 命令之后输入的所有语句。 RESET 清除已输入的所有语句。 ED 调用编辑器。 !! command 执行操作系统命令。 QUIT 或 EXIT( ) 直接退出 isql。 CTRL+C 不退出 isql 而结束查询。 仅当命令终止符 GO(默认)、RESET、ED、!!、EXIT、QUIT 和 CTRL+C 出现在一行的开始(紧跟 isql 提示符)时才可以被识别。isql 忽视同一行中这些关键字后输入的任何内容。 http://www.jb51.net/article/29170.htm ----援引 百度百科 osql 工具是一个 Microsoft Windows 32 命令提示符工具,您可以使用它运行 Transact-SQL 语句和脚本文件。osql 工具使用 ODBC 数据库应用程序编程接口 (API) 与服务器通讯。 目录 1. 1 如何使用 Osql 2. 2 提交 Osql 作业 3. ▪ 方法一 1. ▪ 方法二 2. ▪ 其中 3. ▪ 其中 1. ▪ 回到顶端 2. ▪ osql -E 如何使用 Osql编辑 一般情况下,可以这样使用 osql 工具: 用户通过与使用命令提示符时相似的方式交互输入 Transact-SQL 语句。 用户提交 osql 作业,方法是:指定单个要运行的 Transact-SQL 语句。 - 或 - 将该工具指向一个包含要运行的 Transact-SQL 语句的脚本文件。 交互式输入 Transact-SQL 语句 如要显示 osql 工具的区分大小写的选项列表,请在命令提示符下键入如下内容,然后按 ENTER 键: osql -? 如想了解关于 osql 工具的每一选项的更多信息,请参见“SQL Server 联机图书”中的“osql Utility”主题。 如要交互输入 Transact-SQL 语句,请按照下列步骤操作: 1. 确认 MSDE 2000 正在运行。 2. 连接到 MSDE 2000(有关更多信息,请参见标题为“连接到 SQL Server 桌面引擎 (MSDE 2000)”的部分)。 3. 在 osql 命令提示符下,键入 Transact-SQL 语句,然后按 ENTER 键。当您在输入的每一行后按 ENTER 键时,osql 将缓存该命令行上的语句。 如要运行当前缓存的语句,请键入“Go”,接着按 ENTER 键。 如要运行一批 Transact-SQL 语句,请分别在单独的行上输入每一个 Transact-SQL 命令。然后,在最后一行上键入“Go”以表示批处理命令的结束并运行当前缓存的语句。 运行结果出现在控制台窗口。 4. 当您在输入的每一行后按 ENTER 键时,如想从 osql 退出,请键入 QUIT 或 EXIT,并按 ENTER 键。 提交 Osql 作业编辑 一般情况下,您可以用两种方法之一提交 osql 作业。您可以:指定单个 Transact-SQL 语句。 - 或 - 将该工具指向一个脚本文件。 下面将详细介绍每一种方法。 方法一 指定单个 Transact-SQL 语句 如要针对 MSDE 2000 的本地默认实例运行 Transact-SQL,请键入与下面这一个类似的命令: osql -E -q "Transact-SQL statement" 其中 -E 表示使用 Microsoft Windows NT 身份验证。 -而- -q表示运行 Transact-SQL 语句,但是在查询结束时不退出 osql。 如要运行 Transact-SQL 语句并退出 osql,请使用-Q 参数来代替 -q。 将该工具指向一个脚本文件 如要将该工具指向一个脚本文件,请按照下列步骤操作: 1. 创建一个包含一批 Transact-SQL 语句的脚本文件(如 myQueries.sql)。 方法二 打开命令提示符,键入与下面类似的一个命令,然后按 ENTER 键: osql -E -i input_file 其中 input_file 是脚本文件及其完整路径。例如,如果脚本文件 myQueries.sql 在 C:\Queries 文件夹中,请将参数 input_file 替换为 C:\Queries\myQueries.sql。 该脚本文件的运行结果将出现在控制台窗口中。如果您想将运行结果定向到一个文件,请向上述命令中添加 -ooutput_file 参数。例如: osql -E -i input_file -o output_file 其中 output_file 是输出文件及其完整路径。 如想消除输出结果中的编号和提示符号,请向上述命令中添加 -n 选项。例如: osql -E -i input_file -o output_file -n 回到顶端 连接到 SQL Server 桌面引擎 (MSDE 2000) 如要连接到 MSDE 2000,请按照下列步骤操作: 1. 确认 MSDE 2000 正在运行。 2. 在承载您要连接的 MSDE 2000 实例的计算机上打开一个命令窗口。 3. 键入下面的命令,然后按 ENTER 键: osql -E 这可以通过使用 Windows 身份验证将您连接到 MSDE 2000 的本地默认实例。 如要连接到 MSDE 2000 的一个命名实例,请键入: osql -E -S servername\instancename 如果您收到了下面的错误消息,表明 MSDE 2000 可能未在运行,或者您可能为安装的 MSDE 2000 的命名实例提供了错误的名称: [Shared Memory]SQL Server does not exist or access denied. [Shared Memory]ConnectionOpen (Connect()). 如果您成功连接到了该服务器,就会出现下面的提示: 1> 此提示表示 osql 已启动。现在,您可以交互输入 Transact-SQL 语句,运行结果将出现在命令提示行上。
附:删除SQL的注册表信息、安装目录、相关服务 的批处理 安装报错 : 1、必须重新启动计算机才能安装 sql server。 regedit定位到HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager 位置 ,删除右边窗口PendingFileRenameOperations,重新运行即可。 2、MsiGetProductInfo 无法检索 Product Code 为“{95120000-00B9-0409-1000-0000000FF1CE}”;的包的 ProductVersion.错误代码: 1605. 把这个GUID的前段“95120000” 倒排成为 00002159 ,然后HKEY_Classes_Root\installer\UpgradeCodes里查找这个倒排的值, 并删除对应的父节点。 3、为 SQL Server 服务提供的指定凭据无效。若要继续操作,请为 SQL Server 服务提供有效的帐户和密码。 SQL Server代理 选 SYSTEMSQL Server database engine 选 NETWORK SERVICESQL Serveranalysis services 选 NETWORK SERVICESQL Serverreporting services 选 NETWORK SERVICESQL Serverintegration services 选 NETWORK SERVICE 不要设置密码。 4、 sql 2008性能计数器注册表配置单元一致性 安装失败 在正常电脑复制C:\WINDOWS\system32目录下的perfc009.dat、perfh009.dat、perfi009.dat(数字可能不同)到本机即可。 还原数据库: 备份集中的数据库备份与现有的 '' 数据库不同。自己新建同名数据库也失败。 解决方案:根节点--数据库右键--还原数据库--选择并勾选备份文件--这时候就有目标数据库供选择了。 卸载方法: 控制面板--卸载Microsoft SQL Server 2008--卸载与2008相关组件--删除磁盘里的安装文件(一般数据库默认安装在C盘) --清空注册表(①HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager中(看右边)找到PendingFileRenameOperations值; ②HKEY_CURRENT_USER\Software\Microsoft\Microsoft SQL Server 、 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server 、 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer ) 定位到一下项,删除SQL相关。 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData [ \S-1-5-18\Produc] Note:删除注册表有风险,最好提前备份。 工具删除 \Program Files\Microsoft SQL Server\ \Program Files (x86)\Microsoft SQL Server\ 以及安装包目录 将下方命令保存为bat,以管理员权限执行即可。 @Echo Off Echo By: CSDN.zxiaofan.cn Echo 请选择 N:退出 其他:删除SQL的注册表信息、安装目录、相关服务 Echo 【Note:注册表将会备份在D盘根目录,文件和服务删除操作不可逆!】 Echo. Echo. Set /p var=请选择: If /i %var%==N (Exit) set "pending=HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager" set "CURRENT_USER_SQL=HKEY_CURRENT_USER\Software\Microsoft\Microsoft SQL Server" set "LOCAL_MACHINE_SQL=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server" set "LOCAL_MACHINE_MSSQL=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\MSSQLServer" set "LOCAL_MACHINE_SQL2008=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server 2008 Redist" set "LOCAL_MACHINE_SQl10=HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server Native Client 10.0" ::备份注册表 ::reg export "%pending%" D:\PendingFile.reg ::reg export "%CURRENT_USER_SQL%" D:\CURRENT_USER_SQL.reg ::reg export "%LOCAL_MACHINE_SQL%" D:\LOCAL_MACHINE_SQL.reg ::reg export "%LOCAL_MACHINE_MSSQL%" D:\LOCAL_MACHINE_MSSQL.reg ::reg export "%LOCAL_MACHINE_SQL2008%" D:\LOCAL_MACHINE_SQL2008.reg ::reg export "%LOCAL_MACHINE_SQl10%" D:\LOCAL_MACHINE_SQl10.reg ::删除注册表 reg delete "%pending%" /v "PendingFileRenameOperations" /f reg delete "%CURRENT_USER_SQL%" /f reg delete "%LOCAL_MACHINE_SQL%" /f reg delete "%LOCAL_MACHINE_MSSQL%" /f reg delete "%LOCAL_MACHINE_SQL2008%" /f reg delete "%LOCAL_MACHINE_SQl10%" /f ::删除SQL目录 rd /s /q "C:\Program Files\Microsoft SQL Server" rd /s /q "D:\Program Files\Microsoft SQL Server" rd /s /q "C:\Program Files (x86)\Microsoft SQL Server" rd /s /q "D:\Program Files (x86)\Microsoft SQL Server" rd /s /q "C:\Program Files (x86)\Microsoft SQL Server Compact Edition" ::删除服务 sc delete MSSQLServerADHelper100 sc delete SQLWriter Pause
需求: 利用函数生成学生成绩表,成绩随机生成。 要求: 成绩调用函数实现(避免函数主体内多次执行相同方法,rand比较简单,遇到复杂的,还是采用调用比较好) 实现: 新建函数-函数(init_grade) BEGIN DECLARE core DOUBLE; # 50=<core<=100 set core =RAND()*50+50; RETURN core; END 新建函数-过程(insert_grade) BEGIN declare var int; set var=1; while var<loop_times do #调用init_grade()函数生成随机成绩 INSERT INTO `study`.`grades` (`id`, `math`, `english`, `java`, `C`)VALUES(var,init_grade(),init_grade(),init_grade(),init_grade()); set var=var+1; end while; END 执行insert_grade过程,输入循环次数即可。
如果想在数据库里插入大量数据,或者更新多个字段,一条条处理,无疑是噩梦。好在Navicat提供了相关的函数。 例:从【指定OrderId开始】将【loop_times个】BuyTradeNO字段更新为OrderId字段的值 方法: 1、数据库里选择函数,右键【新建函数】; 2、函数向导中选择【过程】,模式选择【IN】,点击完成; 3、在定义一栏添加函数体 BEGIN declare var int; set var=0; while var<loop_times do UPDATE `tableName` SET `BuyTradeNO`=order_id+'' WHERE (`OrderId`=order_id+''); set var=var+1; set order_id=order_id+1; end while; END;4、修改定义下的参数为【IN `loop_times` int,IN `order_id` int】 5、保存,点击运行,输入参数【1000,1508144000】,即从1508144000这个Orderid开始,将随后的1000个BuyTradeNO字段更新为OrderId字段的值。 PS:当然函数操作可以自定义修改,比如你要插入值:INSERT INTO `tableName` VALUES (order_id+''……);
Query Cache(QC) 缓存完整的Select结果,当查询命中该缓存,MySQL会立刻返回结果,跳过解析、优化和执行阶段。 1、如何判断缓存命中 缓存存放在一个引用表中,通过哈希值引用。哈希值包括查询本身、待查数据库、客户端协议版本等可能影响返回结果的信息。 注: 当表被lock tables锁住时,仍可以通过查询缓存返回数据。 任何字符不同(包括空格、注释)都会导致缓存的不命中。 不会被缓存:①查询语句包含不确定数据(如函数now()、current_date());②不同用户返回不同结果current_user()、connection_id());③包含自定义函数、存储函数、用户变量、临时表、mysql中的系统表、包含列级别权限的表等等。 a)“如果查询中包含一个不确定的函数,MySQL则不会检查查询缓存”? 错误。MySQL检查查询缓存时,仅仅检测SQL语句是否以sel(大小写不敏感)开头,还没有解析SQL语句。 b)“如果查询语句中包含任何不确定函数,则查询缓存中是找不到缓存结果的” 正解。即使执行了,结果也不会放在查询缓存中。 MySQL只要发现不能被缓存的部分,将禁止此查询被缓存。 如果查询中带有current_date,最好直接写死'2016-01-25', 缺点: 打开查询缓存会对读写操作带来额外的消耗; 读查询开始前必须检查是否命中; 如果读查询可被缓存但还未被缓存,会将其结果存入查询缓存(额外消耗); 写数据时,对应表所有缓存都失效,如果查询缓存非常大或碎片很多,将带来大量系统消耗。 查询缓存加锁排他。 2、查询缓存如何使用内存 查询缓存完全存储于内存中,MySQL自行管理这块内存。 服务器启动; 初始化查询缓存所需内存;(此内存池是一个完整的空闲块,大小为配置的查询缓存大小减去维护查询缓存数据结构所需空间<约40K>) 查询结果若需要缓存,MySQL从缓存池申请一个数据块用于存储,该块大于参数query_cache_min_res_unit,即使查询结果远小于query_cache_min_res_unit。【查询开始返回结果就分配空间,无法预估查询结果的大小,So无法为每个查询结果精确分配大小合适的缓存空间】 缓存时,MySQL优先选择尽可能小的数据块,若该块空间不够,将申请新的尽可能小的数据块;(亦可能选择较大的,此不深究);若该块有剩余,MySQL将其释放,并放入空闲部分。 Note: 分配数据块需先锁住空间块,再找到合适大小,so相对耗时,MySQL尽量避免。 碎片:若平均查询结果非常小,服务器并发地向两个连接返回结果。返回结果后MySQL回收剩余数据块时,发现回收的块小于query_cache_min_res_unit,不能直接在后续的内存块中分配使用,即产生碎片。 如何减少碎片呢? 完全避免是不可能的,选择合适的query_cache_min_res_unit可平衡每个数据块的大小和每次存储结果时内存申请的次数。(太小导致频繁申请,太大导致大量碎片)实际消耗(query_cache_size - Qcache_free_memory)除以Qcache_queries_in_cache计算单各查询的平均缓存大小。最糟糕时,任何两个数据块间都有一个非常小的空闲块,此时Qcache_free_blocks恰好达到Qcache_total_blocks / 2,碎片问题很严重。 可通过query_cache_limit限制可缓存的最大查询结果以便减少大查询结果的缓存,从而减少碎片。 3、何时应该使用查询缓存 缓存和失效都会带来额外的消耗,So当缓存带来的资源节约大于其本身的资源消耗才推荐使用。 命中率:Qcache_hits / (Qcache_hits+Com_select)(核心,但难判断且不直观) 消耗大量资源的查询;(如汇总计算count等;多表join再排序分页,查询消耗巨大但返回结果集小) 相关表update、delete、insert 比 select少很多; 数据的访问频率非常高,或者访问频率不高,但是它的生存周期很长。 命中和写入比率:Qcache_hits / Qcache_inserts(通常3:1即可,10:1更佳) 如何 分析和配置 查询缓存 show variables like '%query_cache%'; +------------------------------+---------+ | Variable_name | Value | +------------------------------+---------+ | have_query_cache | YES | | query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 599040 | | query_cache_type | ON | | query_cache_wlock_invalidate | OFF | +------------------------------+---------+ (1)query_cache_type有3个值 : a、0或off,代表关闭 b、1或on,代表开启 在on模式下,如果你不想使用缓存,需要通过sql_no_cache关键词来显示的指明, 如select sql_no_cache id,name from tableName; c、2 或demand,按需要是否开启缓存。 当sql语句中有SQL_CACHE关键词时才缓存, 如:select SQL_CACHE user_name from users where user_id = '100'; 无论哪种模式下,当sql中使用了mysql函数时,都不会缓存。 (2)query_cache_size表示分配给查询缓存的总内存大小,该值并非越大越好,需要结合实际情况设置。 (3)query_cache_limit 单次查询结果大于这个值,则不再缓存。该值默认是1048576,即1M。 (4)query_cache_min_res_unit 分配的最小缓存块大小,默认是4KB,设置该值大,对大数据查询有好处,但如果你的查询都是小数据查询,就容易造成内存碎片和浪费。 (5)query_cache_wlock_invaliate 默认为OFF,可以读取已锁定的表的缓存数据。 缓存功效实践: ①测试数据:MySQL自带数据库sakila.rental ②测试SQL: a)SELECT COUNT(*)FROM sakila.rental; b)SELECT rental_date FROM sakila.rental WHERE rental_date>'2005-08-20 21:35:58'; 时间对比如下: 耗时分别为:navicat和【黑屏】,黑屏时间保留两位小数(0.01 s) 耗时ms 开启缓存 (首次执行) 开启缓存 (多次平均) 关闭缓存 (首次执行) 关闭缓存 (多次平均) a 36【10】 0【0】 8【30】 3【10】 b 9【10】 1【0】 9【10】 2【0】 Note: 关闭缓存后重启MySQL:net stop/start mysql57 一定要先关闭缓存,不能在运行时设置参数关闭。 C:\ProgramData\MySQL\MySQL Server 5.7\my.ini文件:query_cache_type=0,query_cache_size=0 总结: 开启缓存是要消耗资源的; 多次执行相同查询,开启缓存效果更佳; 疑问: 关闭缓存后,首次执行虽比开启缓存节省时间,但依旧比多次平均执行耗时。 执行a语句时,黑屏总比navicat耗时? 关闭缓存后,第一次查询很慢,后面很快? ①缓存禁用了,第一次查时数据从硬盘加载到内存,再连续查速度变快。 由于内存有限,一会儿后数据从内存清除,当然再查就慢了。 ②禁用缓存,仅是禁止了SQL语句重新分析和数据读取,但如果有些表,索引已经打开或者加载到内存中,则在内存无其它冲突请求时仍然有效。 因此会速度快于第一次。 常用命令: 碎片整理:flush query cache【将所有查询缓存重新排序,并将所有空闲空间聚集起来】 清空缓存:reset query cache【加锁访问所有查询缓存,此期间其他连接无法访问查询缓存,从而导致服务器僵死,so尽量使查询缓存空间较小,控制服务器僵死在非常短的时间内】
一、MySQL优化原则: where子句哪些操作将导致MySQL引擎放弃索引而进行全表扫描? 对字段进行where num is null判断。【给num设置默认值0】 使用!=或<>。 使用or连接条件,如:where num=10 or num=20【select……union all select……替代】 使用in或not in,如:where num in(1,2,3)【连续数值可用where num between 1 and 3替代】 like‘李%’【考虑全文检索】 使用参数 1、【只要一行数据】或【只需要确认是否包含该数据】: LIMIT 1 查询时,你已经知道结果只会有一条,但因为可能需要去fetch游标,或是你会去检查返回的记录数。在这种情况下,加上 LIMIT 1 可以增加性能。这样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。 测试数据: MySQL自带数据库:sakila库rental表(数据量16W+),待查询数据在10467行。 SELECT * FROM rental WHERE rental_date ='2005-08-01 09:45:58' # 耗时0.018s # LIMIT 1 # 耗时0.002s ; 2、为搜索字段建索引 索引也有消耗,性别无需索引。 3、避免select * 读出数据越多,查询就越慢。如果数据库和web服务器独立,还会增加网络传输的负载。请求所有列再丢掉不需要的列?减少表结构的影响。 4、不要 ORDER BY RAND() 【随机挑选数据】【打乱返回数据】 看着很方便,但会让你的数据库性能指数级下降(MySQL不得不执行RAND()函数,耗CPU时间)。 SELECT rental_id FROM rental ORDER BY RAND() LIMIT 1; 这条要求似乎是针对就数据库,在MySQL 5.7.10测试时,仅第一次耗时达到几百毫秒,随后都是几毫秒。 5、用 ENUM 而不是 VARCHAR ENUM 类型是非常快和紧凑的。在实际上,其保存的是 TINYINT,但其外表上显示为字符串。如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是 VARCHAR。 测试数据: MySQL自带数据库:world库country表(暂略) 6、尽可能使用not null ①null是空值吗?不是,空值不占用空间,而数据库里的null占用空间,数据库里任何数跟NULL进行运算都是NULL, 判断值是否等于NULL,不能简单用=,而要用IS NULL关键字。 7、快速删除表 先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 8、拒绝大SQL 一条SQL叧能在一个CPU运算,5000+ QPS(每秒查询率Query Per Second)的高幵发中,1秒大SQL可能一条大SQL就把整个数据库堵死。拆解成多条简单SQL,简单SQL缓存命中率更高,减少锁表时间,特别是MyISAM,用上多CPU。 9、范围查找 同一字段:in代替or or效率:O(n);in效率:O(Log n),建议n小于200。 select*FROM rental WHERE return_date='2005-08-04 10:49:24'or return_date='2005-08-08 06:46:39'; # 8ms select*FROM rental WHERE return_date IN('2005-08-04 10:49:24','2005-08-08 06:46:39'); # 7ms 不同字段:union代替or select*FROM rental WHERE return_date='2005-08-04 10:49:24' or inventory_id=3157; select*FROM rental WHERE return_date='2005-08-04 10:49:24' UNION SELECT * FROM rental WHERE inventory_id=3157; 10、limit高效分页 Select * from table limit 10000,10; 改为: Select * from table WHERE id>=23423 limit 11; #10+1 (每页10条) 11、union all而不是union 若无需对结果进行去重,则用UNION ALL,UNION 要去重,有开销。【此处效果不是很明显,或许数据量不够16W+】 Select SQL_NO_CACHE *from study.rentalUNION SELECT * FROM sakila.rental; #0.74s Select SQL_NO_CACHE *from study.rental UNION ALL SELECT * FROM sakila.rental; #0.72s 12、同类型比较 数字比数字,字符比字符。 数值比字符:同时转为双精度再比对;字符比数值:字符整列转数值,无法使用索引。 参考资料: 提高mysql千万级大数据SQL查询优化30条经验(Mysql索引优化注意)
说是史上最全,或许有点吸引眼球的嫌疑了,但我在网上确实也没有找到更全的,这图也是我对照Java源码挨个分析,画出了较为常见的关系图,及其重要特性。 图中部分集合的使用事例可以参见我的github(点击访问),部分集合的源码分析可以参见我的CSDN的其他文章。如果需要下图的vsdx原图,请点击此处下载,Java集合关系图。 下图由于CSDN对图像尺寸有限制,可能有点模糊,可以点此访问http://img.blog.csdn.net/20160124221843905。
select查询结果是一个包含一或多条数据的结果集,类似数学里的集合,可进行交(intersect)、并(union)、差(minus)运算。 被操作的结果集需满足: 两结果集所含数据列的数量相等。 两结果集所含数据列的类型必须一一对应。 # 集合运算-练习 # 必须保证作集合运算的两个结果集的列数、数据类型一一对应 # # 1、union并运算 # 重复的数据只保留一个 SELECT id, java # 查询结果显示的名字是java FROM grades UNION SELECT id, math FROM student; # 2、minus差运算(MySQL不支持) # 从集合A中减去集合AB共有的。 # 语法格式:select 语句 minus select 语句【不支持】,但可借助子查询notin实现minus运算 SELECT id, math FROM student WHERE (id, math)NOT IN(SELECT id, math FROM grades); # 3、interset交运算(MySQL不支持) # 语法格式:select 语句 interset select 语句【不支持】,但可借助多表连接查询实现interset交运算 SELECT #即使是查交集,由于是多表查询,也【必须】指定同名字段的表名 s.id, s.math FROM student s JOIN grades g ON(s.id = g.id AND s.math = g.math) WHERE s.id >=1; # g.id 效果一样
子查询就是在查询语句中嵌套另一个查询,子查询支持多层嵌套。 子查询出现位置: form语句后当成数据表(实质是一个临时视图,so这种方法也被称为内视图); where条件后作为过滤条件的值。 Note: 子查询要用括号括起来; 把子查询作为数据表时(出现在from后),可为其起别名,作为前缀来限定数据列时,必须起别名【临时视图】。 作为过滤条件where时,将子查询放在比较运算符的右边,可增强查询的可读性; 作为过滤条件where时,单行子查询使用单行运算符,多行子查询使用多行运算符; 作为过滤条件where,如果子查询返回单行或单列值,则被当成一个标量值使用,即可使用单行记录比较符。 ①若子查询返回多个值,则需要使用in、any、all等关键字。 ②in可单独使用,效果同比较运算符的in,子查询返回的多个值将被当成一个值列表。 ③any、all可与>、>=、<、<=、<>、=等运算符结合使用。与any(all)结合表示大于其中任意一个值(所有值)。 ④=any 与 in 的作用相同。 作为where条件时,若子查询返回多行多列,则where应该有对应的数据列,并用圆括号将其组合起来。 # 子查询 # # 作为数据表(临时视图) SELECT * FROM (SELECT * FROM student) s WHERE s.id =6; # 作为where条件,若子查询返回单行或单列值,则被当成一个标量值使用 SELECT * FROM student WHERE grade >( # 此子查询返回单行数据 SELECT math FROM grades WHERE id =1 ); # 若子查询返回多个值,则需要使用in、any、all等关键字 SELECT * FROM student WHERE id IN( # in可单独使用,效果同比较运算符的in,子查询返回的多个值将被当成一个值列表。 SELECT id FROM grades WHERE id =5 OR id =6 ); # =any SELECT * FROM student WHERE # =any等价于in id =ANY(SELECT id FROM grades); # >=all SELECT * FROM student WHERE id >=ALL(SELECT id FROM grades); # 子查询返回多行多列,则where应该有对应的数据列,并用圆括号将其组合起来。 SELECT s.* FROM student s WHERE # 组合内的字段均须对应相等 (id, grade)=ANY(SELECT id, math FROM grades g);
组函数即《数据库函数》中提到的多行函数。每组记录作为整体计算,并返回一个结果,而不是每条记录返回一个结果。 常用的5个组函数:(以下expr均可以是变量、常量、数据列,无特别说明则数据类型可为任意类型) AVG([DISTINCT|all] expr):计算多行expr的平均值,数据类型:数值型。 DISTINCT:不计算重复值;all(省略时效果相同):需要计算重复值。 COUNT(*|[DISTINCT|all]expr,[expr...]):计算多行expr的总条数 *:统计该表内的记录行数;distinct:不计算重复值。 MAX(expr)、MIN(expr):计算多行expr的最值 SUM([DISTINCT|all] expr):多行expr的总和,数据类型:数值型 Note: distinct 和 * 不能同时使用。 组函数默认把所有记录当成一组,可使用group by显示分组。 group by当一列或多列的值完全相同时,才把这些记录当成一组。 group by 时 null也算一组,默认升序。 很多数据库分组有严格规则,如果查询列表使用了组函数或select使用group by分组,则要求出现在select的字段,要么用组函数包起来,要么必须出现在group by子句中。因为组函数或group by都将导致只有一条输出,而系统无法确定输出哪条。【但MySQL没有这些规则,会输出该组的第一条记录】 分组过滤having having子句后是条件表达式,满足该表达式的分组才会被选出来。 和where子句的区别: where子句仅过滤行,过滤组必须用having子句; where子句中不能使用组函数,having才可以; # 多行expr的平均值 SELECT AVG(ALL `level`) FROM student; #COUNT,多行expr的总条数,不考虑null SELECT # COUNT(*) # 表中记录条数 # COUNT(DISTINCT `level`) # level列有多少个不同的值 COUNT(DISTINCT age, `level`) # age、level不为null,且不同时相等的行数 # COUNT(DISTINCT*)# DISTINCT和*不能共用,会报异常 FROM student; # SUM(expr) SELECT # SUM(`level`) # `level`数值列的和 # expr是常量2,所以每行值都相同 # so近似理解为行数的2倍,即n*COUNT(*) # SUM(2) SUM(DISTINCT 2) #每行都是2,再去重,故值为2 FROM student; # MAX(expr)、Min() SELECT MAX(id) #id列的最大值9 FROM student; # 统计时对null的处理 SELECT AVG(IFNULL(`level`,0)) #不仅仅是简单的赋值,还增加了计算的列数 FROM student; # GROUP BY显式分组,多列参数同时相等才算一组 # null也算一组 SELECT NAME, # name显示的是当前组的第一个字段(与升降序无关) sex, `level`, COUNT(*) FROM student GROUP BY # `level` DESC; # 降序 LEVEL, sex; # having过滤组 SELECT sex, LEVEL, COUNT(*) FROM student GROUP BY sex # 按sex分组 HAVING # 要求不重复level数量大于等于3的组(此处结果只有女生组) count(DISTINCT `level`)>=3;
多表查询可理解为一个嵌套循环遍历。 多表连接查询有两种规范,较早的SQL92规范支持: 等值连接:连接条件要求两列值相等 非等值连接 广义笛卡尔积:没有任何连接条件(n*m条记录) 外连接 【MySQL 不支持 92规范的外连接】 外连接就是在外连接符所在的表中增加一个”万能行“,这行记录的所有数据都是null,而且该行可以与另一个表中所有不满足条件的记录匹配。即可以把另一表中的所有记录选出来,不管是否满足条件。 SQL99规范:提供可读性更好的多表连接语法,及更多类型的连接查询 交叉连接 自然连接 使用using子句的连接 使用on子句的连接 全外连接或左、右外连接。 此外,还有一种自连接。如果同一表中的不同记录存在主、外键约束关联,则需使用自连接查询。自连接只是连接的一种用法,不是一种连接类型,SQL92、SQL99都可使用。自连接本质是把一个表当成两个表用。 CREATE TABLE emp_table ( # 建立自关联数据表 id INT auto_increment PRIMARY KEY, uname VARCHAR(255), manager_id INT, FOREIGN KEY(manager_id) REFERENCES emp_table (id) ); INSERT INTO emp_table VALUES (NULL,'tang',NULL), # 唐僧是老大 (NULL,'sun',1), (NULL,'zhu',1), (NULL,'sha',1); 生成的表如下(取经团队): "id" "uname" "manager_id" "1" "tang" "" "2" "sun" "1" "3" "zhu" "1" "4" "sha" "1" 查询该表所有员工名及对应经理名,必须使用自连接查询。为一个表起两个别名,且查询中所有数据列都要加表别名前缀。 SELECT emp.id, emp.uname 员工名, mgr.uname 经理名 FROM emp_table emp, emp_table mgr WHERE emp.manager_id = mgr.id; 上述查询可查询出所有的员工名及对应的经理名。 "id" "员工名" "经理名" "2" "sun" "tang" "3" "zhu" "tang" "4" "sha" "tang" 1、SQL92的连接查询 多个数据表放在from之后,表间逗号隔开; 连接条件放where之后,与查询条件间用and逻辑运算符连接; 多个数据列具有相同列名时,需在同名列间用表(别)名前缀作为限制。 语法格式: SELECT col1, col2... FROM table1, table2... [WHERE join_condition ] 例子: # # SQL92规范 # # 查询学生信息及其Java成绩 SELECT s.*,java FROM student s, # 取别名 grades g WHERE #去掉where条件得到广义笛卡尔积 # s.id = g.id # 等值连接 s.id = g.id AND s.math > g.math # and连接过滤条件 # #MySQL不支持SQL92规范的外连接,以下报错 # SELECT s.*, g.java FROM student s, grades g WHERE s.id = g.id (*); # 右外连接 2、SQL99的连接查询 99和92原理基本相似,但99可读性更强: 多数据表显式用xxx join连接,而不是依次排在from后,from后只需放一个数据表; 提供了专门的连接条件子句,连接条件不再放在where后。 以下查询结果均是符合条件的行的笛卡尔积。 例子: # 交叉连接(crossjoin) # 效果就是92的广义笛卡尔积,无需任何连接条件 SELECT s.*,java# 99多连接查询的from后只有一个表名 FROM student s CROSS JOIN grades g; # 自然连接(natural join) # 表面看起来无条件,但是有连接条件的,以两个表中【所有同名列】作为连接条件; # 查询结果:为【所有同名列】数据相同行的笛卡尔积 # 如果两表没有同名列,则和交叉连接效果一样。 SELECT s.*,java FROM student s NATURAL JOIN grades g; # using子句连接 # 显式指定一列或多列的同名列作为连接条件; # using指定的列必须是同名列,否则报错[Err]1054-Unknown column 'java' in 'from clause' SELECT s.*,java FROM student s # join连接另一个表g JOIN grades g USING(id); # on子句连接 # SQL99语法在on子句指定连接条件 # 每个on子句只指定一个连接条件。即如果需要N表连接,则需要N-1个join...on对。 # 查询出符合条件的行的笛卡尔积 # on子句条件可以是等值、非等值的,完全可以替换SQL92的(非)等值连接 SELECT s.*,java FROM student s JOIN grades g # on指定连接条件 ON s.grade = g.java JOIN emp_table e ON s.id > e.id; # 最后结果集有2*2*4行 # 左、右、全外连接:left[outer]join、right[outer]join、full[outer]join # outer(外),默认省略; # on子句指定连接条件,(非)等值连接条件; # 99外连接与92恰好相反; # 左外连接:查询符合条件的结果集+left左边表中不符合条件的记录。 # 右外连接与左外相反; # 全外连接:额外输出两表中所有不满足条件的记录。 SELECT s.*,java FROM student s # RIGHT JOIN grades g # 右外连接 LEFT JOIN grades g # 左外连接 ON s.grade = g.java; # 截止5.7.10MySQl不支持全外连接full,但可通过左外、右外连接实现 SELECT s.*,java FROM student s LEFT JOIN grades g # 左外连接 ON s.grade = g.java UNION( SELECT s.*,java FROM student s RIGHT JOIN grades g # 右外连接 ON s.grade = g.java );
每个数据库都会在标准的SQL基础上扩展一些函数。函数一般会有一或多个输入(即参数),最终只返回一个值作为返回值。 SQL中的函数是独立的程序单元。调用时无需使用任何类、对象作为调用者。 多行函数(也称 聚集函数、分组函数) 对多行输入整体计算,最后只得到一个结果。主要完成一些统计功能,在大部分数据库中基本相同。 单行函数 对每行输入值单独计算,每行得到一个结果并返回。不同数据库单行函数差别很大。 MySQL中的单行函数具有如下特征: 可多个参数,但只返回一个值; 参数可以是变量、常量、数据列; 对每行单独起作用,每行返回一个结果; 使用单行函数可改变参数的数据类型; 支持嵌套使用(内层函数返回值是外层函数的参数)。 MySQL单行函数分类: MySQL数据库的数据类型大致分为数值型、字符型和日期时间型,MySQL提供了对应的函数。 数值函数、字符函数、日期和时间函数、转换函数、其他函数(位函数、流程控制函数、加密解密函数、信息函数)。 # 表中name字段的字符长度(输出行数与表的行数相同) SELECT CHAR_LENGTH(NAME) FROM student; # 嵌套使用 SELECT SIN(CHAR_LENGTH(NAME)) FROM student; # 为指定日期添加一定的时间 SELECT DATE_ADD( '2016-02-28', # INTERVAL是关键字,后跟一个值和单位 INTERVAL 2 DAY #输出2016-03-01 ); # 更简便的增加指定时间 SELECT ADDDATE('2015-01-18',3); SELECT # CURTIME(); # 获取当前时间,如17:52:55 # 获取当前日期,如2016-01-19 CURDATE(); # MD5加密 SELECT MD5('hello'); # #处理NULL的函数 # SELECT # IFNULL(id,'expr') # id为null,则返回expr # NULLIF(grade, math) # grade和math相等则返回null,否则返回grade ISNULL(id) # 为null则返回0(true),否则返回1(false) FROM student; SELECT # 若expr1(id)为true、不等于0且不等于null,则返回expr2,否则返回expr3 #此处id不是布尔值,只要非空即为true IF(id,'expr2','expr3') FROM student; # # case流程控制函数 # 两种用法 # SELECT NAME, CASE id WHEN 1 THEN 'is 1' WHEN 2 THEN 'is 2' ELSE '>2' END AS `level` FROM student; # case语法2更灵活 # when condition(返回boolean值的表达式) SELECT NAME, CASE # id WHEN id <=5 THEN 'small' WHEN id <10 THEN 'big' ELSE 'null' END AS `level` FROM student;
1、语法格式 select语句的功能就是查询数据,在SQL语句中功能最丰富,可单表查询、多表连接查询、子查询。 SELECT NAME, grade FROM student WHERE grade >80; 数据源student可以是表、视图等;select后列表用于确定选择哪些列(* 即所有列),where确定选择哪些行(无则选出所有行)。 select语句中可使用算术运算符(+、-、*、/)形成算术表达式,用于数值型、日期型的数据列、变量、常量;运算符可在两列间进行运算。 运算符优先级同Java。 SELECT * FROM student WHERE age *3> grade; 2、字符串连接concat MySQL没有提供字符串连接运算符,即无法使用加号(+)将字符串常量、变量、列连接起来。可使用concat。 SELECT CONCAT(NAME,'-', age) # -记得加单引号 FROM student WHERE age *4> grade; 算术运算符使用null,将导致整个算术表达式的值为null 字符串连接运算使用null,导致连接后的结果也是null。(如将上面的连接语句中的age改为null,则返回的结果与之前行数依旧相同,但值全为null) 3、别名as 为数据列或表达式起别名时,别名紧跟数据列,中间以空格或关键字as隔开。 SELECT NAME AS alias FROM student WHERE age >20; 别名使用特殊字符(如空格),或需要强制大小写敏感,都可通过别名加双引号来实现。 SELECT NAME AS "My'Name" …… #别名中有单引号时必须使用双引号,其余可使用单引号。 为多列起别名 SELECT NAME AS 'My Name', age AS 年龄 为表其别名(语法相同) 连接符、别名共用 SELECT NAME AS'My Name', CONCAT(grade,'-',math) grade1 FROM student WHERE age >20; 4、dual虚表 selec、where子句可都不出现列名 SELECT 1+2 FROM student WHERE1<3; where总为true,selec仅仅选择了常量,SQL将其当成一列,student表中有多少条记录,该常量就出现多少次。 选择常量时,指定数据表没有太大的意义,so MySQL扩展语法,允许select后没有from子句 SELECT 1+2; # 此时,返回结果只有一列,而不是表中有多少记录返回多少列了。 Oracle提供名为dual的虚表(没有任何意义,仅相当于from后的占位符),经测试(select version();)MySQL至少从5.1.58开始就已支持。 5、去重distinct select默认选出所有符合条件的记录,即使有两行一模一样。 SELECT DISTINCT NAME, age FROM student; distinct紧跟select; 上述语句选出name、age不同时相同的记录。 6、比较运算符 值相等 单等号= 不相等 <> 赋值 冒号等号:= ex1 between ex2 and ex3 表示ex2 =< ex1 <= ex2 ex in(ex1,ex2)类似枚举,ex的值必须是括号里任一个表达式的值 like 字符串匹配,like后的字符串支持通配符 is null 要求指定值为null 使用注意: ex1 between ex2 and ex3 ex2必须小于等于ex3,否则返回值为null; ex2和ex3可以是常量、变量,甚至是列名。 SELECT * FROM student WHERE # 查询age小于等于20,grade大于等于20的数据。 20 BETWEEN age AND grade; ex in(ex1,ex2) in括号内的值亦可常量、变量、列名。 SELECT * FROM student WHERE # 查询grade或math为88的所有记录。 88IN(grade, math); like 模糊查询 SQL语句两个通配符:下划线_(一个任意字符)和百分号%(任意多个字符) SELECT * FROM student WHERE NAME LIKE '小%'; #以“小”开头的所有记录 # NAME LIKE '小_'; # 以“小”开头的两个字符的所有记录 转义(查询条件需要下划线或百分号) MySQL使用反斜线(\)转义字符,LIKE '小\_',表示查询“小_” 标准SQL没有提供反斜线的转义字符,而是使用关键字escape显示转义。 LIKE '\_%' ESCAPE '\'; # 但MySQL不支持这种用法 is null 判断值为空,不要用=null,因为SQL中null=null返回null。 SELECT * FROM student WHERE # 查询id为空的记录 id ISNULL; 如将上面的is null改为=null,返回的结果将为空。 7、逻辑运算符 and、or、not WHERE NAME LIKE'__'AND id >6; # name为两个字符且id>6的记录 WHERE NOT NAME LIKE'__'; # name不是两个字符的记录 8、比较运算符、逻辑运算符优先级 所有比较运算符 > not > and> or 括号的优先级最高。 9、排序 查询结果默认按照插入顺序排列。 可用order by按值大小排列。 order by默认升序(asc),desc可改为降序。 SELECT * FROM student ORDERBY #按age升序排列,当age相同时,按grade降序排列。 age, grade DESC; 多列排序,则每列的asc、desc必须单独指定。 第一排序列为首排序列,仅当第一列值相同时,第二排序列才起作用。
计算机数据库中的视图是一个虚拟表,其内容由查询定义; 不是数据表,因为它不能存储数据; 只是一个或多个数据表中数据的逻辑显示。 本质:一条被命名的SQL语句。 视图好处: 对机密数据提供安全保护,限制对数据的访问; 简化复杂的查询; 提供数据的独立性; 分解复杂的查询需求,创建多个视图获取数据。将视图联合起来就能得到需要的结果了。 提供对相同数据的不同显示。 语法: CREATE OR REPLACE VIEW view1 AS subquery; #subquery为查询语句 建立好的视图与普通数据表没什么区别了,增删改查均可以。但通常我们不允许改变视图的数据。 MySQL等大部分数据库在创建视图时加入with check option语句,表示创建的视图不允许修改。(此时若修改视图数据,SQL不报错,但无法真正修改) Oracle使用with read only。 删除视图: drop view 视图名; 视图缩减业务逻辑 视图用来隐藏复杂的业务逻辑,从join连接查询产生一个view。先使用视图完成一定的逻辑,再在视图的基础上完成另外的逻辑。 通常,视图完成的逻辑都是相对比较基础的逻辑。 Note: 尽量使用视图完成读操作; 对视图的修改,也是对基表的修改,会即时生效; 删除视图时,不会销毁实体表内的数据; 如果做的是外部接口,一个数据库多个应用,针对每一个应用,采用不同的视图接口。
DML语句用于操作数据表的数据,如:插入、修改、删除。insert into、update和delete from三个命令组成。 1、insert into INSERT INTO `grade` (`id`, `math`)VALUES('1','83'); 若省略表名后的括号及括号内的列名列表,默认将为所有列插入值。 若某列值不确定,则为该列分配一个null值。INSERT INTO `grade` (`id`, `math`) VALUES (NULL , '83'); 标准SQL语句,每次只能插入一条数据。 带子查询的插入语句(一次可插入多条记录):选择的源表、带插入的目标表的列数、数据类型匹配即可。 将student表中所有的grade字段全部对应插入grades表中的math字段。 INSERT INTO grades (math)SELECT grade FROM student 将student表中所有的grade、level字段全部对应插入grades表中的math、java字段。 INSERT INTO grades (math,java)SELECT grade, `level` FROM student MySQL扩展语法:允许一次插入多条记录。 INSERT INTO student (NAME, age) VALUES ('Mary',21), ('Jack',25); 2、update语句 UPDATE student SET `name` ='Mary', `age` ='21' WHERE id =9; update一次可修改多条、多列记录。 where语句是一个条件表达式,没有where语句意味着where表达式的值总是true,即表中所有记录都会被修改。 3、delete from语句 总是整行整行的删除,可一次删除多行。同样,没有where语句,竟会删除表内所有记录。 DELETEFROM student WHERE NAME ='Jack' Note: 主表被从表参照时,主表记录不能被删除。 先删除对应从表记录,才能删除主表记录; 或定义外键约束时定义了主从表间的级联删除on delete cascade; 或使用on delete set null 指定当主表记录被删除时,对应从表记录设为null。
create table [模式名]表名 ( # 列定义 ) 例:向study数据库中插入表person CREATE TABLE IF NOT EXISTS study.person ( id INT(11)NOTNULL AUTO_INCREMENT, username VARCHAR(20)NOTNULL, age INT, grade DOUBLE, addTime DATE, PRIMARYKEY(id) ) ENGINE = MyISAM DEFAULT CHARSET = UTF8; 当没有在study数据库时,可以用下列2种方法 USE study; CREATE TABLE IF NOT EXISTS person1 ( id INT(11)NOTNULL AUTO_INCREMENT, PRIMARYKEY(id) ) ENGINE = MyISAM DEFAULT CHARSET = UTF8; CREATE TABLE IF NOT EXISTS study.person ( id INT(11)NOTNULL AUTO_INCREMENT, PRIMARYKEY(id) ) ENGINE = MyISAM DEFAULT CHARSET = UTF8; MyBatis动态建表-SR <update id="createUserWorkflowTableInt" parameterType="com.xhuoaservice.model.vo.UserWorkflowVo"> create table If Not Exists ${userName}( localId int(10) NOT NULL AUTO_INCREMENT, id varchar(10) , memId varchar(10), nodeId varchar(10), docId varchar(10), createDate datetime, workStatus varchar(1), handleState varchar(10), bustleNum int(2), isFollow varchar(1), modifyTime timestamp NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (localId) ) </update> 以上是常见建表语句,只是创建空表。使用子查询建表语句,则可以在建表时插入数据。 create table [模式名.]表名 # 创建StuCopy表,该表和student完全相同 CREATE TABLE StuCopy AS SELECT * FROM student;
约束: 在表上强制执行的数据校验规则,保证数据库里数据的完整性。当表中数据相互依赖时,保证相关数据不被删除。 也是数据库对象,也有自己的名字。 约束通常无法修改。 大部分数据库支持一下5种约束: NOT NULL:非空约束,指定某列不能为空; UNIQUE:唯一约束,指定某列或几列组合的数据不能重复; PRIMARY KEY:主键,指定该列的值可以唯一地标识该条记录; FOREIGN KEY:外键,指定改行记录从属于主表中的一条记录,主要用于保证参照完整性; CHECK:检查,指定一个布尔表达式,用于指定对应列的值必须满足该表达式。 MySQL不支持CHECK约束(可以使用,但没有任何作用)。 约束分类: ①单列约束;②多列约束(可约束多个数据列)。 约束时机: 建表同时为相应数据列指定约束; 建表后创建,以修改表的方式增加约束。 列级约束语法、表级约束语法。 Note: MySQL使用information_schema数据库的TABLE_CONSTRAINTS表保存该数据库实例的所有约束信息,可通过该表查询。 SQL中的null: 不区分大小写;所有类型均可以为null(包括int、boolean);空字符串和0不等于null。 1、NOT NULL:列级 有not null当然也有null。 ①建表指定: CREATE TABLE person ( id INT NOT NULL, age VARCHAR(2)NULL ) ②alter修改表时增删: ALTER TABLE person #增加非空约束 MODIFY username VARCHAR(10)NO TNULL, #取消非空约束 MODIFY age INT NULL, #取消非空约束,并指定默认值 MODIFY grade INT DEFAULT 99 NULL; 2、UNIQUE:列级、表级 列不可以出现重复值,null除外(数据库中null不等于null)。 单列:使用列级约束语法【第一约束名即为字段名,其他加序号,如id_2】 CREATE TABLE unique_test (id INT UNIQUE); 多列、自行指定约束名:使用表级约束语法【格式:constraint 约束名 约束定义】 ①建表时-分别建立唯一约束: CREATE TABLE unique_test ( #非空约束 id INT NOT NULL, uname VARCHAR(25), pwd VARCHAR(25), #表级约束语法建立唯一约束 UNIQUE(uname), #表级约束语法建立唯一约束,并指定约束名uni CONSTRAINT uni UNIQUE(pwd) ); ②建表时-列组合建立唯一约束: CREATE TABLE unique_test ( #非空约束 id INTNOTNULL, uname VARCHAR(25), pwd VARCHAR(25), #表级约束语法建立唯一约束,指定3列组合【不允许同时重复】 CONSTRAINT uni UNIQUE(id, uname, pwd) ); ③修改表时,add【表级约束语法】 ALTER TABLE unique_test ADD UNIQUE(id, uname, pwd); ④修改表时,modify【只能对单列采用列级约束】 ALTER TABLE unique_test MODIFY id INT UNIQUE; ⑤删除约束 大部分数据库使用“drop constraint 约束名”,MySql使用“drop index 约束名”。 ALTER TABLE unique_test DROP INDEX id; 3、PRIMARY KEY:列级、表级 主键约束等价于非空约束+唯一约束(不允许重复和null值)。 主键列的值可用于唯一标识表中的一条记录。 每个表最多一个主键,但主键约束可由多个数据列组合而成。 大部分数据库允许定义主键约束的名字,MySql出于保持与标准SQL的兼容性,允许命名,但这个名字没有任何作用,主键名依旧是PRIMARY。 主键约束与唯一约束语法非常相似,只是使用primary key。 ①建表时-列级约束语法: CREATE TABLE unique_test (id INT PRIMARY KEY); ②建表时-表级约束语法: CREATE TABLE unique_test ( id INT, uname VARCHAR(25), #指定主键名对MySql无效,依旧是primary CONSTRAINT myPrimary PRIMARY KEY(id), #多列组合主键约束 #CONSTRAINT myPrimary PRIMARY KEY(id, uname) ); ②删除主键约束 ALTER TABLE unique_test DROP PRIMARY KEY; ③修改表时,add【表级约束语法】 ALTER TABLE unique_test ADD PRIMARY KEY(id, uname, pwd); ④修改表时,modify【只能对单列采用列级约束】 ALTER TABLE unique_test MODIFY id INT PRIMARY KEY; 自增长【整型】:auto_increment CREATETABLE auto_inc_test ( id intauto_incrementPRIMARYKEY, uname VARCHAR(25) ); 4、FOREIGN KEY:列级、表级 外键约束:保证一个或两个数据表之间的参照完整性,构建于一个表的两个字段或两个表的两个字段间的参照关系; 外键确保两相关字段的参照关系:子(从)表外键列的值必须在被参照列的值范围内,或为空(可自行增加非空约束); 删除:欲删除(被从表记录参照的)主表记录,必须先删除从表里参照该记录的所有记录;或删主表记录时级联删除从表中所有参照该记录的从表记录; 外键只能参照主表主键列或唯一键列; 一个表可有多个外键; MySql也会为外键约束列建立索引; 用于实体间一对多<多的一端增加外键列>、一对一<任一方增加外键列,再增加唯一约束>的关联关系(多对多需额外增加一个连接表记录其关系); ①列级约束语法:REFERENCES 【出于保持与标准SQL的兼容性,实际无效,只能使用表级约束语法】 #为保证从表参照的主表存在,应先建主表 CREATE TABLE teacher_table ( id INT auto_increment, uname VARCHAR(25), PRIMARY KEY(id) ); CREATE TABLE student_table ( stu_id INT auto_increment PRIMARY KEY, stu_name VARCHAR(25), java_teacher INTREFERENCES teacher_table (id) ); ②表级约束语法: #为保证从表参照的主表存在,应先建主表 CREATE TABLE teacher_table ( id INT auto_increment, uname VARCHAR (25), PRIMARY KEY (id) ); CREATE TABLE student_table ( stu_id INT auto_increment PRIMARY KEY, stu_name VARCHAR(25), java_teacher INT, #constraint指定外键约束名 CONSTRAINT stu_tea_fk FOREIGN KEY(java_teacher)REFERENCES teacher_table (id) ); MySql外键约束名默认为:table_name_ibfk_n,table_name是从表表名,n是从1开始的正数。 #为保证从表参照的主表存在,应先建主表 CREATE TABLE teacher_table ( id INT auto_increment, uname VARCHAR(25), #必须以列组合建立主键 PRIMARYKEY(id, uname) ); CREATE TABLE student_table ( stu_id INT auto_increment PRIMARY KEY, stu_name VARCHAR(25), java_teacher INT, #CONSTRAINT指定外键约束名 CONSTRAINT stu_tea_fk FOREIGN KEY(java_teacher, stu_name)REFERENCES teacher_table (id, uname) ); ③删除外键约束:DROP ALTER TABLE student_table DROP FOREIGN KEY stu_tea_fk; #stu_tea_fk为外键名 ④增加外键约束:ADD FOREIGN KEY ALTER TABLE student_table ADD FOREIGN KEY(stu_id, stu_name) REFERENCES teacher_table (id, uname); ⑤自关联:参照自身 CREATE TABLE student_table ( stu_id INT auto_increment PRIMARYKEY, stu_name VARCHAR(25), refer_id INT, CONSTRAINT stu_tea_fk FOREIGNKEY(refer_id)REFERENCES student_table (stu_id) ); ⑥级联删除: ON DELETE CASCADE:删除主表时,从表记录全部级联删除; ON DELETE SET NULL:删除主表时,参照该主表记录的外键设为null。 #为保证从表参照的主表存在,应先建主表 CREATE TABLE teacher_table ( id INT auto_increment, uname VARCHAR(25), #必须以列组合建立主键 PRIMARY KEY(id, uname) ); CREATE TABLE student_table ( stu_id INT auto_increment PRIMARY KEY, stu_name VARCHAR(25), #CONSTRAINT指定外键约束名 #定义级联删除 #亦可onDELETESETNULL CONSTRAINT stu_tea_fk FOREIGNKEY(stu_id)REFERENCES teacher_table (id)ONDELETECASCADE ); 5、CHECK【MySql无效】 CREATE TABLE check_test ( id INT auto_increment, uname VARCHAR(25), PRIMARY KEY(id), CHECK(id >10) # 要求id>10 ); 可借助MySql的触发机制来实现CHENCK约束,甚至更复杂的完整性约束。
SQL(Structured Query Language 结构化查询语言) 标准SQL语句类型 查询语句 select关键字,SQL语句中最复杂但功能最丰富 DML 数据操作语言 insert、update、delete DDL 数据定义语言 create(创建)、alter(修改)、drop(删除)、truncate(快速删表) 数据库对象 DCL 数据控制语言 grant、revoke;【授权、回收用户权限,通常无需程序员操作】 事物控制语句 commit、rollback、savepoint 1、SQL语句关键字不区分大小写; 2、SQL语句字符串:单引号 3、标识符:以字母开头,包括字母、数字和三个特殊符号(#_$),建议多单词下划线连缀; DDL语句:操作数据库对象的语句 4、常见数据库对象: table表:列是字段,行是记录 数据字典:就是系统表,存放数据库相关信息的表,程序员一般不用手动改 constraint约束:数据检验规则 view视图:数据的逻辑显示,不储存数据 index索引:为了提高数据查询的性能 function函数:完成特定计算,具有返回值 procedure存储过程:完成特定的业务处理,没有返回值 trigger触发器:相当于一个事件监听器 5、《MySql建表》 6、修改表结构ALTER 增加列定义、修改列定义、删除列、重命名列。 增加列定义ADD:(新增不存在的) ALTER TABLE student ADD new_id INT; #新增一列可以省略圆括号 ALTER TABLE student ADD( #指定初始值为dog love VARCHAR(255)DEFAULT'dog', noLove VARCHAR(255) ) 修改列定义MODIFY:(修改已存在的) # (MySql不支持一个modify命令修改多个列定义,可在ALTER TABLE后使用多个MODIFY) ALTER TABLE student MODIFY new_id VARCHAR(255), MODIFY noLove INT # 同样,若表中已有数据,修改列定义很容易失败。 删除列定义DROP: ALTER TABLE student DROP noLove 以上是标准SQL语法,对所有数据库均适用,NySQL还提供了两种特殊语法: 重命名数据表RENAME: ALTER TABLE student RENAME TO newstu 完全改变列定义CHANGE: # 同样,若表中已有数据,修改列定义很容易失败。 ALTER TABLE student CHANGE new_id idid INT 7、删除表DROP DROP TABLE personnew 表结构被删除,表对象不在存在;表中所有数据被删除;表相关的索引、约束被删除。 8、表TRUNCATE TRUNCATE table 截断表,清空所有数据但保留表结构;只能一次性删除所有数据。 MySQL对其处理较特殊,若使用非InnoDB机制,truncate比delete快; 对于InnoDB机制,在5.0.3之前,truncate和delete一样;在5.0.3之后,truncate比delete高效,但如果表被外键约束参照,则变为delete操作;5.1.3后,快速truncate总是可用,即更高效。 1、数据库中不能重复的字段 主键、唯一键: PRIMARY KEY (`KeyID`), UNIQUE KEY `UK_BaseDataDetailed201512_` (`PayNum`),
MySQL是一种开放源代码的关系型数据库管理系统(RDBMS),MySQL数据库系统使用最常用的数据库管理语言--结构化查询语言(SQL)进行数据库管理。 查看方法:show engines; MySQL5.5以后默认使用InnoDB存储引擎,其中InnoDB和BDB提供事务安全表,其它存储引擎都是非事务安全表。 若要修改默认引擎,可以修改配置文件中的default-storage-engine。可以通过:show variables like 'default_storage_engine';查看当前数据库到默认引擎。命令:show engines和show variables like 'have%'可以列出当前数据库所支持的引擎。其中Value显示为disabled的记录表示数据库支持此引擎,而在数据库启动时被禁用。 在MySQL5.1以后,INFORMATION_SCHEMA数据库中存在一个ENGINES的表,它提供的信息与show engines;语句完全一样,可以使用下面语句来查询哪些存储引擎支持事物处理:select engine from information_chema.engines where transactions = 'yes'; 可以通过engine关键字在创建或修改数据库时指定所使用到引擎。 主要存储引擎:MyISAM、InnoDB、MEMORY和MERGE介绍: 在创建表到时候通过engine=...或type=...来指定所要使用到引擎。show table status from DBname来查看指定表的引擎。 1. MyISAM:这种引擎是mysql最早提供的。又可以分为静态MyISAM、动态MyISAM 和压缩MyISAM三种: 静态MyISAM:如果数据表中的各数据列的长度都是预先固定好的,服务器将自动选择这种表类型。因为数据表中每一条记录所占用的空间都是一样的,所以这种表存取和更新的效率非常高。当数据受损时,恢复工作也比较容易。 动态MyISAM:如果数据表中出现varchar、xxxtext或xxxBLOB字段时,服务器将自动选择这种表类型。相对于静态MyISAM,这种表存储空间比较小,但由于每条记录的长度不一,所以多次修改数据后,数据表中的数据就可能离散的存储在内存中,进而导致执行效率下降。同时,内存中也可能会出现很多碎片。因此,这种类型的表要经常用optimize table 命令或优化工具来进行碎片整理。 压缩MyISAM:以上说到的两种类型的表都可以用myisamchk工具压缩。这种类型的表进一步减小了占用的存储,但是这种表压缩之后不能再被修改。另外,因为是压缩数据,所以这种表在读取时要先时行解压缩。 但是,不管是何种MyISAM表,目前它都不支持事务、行级锁和外键约束的功能。 2. MyISAM Merge引擎:这种类型是MyISAM类型的一种变种。合并表是将几个相同的MyISAM表合并为一个虚表。常应用于日志和数据仓库。 3. InnoDB:InnoDB表类型可以看作是对MyISAM的进一步更新产品,它提供了事务、行级锁机制和外键约束的功能。 4. memory(heap):这种类型的数据表只存在于内存中。它使用散列索引,所以数据的存取速度非常快。因为是存在于内存中,所以这种类型常应用于临时表中。 5. archive:这种类型只支持select 和 insert语句,而且不支持索引。常应用于日志记录和聚合分析方面。 6. CSV: 这种类型的存储引擎利用冒号分割值的格式将数据存在文本文件上。我们可以利用CSV存储引擎方便的对遵照CSV格式的数据进行导入导出或者与其他软件进行数据交换 当然MySql支持的表类型不止上面几种。 如何选择(存储引擎之间的优缺点) 来源:Mysql官网 tip:Byte、KB、MB、GB、TB、PB、EB、ZB、YB、DB、NB。 Feature MyISAM Memory InnoDB Archive NDB Storage limits 256TB RAM 64TB None 384EB Transactions No No Yes No Yes Locking granularity Table Table Row Row Row MVCC No No Yes No No Geospatial data type support Yes No Yes Yes Yes Geospatial indexing support Yes No Yes[a] No No B-tree indexes Yes Yes Yes No No T-tree indexes No No No No Yes Hash indexes No Yes No[b] No Yes Full-text search indexes Yes No Yes[c] No No Clustered indexes No No Yes No No Data caches No N/A Yes No Yes Index caches Yes N/A Yes No Yes Compressed data Yes[d] No Yes[e] Yes No Encrypted data[f] Yes Yes Yes Yes Yes Cluster database support No No No No Yes Replication support[g] Yes Yes Yes Yes Yes Foreign key support No No Yes No No Backup / point-in-time recovery[h] Yes Yes Yes Yes Yes Query cache support Yes Yes Yes Yes Yes Update statistics for data dictionary Yes Yes Yes Yes Yes [a] InnoDB support for geospatial indexing is available in MySQL 5.7.5 and higher. [b] InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature. [c] InnoDB support for FULLTEXT indexes is available in MySQL 5.6.4 and higher. [d] Compressed MyISAM tables are supported only when using the compressed row format. Tables using the compressed row format with MyISAM are read only. [e] Compressed InnoDB tables require the InnoDB Barracuda file format. [f] Implemented in the server (via encryption functions), rather than in the storage engine. [g] Implemented in the server, rather than in the storage engine. [h] Implemented in the server, rather than in the storage engine. 参考资料: MySQL存储引擎 浅谈MySql的存储引擎(表类型) https://dev.mysql.com/doc/refman/5.1/en/storage-engines.html 维基百科正式从MySQL迁移到MariaDB数据库
最近在看《疯狂Java讲义》的数据库篇,较为系统的复习一下数据库知识,相关笔记首写与个人有道云笔记,现发于博客,希望和大家一起分享交流。本分类暂只包含MySQL的相关基础知识,后期会逐渐添加数据库优化相关知识。如发现文章有任何问题或你有任何想法,欢迎交流探讨。 注:涉及到的书中观点,版权归原作者所有。
package stackAndQueue; import java.util.Stack; /** * 用栈来求解汉诺塔问题:HanoiStack【3】 * * 【问题描述】:将汉诺塔游戏(小压大)规则修改,不能从左(右)侧的塔直接移到右(左)侧,而是必须经过中间塔。 * * 求当塔有N层时,打印最优移动过程和最优移动步数。如N=2,记上层塔为1,下层为2.则打印:1:left->mid;1 * * 由于必须经过中间,实际动作只有4个:左L->中M,中->左,中->右R,右->中。 * * 原则:①小压大;②相邻不可逆(上一步是L->M,下一步绝不能是M->L) * * 非递归方法核心结论:1.第一步一定是L-M;2.为了走出最少步数,四个动作只可能有一个不违反上述两项原则。 * * 核心结论2证明:假设前一步是L->M(其他3种情况略) * * a.根据原则①,L->M不可能发生;b.根据原则②,M->L不可能;c.根据原则①,M->R或R->M仅一个达标。 * * So,每走一步只需考察四个步骤中哪个步骤达标,依次执行即可。 * * @author xiaofan */ public class HanoiStack { private enum Action { None, LToM, MToL, MToR, RToM }; static Action preAct = Action.None; // 上一步操作,最初什么移动操作都没有 final static int num = 4; // 汉诺塔层数 public static void main(String[] args) { int steps = transfer(num); System.out.println("It will move " + steps + " steps."); } private static int transfer(int n) { Stack<Integer> lS = new Stack<>(); // java7菱形用法,允许构造器后面省略范型。 Stack<Integer> mS = new Stack<>(); Stack<Integer> rS = new Stack<>(); lS.push(Integer.MAX_VALUE);// 栈底有个最大值,方便后续可以直接peek比较 mS.push(Integer.MAX_VALUE); rS.push(Integer.MAX_VALUE); for (int i = n; i > 0; i--) { lS.push(i);// 初始化待移动栈 } int step = 0; while (rS.size() < n + 1) {// n+1,因为rS.push(Integer.MAX_VALUE);等于n+1说明全部移动完成 step += move(Action.MToL, Action.LToM, lS, mS);// 第一步一定是LToM step += move(Action.LToM, Action.MToL, mS, lS);// 只可能有这4种操作 step += move(Action.MToR, Action.RToM, rS, mS); step += move(Action.RToM, Action.MToR, mS, rS); } return step; } /** * 实施移动操作. * * @param cantAct * 不能这样移动 * @param nowAct * 即将执行的操作 * @param fromStack * 起始栈 * @param toStack * 目标栈 * @return step(成功与否) */ private static int move(Action cantAct, Action nowAct, Stack<Integer> fromStack, Stack<Integer> toStack) { if (preAct != cantAct && toStack.peek() > fromStack.peek()) { toStack.push(fromStack.pop()); // 执行移动操作 System.out.println(toStack.peek() + ":" + nowAct); preAct = nowAct; // 更新“上一步动作” return 1; } return 0; } } 代码地址:https://github.com/zxiaofan/Algorithm/tree/master/src/stackAndQueue
package stackAndQueue; import java.util.Stack; import org.junit.Test; /** * 由两个栈组成的队列:TwoStackQueue【2】 * * 【编写一个类,用两个栈实现队列,支持队列的基本操作(add、poll、peek)】 * * 设计思路:栈-先进后出,队列-先进先出。用两个栈把顺序反过来。 * * stackPush只管进栈,stackPop只管出栈且【仅当】其为空时,将stackPush的元素【全部】转移到stackPop。 * * @author xiaofan */ public class TwoStackQueue { private Stack<Integer> stackPush; private Stack<Integer> stackPop; public TwoStackQueue() { this.stackPush = new Stack<Integer>(); this.stackPop = new Stack<Integer>(); } public void add(int e) { this.stackPush.push(e); } public int poll() { tranfer(); return this.stackPop.pop(); } public int peek() { tranfer(); return this.stackPop.peek(); } private void tranfer() { if (this.stackPop.empty()) { if (this.stackPush.isEmpty()) { // isEmpty是Stack继承的Vector的方法 throw new RuntimeException("Your queue is empty."); } while (!this.stackPush.empty()) { // empty是Stack自带的方法 this.stackPop.push(this.stackPush.pop()); } } } /////// 测试方法////// @Test public void test() { TwoStackQueue queue = new TwoStackQueue(); queue.add(1); queue.add(2); queue.add(3); queue.add(3); queue.add(4); System.out.println("peek:" + queue.peek()); while (true) { // 未重写empty方法,只能这样遍历 try { System.out.println(queue.poll()); } catch (Exception e) { break; } } TwoStackQueue queue1 = new TwoStackQueue(); queue1.peek(); // java.lang.RuntimeException: Your queue is empty. } } 代码地址:https://github.com/zxiaofan/Algorithm/tree/master/src/stackAndQueue