第二十九天-加强1-Junit&类加载&反射&Properties&BeanUtils&xml&动态代理&数据库【悟空教程】
第1天Junit&反射&Properties
Junit是Java语言编写单元测试框架,最直观的理解,就是取代java类中的main方法。Junit属于第三方工具,一般情况下需要导入jar包,而多数Java开发环境都集成了Junit。
创建“MyJunit”java项目,并创建“cn.com.javahelp_00_Junit”包
1. 编写测试类,简单理解Junit可以用于取代java的main方法
2. 在测试类方法上添加注解 @Test
3. @Test修饰的方法要求:public void 方法名() {…} ,方法名自定义建议test开头,没有参数。
4. 添加Eclipse中集成的Junit库,鼠标点击“@Test”,使用快捷键“ctrl + 1”,点击“Add Junit …”
结果
5. 使用:选中方法右键,执行当前方法;选中类名右键,执行类中所有方法(方法必须标记@Test)
6. 常用注解
@Test,用于修饰需要执行的方法
@Before,测试方法前执行的方法
@After,测试方法后执行的方法
7. 常见使用错误,如果没有添加“@Test”,使用“Junit Test”进行运行,将抛异常
class文件由类加载器装载后,在JVM中将形成一份描述class结构的元信息对象,通过该元信息对象Class可以获知class文件的结构信息:如构造函数,属性和方法等。
虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
简而言之:class文件被虚拟机加载在内存生产Class对象。
类加载器就是寻找类的字节码文件,并构造出类在JVM内部表示的对象组件。在Java中,类加载器把一个类装入JVM中,要经过以下步骤:
1、 加载:查找和导入class文件;
2、 链接:把类的二进制数据合并到JRE中;
3、 初始化:对类的静态变量,静态代码块执行初始化操作
在装载阶段,虚拟机需要完成以下3件事情
虚拟机规范中并没有准确说明二进制字节流应该从哪里获取以及怎样获取,这里可以通过定义自己的类加载器去控制字节流的获取方式。
虚拟机如果不检查输入的字节流,对其完全信任的话,很可能会因为载入了有害的字节流而导致系统奔溃。
准备阶段是正式为类变量分配并设置类变量初始值的阶段,这些内存都将在方法区中进行分配,需要说明的是:
这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中;这里所说的初始值“通常情况”是数据类型的零值,假如:
public static int value = 123;
value在准备阶段过后的初始值为0而不是123,而把value赋值的putstatic指令将在初始化阶段才会被执行
下列四种情况能够触发类的初始化
Java反射机制是在运行状态中,对指定的类,任意的方法或任意的字段进行操作,这种动态获取的信息以及动态调用对象方法的功能称为java语言的反射机制。
简而言之,反射就是:在运行时通过代码操作类。
想要操作类,就必须先获得该类的字节码对象Class。通过Class提供的方法对类进行进一步的解剖,从而实现各种操作。
在进行具体操作之前,大家需要先掌握类各个组成的专业描述
public class Bean {
private String id;
private String className;
public String getId() {
System.out.println("getId方法执行");
return id;
}
public void setId(String id) {
System.out.println("setId方法执行:" + id);
this.id = id;
}
//1 通过类型获得
// 语法:类名.class
// 应用场景:确定类型 等
Class clazz1 = Bean.class;
//2 通过实例对象获得
// 语法:变量.getClass()
// 应用场景:在方法内部通过参数获得类型 等
Bean bean = new Bean();
Class clazz2 = bean.getClass();
//3 通过字符串获得
// 语法:Class.forName("全限定类名")
// 应用场景:通过配置获得字符串 等
Class clazz3 = Class.forName("cn.com.javahelp_00_Bean.Bean");
//私有方法
private String show(){
System.out.println("私有方法执行");
return "Bean["+id+", "+ className +"]";
}
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i] +", ");
}
}
public class Bean {
private String id;
private String className;
public String description;
Properties 类表示了一个持久的属性集。Properties 可保存在流中或从流中加载。属性列表中每个键及其对应值都是一个字符串。
特点:
1、Map接口的子类,map中的方法都可以用。
2、该集合没有泛型。键值都是字符串。
3、它是一个可以持久化的属性集。键值可以存储到集合中,也可以存储到持久化的设备(硬盘、U盘、光盘)上。键值的来源也可以是持久化的设备。
4、有和流技术相结合的方法。
代码演示:
把集合中的数据,保存到指定的流所对应的文件中,参数commonts代表对描述信息
需求:使用Properties集合,完成把集合内容存储到IO流所对应文件中的操作
把指定流所对应的文件中的数据,读取出来,保存到Propertie集合中
需求:从属性集文件prop.properties 中取出数据,保存到集合中
注意:使用字符流FileReader就可以完成文件中的中文读取操作了
通过配置文件确定创建那个JavaBean对象的实例,并通过配置文件确定JavaBean实例中存放具体数据。
public class User {
private String uid;
private String username;
private String password;
public class Book {
private String bid;
private String title;
private String price;
id=xxx
className=cn.com.javahelp.domain.User
uid=u001
username=jack
password=1234
public class BeanConfig {
private String id;
private String className;
private Properties props = new Properties();
@Test
public void demo01() throws Exception{
//解析properties
BeanConfig beanConfig = new BeanConfig();
// bean解析
//1 解析bean.properties文件
//1.1 加载配置文件
Properties beanProps = new Properties();
beanProps.load(new InputStreamReader(new FileInputStream("bean.properties"), "UTF-8"));
//1.2 获得具体数据
String id = beanProps.getProperty("id");
String className = beanProps.getProperty("className");
//1.3将数据封装到BeanConfig
beanConfig.setId(id);
beanConfig.setClassName(className);
// data解析
//2 解析data.properties文件
//2.1 加载配置文件
Properties dataProps = new Properties();
dataProps.load(new InputStreamReader(new FileInputStream("data.properties"), "UTF-8"));
//2.2 将所有的数据存放到Properties对象中
for(String name : dataProps.stringPropertyNames()){
String value = dataProps.getProperty(name);
beanConfig.getProps().setProperty(name, value);
}
System.out.println(beanConfig);
}
@Test
public void demo02() throws Exception{
//模拟数据
BeanConfig beanConfig = new BeanConfig();
beanConfig.setId("xxx");
beanConfig.setClassName("cn.com.javahelp_02_demo.bean.User");
beanConfig.getProps().setProperty("uid", "u001");
beanConfig.getProps().setProperty("username", "jack");
beanConfig.getProps().setProperty("password", "1234");
//从beanConfig获得数据,然后创建对应JavaBean,并赋值
//1 获得需要创建的JavaBean字符串
String className = beanConfig.getClassName();
//2 字节码对象
Class clazz = Class.forName(className);
//3获得实例
Object obj = clazz.newInstance();
//4 执行字段对应setter方法进行赋值操作
for(String name : beanConfig.getProps().stringPropertyNames()){
String value = beanConfig.getProps().getProperty(name);
//执行setter方法,uid --> setUid
// 4.1 获得方法名称
String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
// 4.2 获得方法
Method method = clazz.getMethod(methodName, String.class);
// 4.3 执行方法
method.invoke(obj, value);
}
System.out.println(obj);
}
private BeanConfig getBean() throws Exception{
BeanConfig beanConfig = new BeanConfig();
// bean解析
//1 解析bean.properties文件
//1.1 加载配置文件
Properties beanProps = new Properties();
beanProps.load(new InputStreamReader(new FileInputStream("bean.properties"), "UTF-8"));
//1.2 获得具体数据
String id = beanProps.getProperty("id");
String className = beanProps.getProperty("className");
//1.3将数据封装到BeanConfig
beanConfig.setId(id);
beanConfig.setClassName(className);
// data解析
//2 解析data.properties文件
//2.1 加载配置文件
Properties dataProps = new Properties();
dataProps.load(new InputStreamReader(new FileInputStream("data.properties"), "UTF-8"));
//2.2 将所有的数据存放到Properties对象中
for(String name : dataProps.stringPropertyNames()){
String value = dataProps.getProperty(name);
beanConfig.getProps().setProperty(name, value);
}
return beanConfig;
}
第2天BeanUtils&XML入门&Dom4j解析
BeanUtils 是 Apache commons组件的成员之一,主要用于简化JavaBean封装数据的操作。它可以给JavaBean封装一个字符串数据,也可以将一个表单提交的所有数据封装到JavaBean中。
使用第三方工具,需要导入jar包:
BeanUtils工具常用工具类:BeanUtils,BeanUtils用于封装数据,常用API如下
方法 | 描述 | |
---|---|---|
BeanUtils对象 | populate(Object bean, Map<String,String[]> properties) | 将Map数据封装到指定Javabean中,一般用于将表单的所有数据封装到javabean |
setProperty(Object obj,String name,Object value) | 设置属性值,如果指定属性不存在,不抛异常 | |
getProperty(Object obj,String name) | 获得属性值,如果指定属性不存在,抛方法找不到异常 |
@Test
public void demo03() throws Exception{
Map<String, String[]> map = new HashMap<>();
map.put("id", new String[]{"u001"});
map.put("username", new String[]{"jack"});
map.put("pwd", new String[]{"1234"});
//将数据封装到javabean中
User user = new User();
BeanUtils.populate(user, map);
System.out.println(user);
/* 结果:
* User [id=u001, username=jack, pwd=1234]
*/
}
public class MyBeanUtils {
/**
* 将数据封装给JavaBean,支持时间类型转换
* @param user
* @param properties
*/
public static void populate0(Object user, Map<String,String[]> properties){
try {
// 封装数据
BeanUtils.populate(user, properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 高级封装,不需要new javabean
* @param beanClass
* @param properties
* @return
*/
public static Object populate1(Class beanClass, Map<String,String[]> properties){
try {
//1 使用反射创建实例
Object bean = beanClass.newInstance();
//3 封装数据
BeanUtils.populate(bean, properties);
return bean;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
XML全称为Extensible Markup Language,意思是可扩展的标记语言。
W3C在1998年2月发布1.0版本,2004年2月又发布1.1版本,但因为1.1版本不能向下兼容1.0版本,所以1.1没有人用。同时,在2004年2月W3C又发布了1.0版本的第三版。我们要学习的还是1.0版本!!!
标记,就是标签。例如:<a></a>
<?xml version="1.0" encoding="UTF-8"?>
<persons>
<person id="p001">
<name>张三</name>
</person>
<person id="p002">
<name>李四</name>
</person>
</persons>
类似于java代码
class Person{
String id;
String name;
}
@Test
public void test(){
HashSet<Person> persons = new HashSet<Person>();
persons.add( new Person("p001","张三") );
persons.add( new Person("p002","李四") );
}
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="user1" className="cn.com.javahelp_00_Bean.User">
<property name="id" value="u001"></property>
<property name="username" value="jack"></property>
<property name="pwd" value="1234"></property>
</bean>
<bean id="user2" className=" cn.com.javahelp_00_Bean.User">
<property name="id" value="u002"></property>
<property name="username" value="rose"></property>
<property name="pwd" value="5678"></property>
</bean>
</beans>
类似于java代码
class Bean{
private String id;
private String className;
private Properties prop;
//补全set\get方法
}
@Test
public void test(){
HashSet<Bean> beans = new HashSet<Bean>();
Bean b1 = new Bean();
b1.setId("user1");
b1.setClassName("cn.com.javahelp.domain.User");
Properties prop = new Properties();
prop.setProperties("id","u001");
prop.setProperties("username","jack");
prop.setProperties("pwd","1234");
b1.setProp( prop );
Bean b2 = new Bean();
b2.setId("user2");
b2.setClassName("cn.com.javahelp.domain.User");
Properties prop = new Properties();
prop.setProperties("id","u002");
prop.setProperties("username","rose");
prop.setProperties("pwd","5678");
b2.setProp( prop );
beans.add(b1);
beans.add(b2);
}
<?xml version="1.0" encoding="UTF-8"?>
1. 文档声明必须为<?xml开头,以?>结束;
2. 文档声明必须从文档的0行0列位置开始;
3. 文档声明只有2个属性:
a) versioin:指定XML文档版本。必须属性,因为我们不会选择1.1,只会选择1.0;
b) encoding:指定当前文档的编码。可选属性,常用值是utf-8;
<bean></bean>
1. 元素是XML文档中最重要的组成部分,
2. 普通元素的结构开始标签、元素体、结束标签组成。例如:<hello>大家好</hello>
3. 元素体:元素体可以是元素,也可以是文本,例如:<b><a>你好</a></b>
4. 空元素:空元素只有开始标签,而没有结束标签,但元素必须自己闭合,例如:<c/>
5. 元素命名:
a) 区分大小写
b) 不能使用空格,不能使用冒号:
c) 不建议以XML、xml、Xml开头
6. 格式化良好的XML文档,必须只有一个根元素。
<bean id=”” className=””>
1. 属性是元素的一部分,它必须出现在元素的开始标签中
2. 属性的定义格式:属性名=属性值,其中属性值必须使用单引或双引
3. 一个元素可以有0~N个属性,但一个元素中不能出现同名属性
4. 属性名不能使用空格、冒号等特殊字符,且必须以字母开头
XML的注释,以“<!--”开始,以“-->”结束。注释内容会被XML解析器忽略!
因为很多符号已经被XML文档结构所使用,所以在元素体或属性值中想使用这些符号就必须使用转义字符,例如:“<”、“>”、“’”、“””、“&”。
<![CDATA[
任意内容
]]>
当大量的转义字符出现在xml文档中时,会使xml文档的可读性大幅度降低。这时如果使用CDATA段就会好一些。
在CDATA段中出现的“<”、“>”、“””、“’”、“&”,都无需使用转义字符。这可以提高xml文档的可读性。
在CDATA段中不能包含“]]>”,即CDATA段的结束定界符。
在XML技术里,可以编写一个文档来约束一个XML文档的书写规范,这称之为XML约束。
常见的xml约束:DTD、Schema
目的:通过约束文档,可以编写出xml文档(具有固定格式,规定的格式)。
DTD(Document Type Definition),文档类型定义,用来约束XML文档。规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等。
开发中,我们很少自己编写DTD约束文档,通常情况我们都是通过框架提供的DTD约束文档,编写对应的XML文档。常见框架使用DTD约束有:struts2、hibernate等。
通过提供的DTD“bean.dtd”编写XML
<?xml version="1.0" encoding="UTF-8"?>
<!--
DTD教学实例文档。
模拟spring规范,如果开发人员需要在xml使用当前DTD约束,必须包括DOCTYPE。
格式如下:
<!DOCTYPE beans SYSTEM "bean.dtd">
-->
<!ELEMENT beans (bean*,import*) >
<!ELEMENT bean (property*)>
<!ELEMENT property (#PCDATA)>
<!ELEMENT import (#PCDATA)>
<!ATTLIST bean id CDATA #REQUIRED
className CDATA #REQUIRED
>
<!ATTLIST property name CDATA #REQUIRED
value CDATA #REQUIRED
>
<!ATTLIST import resource CDATA #REQUIRED>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans SYSTEM "bean.dtd">
<beans>
<bean id="" className=""></bean>
<bean id="" className="">
<property name="" value=""></property>
<property name="" value=""></property>
</bean>
<import resource=""></import>
<import resource=""></import>
</beans>
1. 内部DTD,在XML文档内部嵌入DTD,只对当前XML有效。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE beans [
... //具体的语法
]>
<beans>
</beans>
2. 外部DTD—本地DTD,DTD文档在本地系统上,公司内部自己项目使用。
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE beans SYSTEM "bean.dtd">
<beans>
</beans>
3. 外部DTD—公共DTD,DTD文档在网络上,一般都有框架提供。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN"
"http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
</beans>
定义元素语法:<!ELEMENT 元素名 元素描述>
元素名:自定义
元素描述包括:符号和数据类型
常见符号:? * + () | ,
常见类型:#PCDATA 表示内容是文本,不能是子标签
属性的语法:(attribute)
<!ATTLIST 元素名
属性名 属性类型 约束
属性名 属性类型 约束
...
>
元素名:属性必须是给元素添加,所有必须先确定元素名
属性名:自定义
属性类型:ID、CDATA、枚举 …
ID : ID类型的属性用来标识元素的唯一性
CDATA:文本类型
枚举:(e1 | e2 | ...) 多选一
约束:
#REQUIRED:说明属性是必须的;required
#IMPLIED:说明属性是可选的;implied
Schema是新的XML文档约束;
Schema要比DTD强大很多,是DTD 替代者;
Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml。
Schema 功能更强大,数据类型更完善
Schema 支持名称空间
与DTD一样,要求可以通过schema约束文档编写xml文档。常见框架使用schema的有:Spring等
通过提供“bean-schema.xsd”编写xml文档
<?xml version="1.0" encoding="UTF-8"?>
<!--
Schema教学实例文档。
模拟spring规范,如果开发人员需要在xml使用当前Schema约束,必须包括指定命名空间。
格式如下:
<beans xmlns="http://www.javahelp.com.cn/bean"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.javahelp.com.cn/bean bean-schema.xsd"
>
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.javahelp.com.cn/bean"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://www.javahelp.com.cn/bean"
elementFormDefault="qualified">
<!-- 声明根标签 -->
<element name="beans">
<complexType>
<choice minOccurs="0" maxOccurs="unbounded">
<element name="bean">
<complexType>
<sequence minOccurs="0" maxOccurs="unbounded">
<element name="property">
<complexType>
<attribute name="name" use="required"></attribute>
<attribute name="value" use="required"></attribute>
</complexType>
</element>
</sequence>
<attribute name="id" use="required"></attribute>
<attribute name="className" use="required"></attribute>
</complexType>
</element>
<element name="import">
<complexType>
<attribute name="resource" use="required"></attribute>
</complexType>
</element>
</choice>
</complexType>
</element>
</schema>
1. 步骤1:创建bean.xml,并将“bean-schema.xsd”拷贝到同级目录
2. 步骤2:从xsd文档中拷贝需要的“命名空间”
3. 完成xml内容编写
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.javahelp.com.cn/bean"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.javahelp.com.cn/bean bean-schema.xsd"
>
<bean id="" className=""></bean>
<bean id="" className="">
<property name="" value=""/>
<property name="" value=""/>
</bean>
<import resource=""/>
<import resource=""/>
</beans>
如果一个XML文档中使用多个Schema文件,而这些Schema文件中定义了相同名称的元素时就会出现名字冲突。这就像一个Java文件中使用了import java.util.*和import java.sql.*时,在使用Date类时,那么就不明确Date是哪个包下的Date了。
总之名称空间就是用来处理元素和属性的名称冲突问题,与Java中的包是同一用途。如果每个元素和属性都有自己的名称空间,那么就不会出现名字冲突问题,就像是每个类都有自己所在的包一样,那么类名就不会出现冲突。
当W3C提出Schema约束规范时,就提供“官方约束文档”。我们通过官方文档,必须“自定义schema 约束文档”,开发中“自定义文档”由框架编写者提供。我们提供“自定义文档”限定,编写出自己的xml文档。
默认命名空间:<xxx xmlns=””> ,使用<标签>
显式命名空间:<xxx xmlns:别名=””> , 使用<别名:标签>
<beans xmlns="http://www.javahelp.cn/bean"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.javahelp.com.cn/bean bean-schema.xsd"
>
xmlns:xsi=”…” 固定写法
表示是一个schema实例文档,就是被schema文档约束的xml文档。
xsi:schemaLocation=”名称 路径 名称 路径 名称 路径 …”
表示用于确定当前xml文档使用到的schema文档的位置。“名称 路径”是成对出现,与xmlns引用命名空间对应。
综合案例目的是:一个“命名空间”必须在schemaLocation中有一个成对描述信息(名称 路径)
当将数据存储在XML后,我们就希望通过程序获得XML的内容。如果我们使用Java基础所学习的IO知识是可以完成的,不过你需要非常繁琐的操作才可以完成,且开发中会遇到不同问题(只读、读写)。人们为不同问题提供不同的解析方式,并提供对应的解析器,方便开发人员操作XML。
1. DOM:要求解析器把整个XML文档装载到内存,并解析成一个Document对象。
a) 优点:元素与元素之间保留结构关系,故可以进行增删改查操作。
b) 缺点:XML文档过大,可能出现内存溢出显现。
2. SAX:是一种速度更快,更有效的方法。它逐行扫描文档,一边扫描一边解析。并以事件驱动的方式进行具体解析,每执行一行,都将触发对应的事件。(了解)
a) 优点:处理速度快,可以处理大文件
b) 缺点:只能读,逐行后将释放资源。
3. PULL:Android内置的XML解析方式,类似SAX。(了解)
XML DOM 将 整个XML文档加载到内存,生成一个DOM树,并获得一个Document对象,通过Document对象就可以对DOM进行操作
DOM中的核心概念就是节点,在XML文档中的元素、属性、文本等,在DOM中都是节点!
如果需要使用dom4j,必须导入jar包。
dom4j 必须使用核心类SaxReader加载xml文档获得Document,通过Document对象获得文档的根元素,然后就可以操作了。
常用API如下:
1. SaxReader对象
a) read(…) 加载执行xml文档
2. Document对象
a) getRootElement() 获得根元素
3. Element对象
a) elements(…) 获得指定名称的所有子元素。可以不指定名称
b) element(…) 获得指定名称第一个子元素。可以不指定名称
c) getName() 获得当前元素的元素名
d) attributeValue(…) 获得指定属性名的属性值
e) elementText(…) 获得指定名称子元素的文本值
f) getText() 获得当前元素的文本内容
@Test
public void demo01() throws Exception
//#1 获得document
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(new File("01.xml/data.xml"));
//#2 获得根元素<beans>
Element rootElement = document.getRootElement();
//#3 获得所有子元素。例如:<bean>
List<Element> allBeanElement = rootElement.elements();
//#4 遍历所有
for (Element beanElement : allBeanElement) {
// #5.1 打印元素名
String childEleName = beanElement.getName();
System.out.println(childEleName);
// #5.2 获得bean的id和className属性
String id = beanElement.attributeValue("id");
String className = beanElement.attributeValue("className");
System.out.print(id + " , ");
System.out.println(className);
//#6 获得所有<property>子元素
List<Element> allPropertyElement = beanElement.elements("property");
for (Element propElement : allPropertyElement) {
String name = propElement.attributeValue("name");
String value = propElement.attributeValue("value");
System.out.print("\t");
System.out.print(name + " , ");
System.out.println(value);
}
}
}
第3天XML案例&注解&类加载器&动态代理
提供一个工厂,可以创建指定的对象。创建的对象通过XML配置确定。
例如:
getBean(“id001”) 将获得 id001对应User实例对象,并为User对象封装了指定的数据。
我们可以多次调用 BeanFactory.getBean(“beanId”),将创建不同对象。
1. BeanFactory首先加载(解析) xml配置文件
2. 将每一个<bean>标签配置内容,封装到BeanConfig对象中
3. 最后将整个xml所有配置内容都封装一个容器,且此容器可以快速查询。将采用Map进行数据存储。
<beans>
<bean id="beanId01" className="cn.com.javahelp.domain.User">
<property name="uid" value="u001"></property>
<property name="username" value="jack"></property>
<property name="pwd" value="1234"></property>
</bean>
<bean id="beanId02" className="cn.com.javahelp.domain.Book">
<property name="bid" value="b001"></property>
<property name="title" value="标题"></property>
<property name="price" value="998"></property>
</bean>
</beans>
public class BeanConfig {
private String id;
private String className;
private Properties props = new Properties();
public class BeanFactory {
//1.1 提供Map存放 bean.xml配置文件中所有内容,且易于快速查询,使用Map<String,BeanConfig>
private static Map<String,BeanConfig> cache = new HashMap<String, BeanConfig>();
//1.2 解析xml,将数据添加到map中
static{
try {
//1) 加载xml文件,获得document
SAXReader saxReader = new SAXReader();
Document document = saxReader.read("bean.xml");
//2) 获得根元素<beans>
Element rootElement = document.getRootElement();
//3) 获得所有<bean>元素
List<Element> allBeanElement = rootElement.elements("bean");
for(Element beanElement : allBeanElement) {
//4) 获得id和className属性
String id = beanElement.attributeValue("id");
String className = beanElement.attributeValue("className");
/**#1 创建BeanConfig, 并封装id和className、*/
BeanConfig beanConfig = new BeanConfig();
beanConfig.setId(id);
beanConfig.setClassName(className);
//5) 获得<property>子标签
List<Element> allPropertyElement = beanElement.elements("property");
for(Element propElement : allPropertyElement) {
//6) 获得name和value属性
String name = propElement.attributeValue("name");
String value = propElement.attributeValue("value");
/**#2 将 name和value 封装到 BeanConfig.props中*/
beanConfig.getProps().setProperty(name, value);
}
/**#3 将封装好的BeanConfig,添加到Map中*/
cache.put(id, beanConfig);
}
System.out.println("数据初始化成功:" + cache);
} catch (Exception e) {
//将编译时转换成运行时异常
throw new RuntimeException(e);
}
}
/**
* 通过指定 bean id 获得具体实例对象
* @param beanId
* @return
*/
public static Object getBean(String beanId){
//1 通过beanId 从Map中获得对应BeanConfig
BeanConfig beanConfig = cache.get(beanId);
if(beanConfig == null){
throw new RuntimeException("获得对象["+beanId+"]不存在");
}
try {
//2 通过 beanConfig.className 创建实例对象
String className = beanConfig.getClassName();
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
//3 循环遍历 BeanConfig.props ,使用BeanUtils进行数据封装
for(String name : beanConfig.getProps().stringPropertyNames()){
String value = beanConfig.getProps().getProperty(name);
//使用BeanUtils封装数据
BeanUtils.setProperty(obj, name, value);
}
return obj;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Test
public void demo01(){
User user = (User)BeanFactory.getBean("id001");
System.out.println(user);
Book book = (Book)BeanFactory.getBean("id002");
System.out.println(book);
}
1. 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override
2. 代码分析:通过代码里标识注解,对代码进行分析,从而达到取代xml目的。
3. 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容
1. @Deprecated 表示被修饰的方法已经过时。过时的方法不建议使用,但仍可以使用。
2. @Override JDK5.0表示复写父类的方法;jdk6.0 还可以表示实现接口的方法
3. @SuppressWarnings 表示抑制警告,被修饰的类或方法如果存在编译警告,将被编译器忽略
deprecation ,或略过时
rawtypes ,忽略类型安全
unused , 忽略不使用
unchecked ,忽略安全检查
null,忽略空指针
all,忽略所有
//#1 方法过期
class Parent1_1{
@Deprecated
public void init(){
}
}
1. 定义类: class
2. 定义接口:interface
3. 定义枚举:enum
// #1 定义注解
@interface MyAnno1{
}
//#2 定义含有属性的注解
@interface MyAnno2{
public String username() default "jack";
}
4. 修饰符:默认值 public abstract ,且只能是public abstract。
5. 返回值类型:基本类型、字符串String、Class、注解、枚举,以及以上类型的一维数组
6. 属性名:自定义
7. default 默认值:可以省略
//#3 完整含属性注解
@interface MyAnno3{
int age() default 1;
String password();
Class clazz();
MyAnno2 myAnno(); // 注解
Color color(); // 枚举
String[] arrs();
}
enum Color{
BLUE,RED;
}
当运行上面程序后,我们希望输出结果是true,但实际是false。TestAnno2类上有@MyAnno1注解,但运行后不能获得,因为每一个自定义注解,需要使用JDK提供的元注解进行修饰才可以真正的使用。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnno1{
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
}
所有的类加载器 都是 java.lang.ClassLoader 的子类
全盘负责:A类如果要使用B类(不存在),A类加载器C必须负责加载B类。
委托机制:A类加载器如果要加载资源B,必须询问父类加载是否加载。
如果加载,将直接使用。
如果没有机制,自己再加载。
如果一个class文件,被两个类加载器加载,将是两个对象。
提示 cn.com.javahelp.Hello 不能强制成 cn.com.javahelp.Hello
h.getClass() -->A h.getClass() -->B
自定义类加载,可以将一个class文件加载多次。
java代理有jdk动态代理、cglib代理,jdk动态代理主要使用的是java反射机制(既java.lang.reflect包)
动态代理:程序运行时,使用JDK提供工具类(Proxy),动态创建一个类,此类一般用于代理。
代理:你 -- 代理(增强) -- 厂商
代理类: 目标类:被代理的
动态代理使用前提:必须有接口
Object proxyObj = Proxy.newProxyInstance(参数1,参数2,参数3);
参数1:ClassLoader,负责将动态创建类,加载到内存。 当前类.class.getClassLoader();
参数2:Class[] interfaces ,代理类需要实现的所有接口(确定方法),被代理类实例.getClass().getInterfaces();
参数3:InvocationHandler, 请求处理类,代理类不具有任何功能,代理类的每一个方法执行时,调用处理类invoke方法。
voke(Object proxy ,Method ,Object[] args)
参数1:代理实例
参数2:当前执行的方法
参数3:方法实际参数。
public interface UserService {
public void addUser();
public void updateUser();
}
public class UserServiceImpl implements UserService {
@Override
public void addUser() {
System.out.println("addUser");
}
@Override
public void updateUser() {
System.out.println("updateUser");
}
}
public class TextP {
public static void main(String[] args) {
// 1 目标类实例
final UserService userService = new UserServiceImpl();
// 2 代理类
UserService proxyService = (UserService) Proxy.newProxyInstance(TextP.class.getClassLoader(),
userService.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 增强
// 1 获得方法名
String methodName = method.getName();
// 2 判断
if ("addUser".equals(methodName)) {
// 1) 前
System.out.println("前");
// 2) 执行
Object obj = method.invoke(userService, args);
// 3) 后
System.out.println("后");
return obj;
}
// 放行:执行目标类的方法
return method.invoke(userService, args);
}
});
// 3 执行代理类
proxyService.addUser();
proxyService.updateUser();
}
}
原理是(歌手、经纪人做例子):
JDK的动态代理机制只能代理实现了接口的类,而不能实现接口的类就不能实现JDK的动态代理,cglib是针对类来实现代理的,他的原理是对指定的目标类生成一个子类,并覆盖其中方法实现增强,但因为采用的是继承,所以不能对final修饰的类进行代理。
示例
1、BookFacadeCglib.java
2、BookCadeImpl1.java
3、BookFacadeProxy.java
4、TestCglib.java
第4天数据库介绍&SQL基础
数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。
数据库管理系统(DataBase Management System,DBMS):指一种操作和管理数据库的大型软件,用于建立、使用和维护数据库,对数据库进行统一管理和控制,以保证数据库的安全性和完整性。用户通过数据库管理系统访问数据库中表内的数据。
数据库中以表为组织单位存储数据。
表类似我们的Java类,每个字段都有对应的数据类型。
那么用我们熟悉的java程序来与关系型数据对比,就会发现以下对应关系。
类----------表
类中属性----------表中字段
对象----------记录
根据表字段所规定的数据类型,我们可以向其中填入一条条的数据,而表中的每条数据类似类的实例对象。表中的一行一行的信息我们称之为记录。
MYSQL :开源免费的数据库,小型的数据库.已经被Oracle收购了.MySQL6.x版本也开始收费。
Oracle :收费的大型数据库,Oracle公司的产品。Oracle收购SUN公司,收购MYSQL。
DB2 :IBM公司的数据库产品,收费的。常应用在银行系统中.
SQLServer:MicroSoft 公司收费的中型的数据库。C#、.net等语言常使用。
SyBase :已经淡出历史舞台。提供了一个非常专业数据建模的工具PowerDesigner。
SQLite : 嵌入式的小型数据库,应用在手机端。
常用数据库:MYSQL,Oracle.
这里使用MySQL数据库。MySQL中可以有多个数据库,数据库是真正存储数据的地方。
参考MySQL安装图解.doc 官网 http://www.javahelp.com.cn下载
安装后,MySQL会以windows服务的方式为我们提供数据存储功能。开启和关闭服务的操作:右键点击我的电脑→管理→服务→可以找到MySQL服务开启或停止。
也可以在DOS窗口,通过命令完成MySQL服务的启动和停止(必须以管理运行cmd命令窗口)
MySQL是一个需要账户名密码登录的数据库,登陆后使用,它提供了一个默认的root账号,使用安装时设置的密码即可登录。
格式1:cmd> mysql –u用户名 –p密码
例如:mysql -uroot –proot
格式2:cmd> mysql --host=ip地址 --user=用户名 --password=密码
例如:mysql --host=127.0.0.1 --user=root --password=root
提供的SQLyog软件为免安装版,可直接使用
输入用户名、密码,点击连接按钮,进行访问MySQL数据库进行操作
在Query窗口中,输入SQL代码,选中要执行的SQL代码,按F8键运行,或按执行按钮运行。
数据库是不认识JAVA语言的,但是我们同样要与数据库交互,这时需要使用到数据库认识的语言SQL语句,它是数据库的代码。
结构化查询语言(Structured Query Language)简称SQL,是关系型数据库管理系统都需要遵循的规范。不同的数据库生产厂商都支持SQL语句,但都有特有内容。
详细的数据类型如下(不建议详细阅读!)
分类 | 类型名称 | 说明 |
---|---|---|
整数类型 | tinyInt | 很小的整数 |
smallint | 小的整数 | |
mediumint | 中等大小的整数 | |
int(integer) | 普通大小的整数 | |
小数类型 | float | 单精度浮点数 |
double | 双精度浮点数 | |
decimal(m,d) | 压缩严格的定点数 | |
日期类型 | year | YYYY 1901~2155 |
time | HH:MM:SS -838:59:59~838:59:59 | |
date | YYYY-MM-DD 1000-01-01~9999-12-3 | |
datetime | YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00~ 9999-12-31 23:59:59 | |
timestamp | YYYY-MM-DD HH:MM:SS 1970~01~01 00:00:01 UTC~2038-01-19 03:14:07UTC | |
文本、二进制类型 | CHAR(M) | M为0~255之间的整数 |
VARCHAR(M) | M为0~65535之间的整数 | |
TINYBLOB | 允许长度0~255字节 | |
BLOB | 允许长度0~65535字节 | |
MEDIUMBLOB | 允许长度0~167772150字节 | |
LONGBLOB | 允许长度0~4294967295字节 | |
TINYTEXT | 允许长度0~255字节 | |
TEXT | 允许长度0~65535字节 | |
MEDIUMTEXT | 允许长度0~167772150字节 | |
LONGTEXT | 允许长度0~4294967295字节 | |
VARBINARY(M) | 允许长度0~M个字节的变长字节字符串 | |
BINARY(M) | 允许长度0~M个字节的定长字节字符串 |
格式:
* create database 数据库名;
* create database 数据库名 character set 字符集;
例如:
#创建数据库 数据库中数据的编码采用的是安装数据库时指定的默认编码 utf8
CREATE DATABASE webdb_1;
#创建数据库 并指定数据库中数据的编码
CREATE DATABASE webdb_2 CHARACTER SET utf8;
查看数据库MySQL服务器中的所有的数据库:
show databases;
查看某个数据库的定义的信息:
show create database 数据库名;
例如:
show create database webdb_1;
drop database 数据库名称;
例如:
drop database webdb_2;
查看正在使用的数据库:
select database();
其他的数据库操作命令
切换数据库:
use 数据库名;
例如:
use webdb_1;
create table 表名(
字段名 类型(长度) [约束],
字段名 类型(长度) [约束],
...
);
类型:
varchar(n) 字符串
int 整形
double 浮点
date 时间
timestamp 时间戳
约束:
primary key 主键,被主键修饰字段中的数据,不能重复、不能为null。
例如:
###创建分类表
CREATE TABLE category (
cid INT primary key, #分类ID
cname VARCHAR(100) #分类名称
);
查看数据库中的所有表:
格式:show tables;
查看表结构:
格式:desc 表名;
例如:desc sort;
格式:drop table 表名;
例如:drop table category;
作用:修改表添加列.
例如:
#1,为分类表添加一个新的字段为 分类描述 varchar(20)
ALTER TABLE category ADD `desc` VARCHAR(20);
作用:修改表修改列的类型长度及约束.
例如:
#2, 为分类表的描述字段进行修改,类型varchar(50) 添加约束 not null
ALTER TABLE category MODIFY `desc` VARCHAR(50) NOT NULL;
作用:修改表修改列名.
例如:
#3, 为分类表的分类名称字段进行更换 更换为 snamesname varchar(30)
ALTER TABLE category CHANGE `desc` description VARCHAR(30);
作用:修改表删除列.
例如:
#4, 删除分类表中snamename这列
ALTER TABLE category DROP description;
作用:修改表名
例如:
#5, 为分类表category 改名成 category2
RENAME TABLE category TO category2;
作用:修改表的字符集
例如:
#6, 为分类表 category 的编码表进行修改,修改成 gbk
ALTER TABLE category CHARACTER SET gbk;
-- 向表中插入某些字段
insert into 表 (字段1,字段2,字段3..) values (值1,值2,值3..);
--向表中插入所有字段,字段的顺序为创建表时的顺序
insert into 表 values (值1,值2,值3..);
INSERT INTO category(cid,cname) VALUES('c001','电器');
INSERT INTO category(cid,cname) VALUES('c002','服饰');
INSERT INTO category(cid,cname) VALUES('c003','化妆品');
INSERT INTO category(cid,cname) VALUES('c004','书籍');
INSERT INTO category(cid) VALUES('c002');
INSERT INTO category(cname) VALUES('耗材');
用来修改指定条件的数据,将满足条件的记录指定列修改为指定值
--更新所有记录的指定字段
update 表名 set 字段名=值,字段名=值,...;
--更新符号条件记录的指定字段
update 表名 set 字段名=值,字段名=值,... where 条件;
delete from 表名 [where 条件];
或者
truncate table 表名;
删除表中所有记录使用delete from 表名; 还是用truncate table 表名;
删除方式:delete 一条一条删除,不清空auto_increment记录数。
truncate 直接将表删除,重新建表,auto_increment将置为零,从新开始。
我们在dos命令行操作中文时,会报错
insert into category(cid,cname) values(‘c010’,’中文’);
ERROR 1366 (HY000): Incorrect string value: '\xB7\xFE\xD7\xB0' for column 'cname' at row 1
解决方案1:在cmd命令窗口中输入命令,此操作当前窗口有效,为临时方案。
set names gbk;
解决方案2:安装目录下修改my.ini文件,重启服务所有地方生效。[不建议]
错误原因:因为mysql的客户端设置编码是utf8,而系统的cmd窗口编码是gbk
1)查看MySQL内部设置的编码
show variables like 'character%'; 查看所有mysql的编码
2)修改client、connection、results的编码一致(GBK编码)
client connetion result 和客户端保持一致,都为GBK
l 将客户端编码修改为gbk.
#方式1:单独设置
set character_set_client=gbk;
set character_set_connection=gbk;
set character_set_results=gbk;
#方式2:快捷设置
set names gbk;
#创建商品表:
create table product(
pid int primary key,
pname varchar(20),
price double,
category_id varchar(32)
);
INSERT INTO product(pid,pname,price,category_id) VALUES(1,'联想',5000,'c001');
INSERT INTO product(pid,pname,price,category_id) VALUES(2,'海尔',3000,'c001');
INSERT INTO product(pid,pname,price,category_id) VALUES(3,'雷神',5000,'c001');
INSERT INTO product(pid,pname,price,category_id) VALUES(4,'JACK JONES',800,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(5,'真维斯',200,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(6,'花花公子',440,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(7,'劲霸',2000,'c002');
INSERT INTO product(pid,pname,price,category_id) VALUES(8,'香奈儿',800,'c003');
INSERT INTO product(pid,pname,price,category_id) VALUES(9,'相宜本草',200,'c003');
INSERT INTO product(pid,pname,price,category_id) VALUES(10,'面霸',5,'c003');
INSERT INTO product(pid,pname,price,category_id) VALUES(11,'好想你枣',56,'c004');
INSERT INTO product(pid,pname,price,category_id) VALUES(12,'香飘飘奶茶',1,'c005');
INSERT INTO product(pid,pname,price,category_id) VALUES(13,'果9',1,NULL);
select [distinct]
* | 列名,列名
from 表
where 条件
1.查询所有的商品. select * from product;
2.查询商品名和商品价格. select pname,price from product;
3.别名查询.使用的关键字是as(as可以省略的).
3.1表别名: select * from product as p;
3.2列别名:select pname as pn from product;
4.去掉重复值. select distinct price from product;
5.查询结果是表达式(运算查询):将所有商品的价格+10元进行显示.
select pname,price+10 from product;
比较运算符 | > < <= >= = <> | 大于、小于、大于(小于)等于、不等于 |
---|---|---|
BETWEEN ...AND... | 显示在某一区间的值(含头含尾) | |
IN(set) | 显示在in列表中的值,例:in(100,200) | |
LIKE ‘张pattern’ | 模糊查询,Like语句中,% 代表零个或多个任意字符,_ 代表一个字符,例如:first_name like ‘_a%’; | |
IS NULL | 判断是否为空 | |
逻辑运算符 | and | 多个条件同时成立 |
or | 多个条件任一成立 | |
not | 不成立,例:where not(salary>100); |
#查询商品名称为“花花公子”的商品所有信息:
SELECT * FROM product WHERE pname = '花花公子'
#查询价格为800商品
SELECT * FROM product WHERE price = 800
#查询价格不是800的所有商品
SELECT * FROM product WHERE price != 800
SELECT * FROM product WHERE price <> 800
SELECT * FROM product WHERE NOT(price = 800)
#查询商品价格大于60元的所有商品信息
SELECT * FROM product WHERE price > 60;
#查询商品价格在200到1000之间所有商品
SELECT * FROM product WHERE price >= 200 AND price <=1000;
SELECT * FROM product WHERE price BETWEEN 200 AND 1000;
#查询商品价格是200或800的所有商品
SELECT * FROM product WHERE price = 200 OR price = 800;
SELECT * FROM product WHERE price IN (200,800);
#查询含有'霸'字的所有商品
SELECT * FROM product WHERE pname LIKE '%霸%';
#查询以'香'开头的所有商品
SELECT * FROM product WHERE pname LIKE '香%';
#查询第二个字为'想'的所有商品
SELECT * FROM product WHERE pname LIKE '_想%';
#商品没有分类的商品
SELECT * FROM product WHERE category_id IS NULL
#查询有分析的商品
SELECT * FROM product WHERE category_id IS NOT NULL
第5天SQL语句高级
通过order by语句,可以将查询出的结果进行排序。放置在select语句的最后。
格式:
SELECT * FROM 表名 ORDER BY 排序字段 ASC|DESC;
ASC 升序 (默认)
DESC 降序
#1.使用价格排序(降序)
SELECT * FROM product ORDER BY price DESC;
#2.在价格排序(降序)的基础上,以分类排序(降序)
SELECT * FROM product ORDER BY price DESC,category_id DESC;
#3.显示商品的价格(去重复),并排序(降序)
SELECT DISTINCT price FROM product ORDER BY price DESC;
之前我们做的查询都是横向查询,它们都是根据条件一行一行的进行判断,而使用聚合函数查询是纵向查询,它是对一列的值进行计算,然后返回一个单一的值;另外聚合函数会忽略空值。
今天我们学习如下五个聚合函数:
#1 查询商品的总条数
SELECT COUNT(*) FROM product;
#2 查询价格大于200商品的总条数
SELECT COUNT(*) FROM product WHERE price > 200;
#3 查询分类为'c001'的所有商品的总和
SELECT SUM(price) FROM product WHERE category_id = 'c001';
#4 查询分类为'c002'所有商品的平均价格
SELECT AVG(price) FROM product WHERE category_id = 'c002';
#5 查询商品的最大价格和最小价格
SELECT MAX(price),MIN(price) FROM product;
分组查询是指使用group by字句对查询信息进行分组。
SELECT 字段1,字段2… FROM 表名 GROUP BY分组字段 HAVING 分组条件;
分组操作中的having子语句,是用于在分组后对数据进行过滤的,作用类似于where条件。
#1 统计各个分类商品的个数
SELECT category_id ,COUNT(*) FROM product GROUP BY category_id ;
#2 统计各个分类商品的个数,且只显示个数大于1的信息
SELECT category_id ,COUNT(*) FROM product GROUP BY category_id HAVING COUNT(*) > 1;
选中数据库,右键 ”备份/导出” , 指定导出路径,保存成.sql文件即可。
数据库列表区域右键“从SQL转储文件导入数据库”, 指定要执行的SQL文件,执行即可。
PRIMARY KEY 约束唯一标识数据库表中的每条记录。
主键必须包含唯一的值。
主键列不能包含 NULL 值。
每个表都应该有一个主键,并且每个表只能有一个主键。
CREATE TABLE Persons
(
Id_P int PRIMARY KEY,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255),
CONSTRAINT pk_PersonID PRIMARY KEY (FirstName,LastName)
)
或
CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255),
PRIMARY KEY (FirstName,LastName)
)
ALTER TABLE Persons ADD [CONSTRAINT 名称] PRIMARY KEY (字段列表)
CREATE TABLE Persons
(
FirstName varchar(255),
LastName varchar(255),
Address varchar(255),
City varchar(255)
)
ALTER TABLE Persons ADD PRIMARY KEY (FirstName,LastName)
如需撤销 PRIMARY KEY 约束,请使用下面的 SQL:
ALTER TABLE Persons DROP PRIMARY KEY
我们通常希望在每次插入新记录时,数据库自动生成字段的值。
我们可以在表中使用 auto-increment(自动增长列)关键字,自动增长列类型必须是整形,自动增长列必须为键(一般是主键)。
CREATE TABLE Persons
(
P_Id int PRIMARY KEY AUTO_INCREMENT,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
l 向persons添加数据时,可以不为P_Id字段设置值,也可以设置成null,数据库将自动维护主键值:
INSERT INTO Persons (FirstName,LastName) VALUES ('Bill','Gates')
INSERT INTO Persons (P_Id,FirstName,LastName) VALUES (NULL,'Bill','Gates')
ALTER TABLE Persons AUTO_INCREMENT=100
NOT NULL 约束强制列不接受 NULL 值。
NOT NULL 约束强制字段始终包含值。这意味着,如果不向字段添加值,就无法插入新记录或者更新记录。
CREATE TABLE Persons
(
Id_P int NOT NULL,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
UNIQUE 约束唯一标识数据库表中的每条记录。
UNIQUE 和 PRIMARY KEY 约束均为列或列集合提供了唯一性的保证。
PRIMARY KEY 拥有自动定义的 UNIQUE 约束。
请注意,每个表可以有多个 UNIQUE 约束,但是每个表只能有一个 PRIMARY KEY 约束。
与主键添加方式相同,共有3种,
CREATE TABLE Persons
(
Id_P int UNIQUE,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255)
)
CREATE TABLE Persons
(
Id_P int,
LastName varchar(255) NOT NULL,
FirstName varchar(255),
Address varchar(255),
City varchar(255),
CONSTRAINT 名称UNIQUE (Id_P)
)
ALTER TABLE Persons ADD [CONSTRAINT 名称] UNIQUE (Id_P)
FOREIGN KEY 表示外键约束,将在多表中学习。
实际开发中,一个项目通常需要很多张表才能完成。例如:一个商城项目就需要分类表(category)、商品表(products)、订单表(orders)等多张表。且这些表的数据之间存在一定的关系,接下来我们将在单表的基础上,一起学习多表方面的知识。
现在我们有两张表“分类表”和“商品表”,为了表明商品属于哪个分类,通常情况下,我们将在商品表上添加一列,用于存放分类cid的信息,此列称为:外键
此时“分类表category”称为:主表,“cid”我们称为主键。“商品表products”称为:从表,category_id称为外键。我们通过主表的主键和从表的外键来描述主外键关系,呈现就是一对多关系。
外键特点:
语法:alter table 从表 add [constraint] [外键名称] foreign key (从表外键字段名) references 主表 (主表的主键);
[外键名称] 用于删除外键约束的,一般建议“_fk”结尾
alter table 从表 drop foreign key 外键名称
保证数据的一致性和完整性
###创建分类表
create table category(
cid varchar(32) PRIMARY KEY ,
cname varchar(100) #分类名称
);
# 商品表
CREATE TABLE `products` (
`pid` varchar(32) PRIMARY KEY ,
`name` VARCHAR(40) ,
`price` DOUBLE
);
#添加外键字段
alter table products add column category_id varchar(32);
#添加约束
alter table products add constraint product_fk foreign key (category_id) references category (cid);
#1 向分类表中添加数据
INSERT INTO category (cid ,cname) VALUES('c001','服装');
#2 向商品表添加普通数据,没有外键数据,默认为null
INSERT INTO products (pid,pname) VALUES('p001','商品名称');
#3 向商品表添加普通数据,含有外键信息(数据存放在)
INSERT INTO products (pid ,pname ,category_id) VALUES('p002','商品名称2','c001');
#4 向商品表添加普通数据,含有外键信息(数据不存在) -- 不能异常
INSERT INTO products (pid ,pname ,category_id) VALUES('p003','商品名称2','c999');
#5 删除指定分类(分类被商品使用) -- 执行异常
DELETE FROM category WHERE cid = 'c001';
### 商品表[已存在]
### 订单表
create table `orders`(
`oid` varchar(32) PRIMARY KEY ,
`totalprice` double #总计
);
### 订单项表
create table orderitem(
oid varchar(50),-- 订单id
pid varchar(50)-- 商品id
);
###---- 订单表和订单项表的主外键关系
alter table `orderitem` add constraint orderitem_orders_fk foreign key (oid) references orders(oid);
###---- 商品表和订单项表的主外键关系
alter table `orderitem` add constraint orderitem_product_fk foreign key (pid) references products(pid);
### 联合主键(可省略)
alter table `orderitem` add primary key (oid,pid);
#1 向商品表中添加数据
INSERT INTO products (pid,pname) VALUES('p003','商品名称');
#2 向订单表中添加数据
INSERT INTO orders (oid ,totalprice) VALUES('x001','998');
INSERT INTO orders (oid ,totalprice) VALUES('x002','100');
#3向中间表添加数据(数据存在)
INSERT INTO orderitem(pid,oid) VALUES('p001','x001');
INSERT INTO orderitem(pid,oid) VALUES('p001','x002');
INSERT INTO orderitem(pid,oid) VALUES('p002','x002');
#4删除中间表的数据
DELETE FROM orderitem WHERE pid='p002' AND oid = 'x002';
#5向中间表添加数据(数据不存在) -- 执行异常
INSERT INTO orderitem(pid,oid) VALUES('p002','x003');
#6删除商品表的数据 -- 执行异常
DELETE FROM products WHERE pid = 'p001';