首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
84
篇与
的结果
2021-01-17
c++基础之变量和基本类型
之前我写过一系列的c/c++ 从汇编上解释它如何实现的博文。从汇编层面上看,确实c/c++的执行过程很清晰,甚至有的地方可以做相关优化。而c++有的地方就只是一个语法糖,或者说并没有转化到汇编中,而是直接在编译阶段做一个语法检查就完了。并没有生成汇编代码。也就是说之前写的c/c++不能涵盖它们的全部内容。而且抽象层次太低,在应用上很少会考虑它的汇编实现。而且从c++11开始,加入了很多新特性,给人的感觉就好像是一们新的编程语言一样。对于这块内容,我觉得自己的知识还是有欠缺了,因此我决定近期重新翻一翻很早以前买的《c++ primer》 学习一下,并整理学习笔记背景介绍为什么会想到再次重新学习c++的基础内容呢?目前来看我所掌握的并不是最新的c++标准,而是“c with class” 的内容,而且很明显最近在关注一些新的cpp库的时候,发现它的语法我很多地方都没见过,虽然可以根据它的写法来大致猜到它到底用了什么东西,或者说在实现什么功能,但是要自己写,可能无法写出这种语法。而且明显感觉到新的标准加入了很多现代编程语言才有的内容,比如正则表达式、lambda表达式等等。这些都让写c++变得容易,写出的代码更加易读,使其脱离了上古时期的烙印更像现代的编程语言,作为一名靠c++吃饭的程序员,这些东西必须得会的。看书、学编程总少不了写代码并编译运行它。这次我把我写代码的环境更换到了mac平台,在mac平台上使用 vim + g++的方式。这里要提一句,在mac 的shell中,g++和gcc默认使用的是4.8的版本,许多新的c++标准并不被支持,需要下载最新的编译器并使用替换环境中使用的默认编译器,使其更新到最新版本gcc / g++ 使用在shell环境中,不再像visual studio开发环境中那样,只要点击build就一键帮你编译链接生成可执行程序了。shell中所有一切都需要你使用命令行来搞定,好在gcc/g++的使用并不复杂,记住几个常用参数就能解决日常80%的使用场景了,下面罗列一些常用的命令-o 指定生成目标文件位置和名称-l 指定连接库文件名称,一般库以lib开头但是在指定名称时不用加lib前缀,例如要链接libmath.o 可以写成-lmath-L 指定库所在目录-Wall 打印所有警告,一般编译时打开这个-E 仅做预处理,不进行编译-c 仅编译,不进行链接-static 编译为静态库-share 编译为动态库-Dname=definition 预定义一个值为definition的,名称为name的宏-ggdb -level 生成调试信息,level可以为1 2 3 默认为2-g -level 生成操作系统本地格式的调试信息 -g相比于-ggdb 来说会生成额外的信息-O0/O1/O2/O3 尝试优化-Os 对生成的文件大小进行优化常用的编译命令一般是 g++ -Wall -o demo demo.cpp开启所有警告项,并编译demo.cpp 生成demo程序 ## 基本数据类型与变量 ### 算术类型 这里说的基本数据类型主要是算术类型,按占用内存空间从小到大排序 char、bool(这二者应该是相同的)、short、wchar_t、int、long、longlong、float、double、long double。当然它们有的还有有符号与无符号的区别,这里就不单独列出了 一般来说,我们脑袋中记住的它们的大小好像是固定,比如wchar_t 占2个字节,int占4个字节。单实际上c++ 并没有给这些类型的大小都定义死,而是固定了一个最小尺寸,而具体大小究竟定义为多少,不同的编译器有不同的实现,比如我尝试的wchar_t 类型在vc 编译环境中占2个字节,而g++编译出来的占4一个字节。下面的表是c++ 规定的部分类型所占内存空间大小 | 类型 | 含义 | 最小尺寸 | |:----------|:--------------|:-----| | bool | 布尔类型 | 未定义 | | char | 字符 | 8位 | | wchar_t | 宽字符 | 16位 | | char16_t | Unicode字符 | 16位 | | char32_t | Unicode字符 | 32位 | | short | 短整型 | 16位 | | int | 整型 | 32位 | | long | 长整型 | 32位 | | longlong | 长整型 | 64位 | | float | 单精度浮点数 | 32位 | | double | 双精度浮点数 | 64位 | 另外c++的标准还规定 一个int类型至少和一个short一样大,long至少和int一样大、一个longlong至少和一个long一样大。 ### 有符号数与无符号数 数字类型分为有符号和无符号的,默认上述都是有符号的,在这些类型中加入unsigned 表示无符号,而char分为 signed char、char、unsigned char 三种类型。但是实际使用是只能选有符号或者无符号的。根据编译器不同,char的表现不同。 一般在使用这些数据类型的时候有如下原则 1. 明确知晓数值不可能为负的情况下使用unsigned 类型 2. 使用int进行算数运行,如果数值超过的int的表示范围则使用 longlong类型 3. 算术表达式中不要使用char或者bool类型 4. 如果需要使用一个不大的整数,必须指定是signed char 还是unsigned char 5. 执行浮点数运算时使用double ### 类型转化 当在程序的某处我们使用了一种类型,而实际对象应该取另一种类型时,程序会自动进行类型转化,类型转化主要分为隐式类型转化和显示类型转化。 数值类型进行类型转化时,一般遵循如下规则: 1. 把数字类型转化为bool类型时,0值会转化为false,其他值最后会被转化为true 2. 当把bool转化为非bool类型时,false会转化为0,true会被转化为1 3. 把浮点数转化为整型时,仅保留小数点前面的部分 4. 把整型转化为浮点数时,小数部分为0;如果整数的大小超过浮点数表示的范围,可能会损失精度 5. 当给无符号类型的整数赋值一个超过它表示范围的数时,会发生溢出。实际值是赋值的数对最大表示数取余数的结果 6. 当给有符号的类型一个超出它表示范围的值时,具体结果会根据编译器的不同而不同 7. 有符号数与无符号数混用时,结果会自动转化为无符号数 (使用小转大的原则,尽量不丢失精度) **由于bool转化为数字类型时非0即1,注意不要在算术表达式中使用bool类型进行运算** 下面是类型转化的具体例子 ```cpp bool b = 42; // b = true int i = b; // i = 1 i = 3.14; // i = 3; double d = i; // d = 3.0 unsigned char c = -1; // c = 256 signed char c2 = c; // c2 = 0 gcc 中 255在内存中的表现形式为0xff,+1 变为0x00 并向高位溢出,所以结果为0 ``` 上述代码的最后一个语句发生了溢出,**对于像溢出这种情况下。不同的编译器有不同的处理方式,得到的结果可能不经相同,在编写代码时需要避免此类情况的出现** 尽管我们知道不给一个无符号数赋一个负数,但是经常会在不经意间犯下这样的错误,例如当一个算术表达式中既有无符号数,又有有符号数的时候。例如下面的代码 ```cpp unsigned u = 10; int i = -42; printf("%d\r\n", u + i); // -32 printf("%u\r\n", u + i); //4294967264 ``` 那么该如何计算最后的结果呢,这里直接根据它们的二进制值来进行计算,然后再转化为具体的10进制数值,例如u = 0x0000000A,i = 0xffffffd6;二者相加得到 0xffffffEO, 如果转化为int类型,最高位是1,为负数,其余各位取反然后加一得到0x20,最终的结果就是-32,而无符号,最后的值为4294967264 ### 字面值常量 一般明确写出来数值内容的称之为字面值常量,从汇编的角度来看,能直接写入代码段中数值。例如32、0xff、"hello world" 这样内容的数值 #### 整数和浮点数的字面值 整数的字面值可以使用二进制、8进制、10进制、16进制的方式给出。而浮点数一般习惯上以科学计数法的形式给出 1. 二进制以 0b开头,八进制以0开头,十六进制以0x开头 2. 数值类型的字面值常量最终会以二进制的形式写入变量所在内存,如何解释由变量的类型决定,默认10进制是带符号的数值,其他的则是不带符号的 3. 十进制的字面值类型是int、long、longlong中占用空间最小的(前提是类型能容纳对应的数值) 4. 八进制、十六进制的字面值类型是int、unsigned int、long、unsigned long、longlong和unsigned longlong 中尺寸最小的一个(同样的要求对应类型能容纳对应的数值) 5. 浮点数的字面值用小数或者科学计数法表示、指数部分用e或者E标示 #### 字符和字符串的字面值常量 由单引号括起来的一个字符是char类型的字面值,双引号括起来的0个或者多个字符则构成字符串字面值常量。字符串实际上是一个字符数组,数组中的每个元素存储对应的字符。**这个数组的大小等于字符串中字符个数加1,多出来一个用于存储结尾的\0** 有两种类型的字符程序员是不能直接使用的,一类是不可打印的字符,如回车、换行、退格等格式控制字符,另一类是c/c++语言中有特殊用途的字符,例如单引号表示字符、双引号表示一个字符串,在这些情况下需要使用转义字符. 1. 转义以\开头,后面只转义仅接着的一个字符 2. 转义可以以字符开始,也可以以数字开始,数字在最后会被转化为对应的ASCII字符 3. \x后面跟16进制数、\后面跟八进制数、八进制数只取后面的3个;十六进制数则只能取两个数值(最多表示一个字节) ```cpp '\\' // 表示一个\字符 "\"" //表示一个" "\155" //表示一个 155的8进制数,8进制的155转化为10进制为109 从acsii表中可以查到,109对应的是M "\x6D" ``` 一般来讲我们很难通过字面值常量知道它到底应该是具体的哪种类型,例如 15既可以表示short、int、long、也是是double等等类型。为了准确表达字面值常量的类型,我们可以加上特定的前缀或者后缀来修饰它们。常用的前缀和后缀如下表所示: | 前缀 | 含义 | |:------|:-----------------------| | L'' | 宽字节 | | u8"" | utf-8字符串 | | 42ULL | unsgined longlong | | f | 单精度浮点数 | | 3L | long类型 | | 3.14L | long double | | 3LL | longlong | | u'' | char16_t Unicode16字符 | | U'' | char32_t Unicode32字符 | ## 变量 变量为程序提供了有名的,可供程序操作的内存空间,变量都有具体的数据类型、所在内存的位置以及存储的具体值(即使是未初始化的变量,也有它的默认值)。变量的类型决定它所占内存的大小、如何解释对应内存中的值、以及它能参与的运算类型。在面向对象的语言中,变量和对象一般都可以替换使用 ### 变量的定义与初始化 变量的定义一般格式是类型说明符其后紧随着一个或者多个变量名组成的列表,多个变量名使用逗号隔开。最后以分号结尾。 一般在定义变量的同时赋值,叫做变量的初始化。而赋值语句结束之后,在其他地方使用赋值语句对其进行赋值,被称为赋值。从汇编的角度来看,变量的初始化是,在变量进入它的生命有效期时,对那块内存执行的内存拷贝操作。而赋值则需要分解为两条语句,一个寻址,一个值拷贝。 c++11之后支持初始化列表进行初始化,在使用初始化列表进行初始化时如果出现初始值存在精度丢失的情况时会报错 c++11之后的列表初始化语句,支持使用赋值运算幅、赋值运算符加上{}、或者直接使用{}、直接使用() ```cpp int i = 3.14; //正常 int i(3.14); //正常 int i{3.14}; //报错,使用初始化列表进行初始化时,由double到int可能会发生精度丢失 int i(3.14); //正常 ``` 如果变量在定义的时候未给定初始值,则会执行默认初始化操作,全局变量会被赋值为0,局部变量则是未初始化的状态;它的值是不确定的。这个所谓的默认初始化操作,其实并不是真的那个时候执行了什么初始化语句。全局变量被初始化为0,主要是因为,在程序加载之初,操作系统会将数据段的内存都初始化为0,而局部变量,则是在进入函数之后,初始化栈,具体初始化为何值,根据平台的不同而不同 ### 声明与定义的关系 为了允许把程序拆分为多个逻辑部分来编写,c++支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。 如果将程序分为多个文件,则需要一种在文件中共享代码的方法。c++中这种方法是将声明与定义区分开来。在我之前的博客中,有对应的说明。声明只是告诉编译器这个符号可以使用,它是什么类型,占多少空间,但前对它执行的这种操作是否合法。最终会生成一个符号表,在链接的时候根据具体地址,再转化为具体的二进制代码。而定义则是真正为它分配内存空间,以至于后续可以通过一个具体的地址访问它。 声明只需要在定义语句的前面加上extern关键字。如果extern 关键字后面跟上了显式初始化语句,则认为该条语句是变量的定义语句。变量可以声明多次但是只能定义一次。另外在函数内部不允许初始化一个extern声明的变量 ```cpp int main() { extern int i = 0; //错误 return 0; } ``` 一个好的规范是声明都在放在对应的头文件中,在其他地方使用时引入该头文件,后续要修改,只用修改头文件的一个地方。一个坏的规范是,想用了,就在cpp文件中使用extern声明,这样会导致声明有多份,修改定义,其他声明都得改,项目大了,想要找起来就不那么容易了。 ### 变量作用域 变量的作用域始于声明语句,终结于声明语句所在作用域的末端 1. 局部变量在整个函数中有效 2. 普通全局变量在整个程序中都有效果 3. 花括号中定义的变量仅在这对花括号中有效 作用域可以存在覆盖,并且以最新的定义的覆盖之前的 ```cpp int i = 10; void func() { int i = 20; { string i = "hello world"; cout
2021年01月17日
0 阅读
0 评论
0 点赞
2019-12-16
Mybatis框架
在之前的内容中,我写了Java的基础知识、Java Web的相关知识。有这些内容就可以编写各种各样丰富的程序。但是如果纯粹手写所有代码,工作量仍然很大。为了简化开发,隐藏一些不必要的细节,专心处理业务相关内容 ,Java提供了许多现成的框架可以使用Mybatis介绍在程序开发中讲究 MVC 的分层架构,其中M表示的是存储层,也就是与数据库交互的内容。一般来说使用jdbc时,需要经历:导入驱动、创建连接、创建statement对象,执行sql、获取结果集、封装对象、关闭连接这样几个过程。里面很多过程的代码都是固定的,唯一有变化的是执行sql并封装对象的操作。而封装对象时可以利用反射的机制,将返回字段的名称映射到Java实体类的各个属性上。这样我们很自然的就想到了,可以编写一个框架或者类库,实现仅配置sql语句和对应的映射关系,来实现查询到封装的一系列操作,从而简化后续的开发。Mybatis帮助我们实现了这个功能。Mybatis实例假设现在有一个用户表,存储用户的相关信息,我们现在需要使用mybatis来进行查询操作,可能要经历如下步骤:定义对应的实体类public class User { private Integer id; private String username; private String birthday; private char sex; private String address; //后面省略对应的getter和setter方法 //为了方便后面的实体类都会省略这些内容 } 编辑主配置文件,主要用来配置mybati的数据库连接信息以及指定对应dao的配置文件<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDConfig3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--mybatis主配置文件--> <configuration> <!--配置环境--> <environments default="mybatis_demo"> <environment id="mybatis_demo"> <!--配置事务的类型--> <transactionManager type="JDBC"></transactionManager> <!--配置连接池--> <dataSource type="POOLED"> <!--配置数据库连接的4个基本信息--> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis_demo"/> <property name="username" value="root"/> <property name="password" value="masimaro_root"/> </dataSource> </environment> </environments> <!--指定配置文件的位置,配置文件是每个dao独立的配置文件--> <mappers> <mapper resource="com/MybatisDemo/Dao/IUserDao.xml"></mapper> </mappers> </configuration>编写dao接口public interface IUserDao { public List<User> findAll(); }并提供dao的xml配置文件<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTDMapper3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--每个函数配置一条,标签名是要进行的数据库操作,resultType是需要返回的数据类型--> <mapper namespace="com.MyBatisDemo.Dao.IUserDao"> <!--标签里面的文本是sql语句--> <select id="findAll" resultType="com.MyBatisDemo.domain.User"> select * from user; </select> </mapper>写完了对应的配置代码,接下来就是通过简单的几行代码来驱动mybatis,完成查询并封装的操作InputStream is = null; SqlSession = null; try { //加载配置文件 is = Resources.getResourceAsStream("dbconfig.xml"); //创建工厂对象 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(is); //创建sqlsession对象 sqlSession = factory.openSession(); //使用sqlsession对象创建dao接口的代理对象 IUserDao userDao = sqlSession.getMapper(IUserDao.class); //使用对象执行方法 List<User> users = this.userDao.findAll(); System.out.println(users); } catch (IOException e) { e.printStackTrace(); }finally{ // 清理资源 if (null != this.is){ try { this.sqlSession.commit(); this.is.close(); } catch (IOException e) { e.printStackTrace(); } } if (null != this.sqlSession){ this.sqlSession.close(); } }mybatis大致的执行过程根据我们传入的InputStream对象来获取配置xml中对应对象的值接着根据配置信息创建连接并生成数据库的连接池对象根据配置文件中的mapper项获取到对应的Dao接口的配置文件,在读取该文件时会准备一个Map结构,其中key是mapper中的namespace + id,value是对应的sql语句,例如上述例子中得到的map结构为{"com.MyBatisDemo.Dao.IUserDao.findAll", "select * from user"}在创建sqlsession时从连接中获取到一个Statement对象在我们调用dao接口时,首先根据dao接口得到详细的类名,然后获取到当前调用的接口名称,由这两项得到一个key,比如在上述例子中,dao接口的名称为com.MyBatisDemo.Dao.IUserDao, 而调用的方法是 findAll,将这两个字符串进行拼接,得到一个key,根据这个key去map中查找对应的sql语句。并执行执行sql语句获取查询的结果集根据resultType中指定的对象进行封装并返回对应的实体类使用mybatis实现增删改查操作在之前的代码上可以看出,使用mybatis来实现功能时,只需要提供dao接口中的方法,并且将方法与对应的sql语句绑定。在提供增删改查的dao方法时如果涉及到需要传入参数的情况下该怎么办呢?下面以根据id查询内容为例:我们先在dao中提供这样一个方法:public User findById(int id);然后在dao的配置文件中编写sql语句<!--parameterType 表示传入的参数的类型--> <select id="findById" resultType="com.MyBatisDemo.domain.User" parameterType="int"> select * from user where id = #{id} </select>从上面的配置可以看到,mybatis中, 使用#{} 来表示输入参数,使用属性parameterType属性来表示输入参数的类型。一般如果使用Java内置对象是不需要使用全限定类名,也不区分大小写。当我们使用内置类型的时候,这里的id 仅仅起到占位符的作用,取任何名字都可以看完了使用内置对象的实例,再来看看使用使用自定义类类型的情况,这里我们使用update的例子来说明,首先与之前的操作一样,先定义一个upate的方法:void updateUser(User user);然后使用如下配置<update id="updateUser" parameterType="User"> update user set username=#{username}, birthday=#{birthday}, sex=#{sex}, address=#{address} where id = #{id} </update>与使用id查询的配置类似,当我们使用的是自定义类类型时,在对应的字段位置需要使用类的属性表示,在具体执行的时候,mybatis会根据传入的类对象来依据配置取出对应的属性作为sql语句的参数。上面在使用内置对象时我们说它可以取任何的名称,但是这里请注意 名称只能是自定义对象的属性名,而且区分大小写这里使用的都是确定的值,如果要使用模糊查询时该如何操作呢,这里我们按照名称来模糊查询,首先在dao中提供一个对应的方法User findByName(String name);接着再来进行配置<select resultType="com.MyBatisDemo.domain.User" parameterType="String"> select * from User where username like #{username} </select>从sql语句来看我们并没有实现模糊的方式,这时候在传入参数的时候就需要使用模糊的方式,调用时应该在参数中添加 %%, 就像这样 userDao.findByName("%" + username + "%")当然我们可以使用另外一种配置<select resultType="com.MyBatisDemo.domain.User" parameterType="String"> select * from User where username like %${username}% </select>这样我们在调用时就不需要额外添加 % 了。既然他们都可以作为参数,那么这两个符号有什么区别呢?区别在于他们进行查询的方式,$ 使用的是字符串拼接的方式来组成一个完成的sql语句进行查询,而#使用的是参数话查询的方式。一般来说拼接字符串容易造成sql注入的漏洞,为了安全一定要使用参数话查询的方式mybatis的相关标签resultMap标签在之前的配置中,其实一直保持着数据库表的字段名与对应的类属性名同名,但是有些时候我们不能保证二者同名,为了解决这问题也为了以后进行一对多和多对多的配置,可以使用resultMap来定义数据库表字段名和类属性名的映射关系下面是一个使用它的例子。我们简单修改一下User类的属性定义public class User { private Integer uid; private String name; private String userBirthday; private char userSex; private String userAddress; //后面省略对应的getter和setter方法 }这样直接使用之前的配置执行会报错,报找不到对应属性的错误,这个时候就可以使用resultMap属性来解决这个问题<resultMap id="UserMapper" type="User"> <id column="id" property="uid"></id> <result column="username" property="username"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> <result column="address" property="address"></result> </resultMap> <select id="findAll" resultMap="UserMapper"> select * from user; </select>其中 id属性来唯一标示这个映射关系,在需要使用到这个映射关系的地方,使用resultMap这个属性来指定type属性表示要将这些值封装到哪个自定义的类类型中resultMap中有许多子标签用来表示这个映射关系id用来表明表结构中主键的映射关系result表示其他字段的映射关系每个标签中的column属性表示的是对应的表字段名标签中的property对应的是类属性的名称properties 标签properties标签可以用来定义数据库的连接属性,主要用于引入外部数据库连接属性的文件,这样我们可以通过直接修改连接属性文件而不用修改具体的xml配置文件。假设现在在工程中还有一个database.properties文件jdbc.driver ="com.mysql.jdbc.Driver" jdbc.url = "jdbc:mysql://localhost:3306/mybatis_demo" jdbc.username ="root" jdbc.password" ="masimaro_root"然后修改对应的主配置文件<!--引入properties文件--> <properties resource="database.properties"> </properties> <!--修改对应的dataSource标签--> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource>typeAliases 标签之前我们说过,使用内置类型时不需要写全限定类名,而且它不区分大小写。而使用自定义类型时需要写很长一串,如何使自定义类型与内置类型一样呢?这里可以使用typeAliases标签。它用来定义类名的别名<typeAliases> <!--typeAlias中来定义具体类的别名,type表示真实类名,alias表示别名--> <typeAlias type="com.MyBatisDemo.domain.User" alias="user"></typeAlias> </typeAliases>使用typeAlias标签时,每个类都需要提供一条对应的配置,当实体类多了,写起来就很麻烦了,这个时候可以使用package子标签来代替typeAlias<typeAliases> <package name="com.MyBatisDemo.domain"/> </typeAliases>它表示这里包中的所有类都使用别名,别名就是它的类名package标签在定义对应的mapper xml文件时,一个dao接口就需要一条配置。dao接口多了,一条条的写很麻烦,为了减轻编写的工作量可以使用package标签<mappers> <!--它表示这个包中的所有xml都是mapper配置文件--> <package name="com/MyBatis/Dao"/> </mappers>连接池在配置数据库连接 dataSource 标签中有一个type属性,它用来定义使用的连接池,该属性有三个取值:POOLE:使用连接池,采用javax.sql.DataSource 规范中的连接池,mybatis中有针对它的数据库连接池的实现UNPOOLED:与POOLED相同,使用的都是javax.sql.DataSource 规范,但是它使用的是常规的连接方式,没有采用池的思想JNDI:根据服务器提供的jndi基础来获取数据库的连接 ,具体获取到的连接对象又服务器提供动态sql当我们自己拼接sql的时候可以根据传入的参数的不同来动态生成不同的sql语句执行,而在之前的配置中,我们事先已经写好了使用的sql语句,但是如果碰上使用需要按照条件搜索,而且又不确定用户会输入哪些查询条件,在这样的情况下,没办法预先知道该怎么写sql语句。这种情况下可以使用mybatis中提供的动态sql假设我们提供一个findByValue的方法,根据值来进行查询。public List<User> findByValue(User user);事先并不知道user的哪些属性会被赋值,我们需要做的就是判断user的哪些属性不为空,根据这些不为空的属性来进行and的联合查询。这种情况下我们可以使用if标签<select id="findByValue" resultType="User" parameterType="User"> select * from user where <if test="id != null"> id = #{id} and </if> <if test="username != null"> username=#{username} and </if> ..... 1=1 </select>if标签中使用test来进行条件判断,而判断条件可以完全使用Java的语法来进行。这里在最后用了一个1=1的条件来结束判断,因为事先并不知道用户会传入哪些值,不知道哪条语句是最后一个条件,因此我们加一个恒成立的条件来确保sql语句的完整当然mybatis中也有办法可以省略最后的1=1,我们可以使用 where标签来包裹这些if,表明if中的所有内容都是作为查询条件的,这样mybatis在最后会在生成查询条件后自动帮助我们进行格式的整理使用if标签我们搞定了不确定用户会使用哪些查询条件的问题,如果有这样一个场景:用户只知道某个字段的名字有几种可能,我们在用户输入的几种可能值中进行查找,也就是说,用户可以针对同一个查询条件输入多个可能的值,根据这些个可能的值进行匹配,只要有一个值匹配上即可返回;针对这种情况没办法使用if标签了,我们可以使用循环标签,将用户输入的多个值依次迭代,最终组成一个in的查询条件我们在这里提供一个根据多个id查找用户的方法public List<User> findByIds(List<Integer> ids);这里我们为了方便操作,额外提供一个类用来存储查询条件public class QueryVo { List<Integer> ids; }<select id="findUserByIds" resultType="User" parameterType="QueryVo"> select * from user <where> <if test="ids != null and ids.size() != 0"> <foreach collection="ids" open="and id in (" close=")" item= "id" separator=","> ${id} </foreach> </if> </where> </select>在上面的例子中使用foreach来迭代容器其中使用collection表示容器,这里取的是parameterType中指定类的属性,open表示在迭代开始时需要加入查询条件的sql语句,close表示在迭代结束后需要添加到查询语句中的sql,item表示每个元素的变量名,separator表示每次迭代结束后要添加到查询语句中的字符串。当我们迭代完成后,整个sql语句就变成了这样: select * from user where 1=1 and id in (id1, id2, ...)多表查询一对多查询在现实中存在着这么一些一对多的对应关系,像什么学生和班级的对应关系,用户和账户的对应关系等等。关系型数据库在处理这种一对多的情况下,使用的是在多对应的那张表中添加一个外键,这个外键就是对应的一那张表的主键,比如说在处理用户和账户关系时,假设一个用户可以创建多个账户,那么在账户表中会有一个外键,指向的是用户表的ID在上面例子的基础之上,来实现一个一对多的关系。首先添加一个账户的实体类,并且根据关系账户中应该有一个唯一的用户类对象,用来表示它所属的用户public class Account { private int id; private int uid; private double money; private User user; }同时需要在User这个实体类上添加一个Account的列表对象,表示一个User下的多个Accountpublic class User { private Integer id; private String username; private String birthday; private char sex; private String address; private List<Account> accounts; }首先根据user来查询多个account,我们可以写出这样的sql语句来查询select u.*, a.id as aid, a.money, a.uid from user as u left join account as a on a.uid = u.id;那么它查询出来的结果字段名称应该是id, username, sex, birthday, address, aid, money, uid 这些,前面的部分可以封装为一个User对象,但是后面的部分怎么封装到Accounts中去呢,这里可以在resultMap中使用collection标签,该标签中对应的对象会被封装为一个容器。因此这里的配置可以写为:<resultMap id="UserAccountMap" type="user"> <id property="id" column="id"></id> <result property="username" column="username"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> <collection property="accounts" ofType="account"> <id property="id" column="aid"></id> <result property="money" column="money"></result> <result property="uid" column="uid"></result> </collection> </resultMap> <select id="findAll" resultMap="UserAccountMap"> select u.*, a.ID as aid, a.MONEY, a.UID from user as u left join acc ount as a on u.id = a.uid </select>我们需要一个resultMap来告诉Mybatis,这些多余的字段该怎么进行封装,为了表示一个容器,我们使用了一个coolection标签,标签中的property属性表示这个容器被封装到resultType对应类的哪个属性中,ofType表示的是,容器中每一个对象都是何种类型,而它里面的子标签的含义与resultMap子标签的含义完全相同从User到Account是一个多对多的关心,而从Account到User则是一个一对一的关系,当我们反过来进行查询时,需要使用的配置是 association 标签,它的配置与使用与collection相同<resultMap id="AccountUserMap" type="Account"> <id property="id" column="aid"></id> <result property="uid" column="uid"></result> <result property="money" column="money"></result> <association property="user" column="uid" javaType="user"> <id property="id" column="uid"></id> <result property="username" column="username"></result> <result property="birthday" column="birthday"></result> <result property="sex" column="sex"></result> <result property="address" column="address"></result> </association> </resultMap> <select id="findUserAccounts" resultType="Account" parameterType="User"> select * from account where uid = ${id} </select>多对多查询说完了一对多,再来说说多对多查询。多对多在关系型数据库中使用第三张表来体现,第三张表中记录另外两个表的主键作为它的外键。这里使用用户和角色的关系来演示多对多查询与之前一样,在两个实体类中新增对方的一个list对象,表示多对多的关系public class Role implements Serializable { private int id; private String roleName; private String roleDesc; private List<User> users; }利用之前一对多的配置,我们只需要修改一下ResultMap和sql语句就可以完成多对多的查询<mapper namespace="com.liuhao.Dao.IUserDao"> <resultMap id="UserRoleMapper" type="User"> <id property="id" column="id"></id> <result column="username" property="username"></result> <result column="sex" property="sex"></result> <result column="address" property="address"></result> <result column="birthday" property="birthday"></result> <collection property="roles" ofType="role"> <id property="id" column="rid"></id> <result column="role_desc" property="roleDesc"></result> <result column="role_name" property="roleName"></result> </collection> </resultMap> <select id="findAll" resultMap="UserRoleMapper"> select user.*, role.ID as rid, role.ROLE_DESC, role.ROLE_NAME from u ser left outer join user_role on user_role.uid = user.id left OUTER join role on user_role.RID = role.ID </select> </mapper>另一个多对多的关系与这个类似,这里就不再单独说明了延迟加载之前说了该如何做基本的单表和多表查询。这里有一个问题,在多表查询中,我们是否有必要一次查询出它所关联的所有数据,就像之前的一对多的关系中,在查询用户时是否需要查询对应的账户,以及查询账户时是否需要查询它所对应的用户。如果不需要的话,我么采用上面的写法会造成多执行一次查询,而且当它关联的数据过多,而这些数据我们用不到,这个时候就会造成内存资源的浪费。这个时候我们需要考虑使用延迟加载,只有需要才进行查询。之前的sql语句一次会同时查询两张表,当然不满足延迟加载的要求,延迟加载应该将两张表的查询分开,先只查询需要的一张表数据,另一张表数据只在需要的时候查询。根据这点我们进行拆分,假设我们要针对User做延迟加载,我们先不管accounts的数据,只查询user表,可以使用sql语句select * from user, 在需要的时候执行select * from account where uid = id在xml配置中可以在collection标签中使用select属性,该属性指向一个方法,该方法的功能是根据id获取所有对象的列表。也就说我们需要在AccountDao接口中提供这么一个方法,并且编写它的xml配置public List<Account> findByUid(int uid);接着我们对之前的xml进行改写<resultMap id="UserMapper" type="User"> <id column="id" property="id"></id> <result column="username" property="username"></result> <result column="sex" property="sex"></result> <result column="birthday" property="birthday"></result> <result column="address" property="address"></result> <collection property="accounts" ofType="Account" select="com.liuhao.Dao.IAccountDao.findByUid" column="id"> </collection> </resultMap> <select id="findAll" resultMap="UserMapper"> select * from user; </select>完成了接口的编写与配置,还需要对主配置文件做一些配置,我们在主配置文件中添加settings节点,开启延迟加载<settings> <setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="false"/> </settings>缓存缓存用来存储一些不经常变化的内容,使用缓存可以减少查询数据库的次数,提高效率。mybatis有两种缓存,一种是在每个sqlsession中的缓存,一种是在每个SqlSessionFactory中的缓存在SqlSession中的缓存又被叫做是Mybatis的一级缓存。每当完成一次查询操作时,会在SqlSession中形成一个map结构,用来保存调用了哪个方法,以及方法返回的结果,下一次调用同样的方法时会优先从缓存中取当我们执行insert、update、delete等sql操作,或者执行SqlSession的close或者clearCache等方法时缓存会被清理在SqlSessionFactory中的缓存被称做二级缓存,所有由同一个SqlSessionFactory创建出来的SqlSessin共享同一个二级缓存。二级缓存是一个结果的二进制值,每当我们使用它时,它会取出这个二进制值,并将这个值封装为一个新的对象。在我们多次使用同一片二级缓存中的数据,得到的对象也不是同一个使用二级缓存需要进行一些额外的配置:在主配置文件中添加配置 在settings的子标签setting 中添加属性 enableCache=True开启二级缓存在对应的dao xml配置中添加 cache标签(标签中不需要任何属性或者文本内容),使接口支持缓存在对应的select、update等标签上添加属性 useCache=true,为方法开启二级缓存
2019年12月16日
2 阅读
0 评论
0 点赞
2019-12-07
EL表达式与JSTL
JSP标准标签库(JSTL)是一个JSP标签集合,它封装了JSP应用的通用核心功能。JSTL支持通用的、结构化的任务,比如迭代,条件判断,XML文档操作,国际化标签,SQL标签。 除了这些,它还提供了一个框架来使用集成JSTL的自定义标签。JSTL安装要使用jstl需要导入对应的库,可以去官方站点下载, 点击这里下载然后解压文件将得到的jar包放入到WEB-INF的lib中导入之后,在要使用它的jsp文件中使用taglib 导入库<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>prefix 是标签的前缀,类似于命名空间,在使用库中的标签时需要加上这个前缀常用标签if 标签if标签用来做判断,当条件成立时,执行标签体的内容,条件写在test属性中,注意:只有if标签而没有对应的else标签。下面是一个例子:<c:if test="${not empty requestScope.error}"> <div style="color:red;width:100%;" align = "center">${requestScope.error}</div> </c:if>上述这个例子表示,当服务器返回错误信息时,将错误信息显示到页面上choose 标签choose 标签相当于switch 语句,该标签中可以包含 when 和 otherwise 作为字标签,相当于switch语句中的case和default,例如下面的例子<p>当前薪水为 : <c:out value="${salary}"/></p> <c:choose> <c:when test="${salary <= 2000}"> 老板我是你爹, 这个工作谁爱干谁干 </c:when> <c:when test="${salary > 50000}"> 公司是我家,工作就是我的价值,我热爱工作 </c:when> <c:otherwise> 心中无半点波澜,甚至想提前下班 </c:otherwise> </c:choose>foreach 标签foreach 用来迭代容器中的元素,或者完成一些重复的操作。当使用foreach标签来进行重复性的操作时可以使用begin、end、var来控制循环,begin表示循环变量开始的值,end表示循环变量结束的值,与正常的for循环不同,循环变量的值可以等于end的值;使用var标签来定义循环变量的名称,使用step表示步进。例如:<c:foreach begin = "1" end = "10" var = "i step = "1"> ${i} <br /> </c:foreach>等价于for(int i = 1; i <= 10; i++){ System.out.println(i); }当使用 foreach来迭代容器时使用item和 var来迭代,其中item为需要迭代的容器,var表示获取到的容器中的元素。例如<c:foreach items = "list" var = "l"> ${l} </c:foreach>等价于for(String l:list){ System.out.println(l); }ELEL 表达式:Expression Language 表达式语言,用于替换和简化jsp页面中java代码的编写。EL 表达式使用 ${} 来表示jsp 默认支持el表达式,在page指令中可以使用 isELIgnored 来指定是否忽略jsp页面中的el表达式;当然也可以使用 \ 来作为转义符,表示 这个el表达式原样输出,例如 \${cookie}EL表达式中可以支持算数运算符、比较运算符、逻辑运算符合empty 空运算符;empty用于判断字符串、集合、数组对象是否为null或者长度为0。在使用el表达式时需要注意以下几点:el表达式只能从域对象中获取值el表达式中如果是类对象,可以根据Java Bean规范来获取属性值针对list这种有序集合可以使用 ${域对象.键名[索引].属性}针对Map集合,使用 ${域对象.键名.key名}或者 ${域对象.键名["key名"]}el 表达式中对域对象都做了重命名,pageScope 对应于 pageContext、requestScope对应于request、sessionScope对应于session、applicationScope对应于applicate(ServletContext)表达式${键名} 依次从最小的域中去查找对应的键值,直到找到为止
2019年12月07日
4 阅读
0 评论
0 点赞
2019-11-17
jsp
之前聊过用java处理web请求,处理cookie和session等等,但是唯独没有提及如何返回信息。作为一个web程序,肯定需要使用HTML作为用户界面,这个界面需要由服务端返回。返回信息可以使用HttpResponse中的OutputStream对象来写入数据。但是既要在里面写入HTML,又要写入相应的值,造成程序很难编写,同时HTML代码长了也不容易维护。我们需要一种机制来专门处理返回给浏览器的信息。JSP就是用来专门处理这种需求的。JSP概述JSP (Java Server Page):Java 服务端页面。是由 Sun Microsystems 公司倡导和许多公司参与共同创建的一种使软件开发者可以响应客户端请求,而动态生成 HTML、XML 或其他格式文档的Web网页的技术标准。jsp可以很方便的在页面中通过java代码嵌入动态页面JSP原理分析下面是一个简单的hello world程序<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>index</title> </head> <body> <% out.println("hello world"); %> </body> </html>我们将其部署到tomcat服务器上,启动并访问它之后会在tomcat所在目录的 work\Catalina\localhost\JSPDemo\org\apache\jsp (其中JSPDemo是项目名称), 在这个目录下面可以看到生成了一个index_jsp.java、index_jsp.class下面是这个jsp生成的部分源码package org.apache.jsp; import javax.servlet.*; import javax.servlet.http.*; import javax.servlet.jsp.*; public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { static { _jspx_imports_packages = new java.util.HashSet<>(); _jspx_imports_packages.add("javax.servlet"); _jspx_imports_packages.add("javax.servlet.http"); _jspx_imports_packages.add("javax.servlet.jsp"); _jspx_imports_classes = null; } public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html;charset=UTF-8"); out.write("\n"); out.write("\n"); out.write("<html>\n"); out.write(" <head>\n"); out.write(" <title>index</title>\n"); out.write(" </head>\n"); out.write(" <body>\n"); out.write(" "); //这里是java代码的开始 out.println("hello world"); //这里是java代码的结尾 out.write("\n"); out.write(" </body>\n"); out.write("</html>\n"); } catch (java.lang.Throwable t) { //todo someting } finally { //todo someting } } }我们查询一下HttpJspBase这个类可以得到如下继承关系java.lang.Object | +--javax.servlet.GenericServlet | +--javax.servlet.http.HttpServlet | +--org.apache.jasper.runtime.HttpJspBase也就是说jsp本质上还是一个Servlet类,当我们第一次访问这个jsp页面时,服务器会根据jsp代码生成一个Servlet类的.java源码文件然后编译。对比jsp代码可以看得出来,在翻译的时候它逐行翻译,将html代码采用out.write进行输出,对应的java代码则原封不动的放在对应的位置。既然它是一个servlet,那么他的生命周期与相关注意事项就与Servlet相同了。jsp语法jsp确实简化了用户界面的编写,但是如果只知道原理,而不知道如何使用它仍然是白瞎,这部分来简单聊聊如何使用它jsp的代码主要放在3种标签中<% code %>: 这种格式中的代码,主要放的是要执行的java代码,它们最后会被解析到类的service方法中<%! code %>: 这种格式中的代码,主要包含的是成员变量的定义,它们最后会被解析到类的成员变量定义中<%= code %>: 这种格式中的代码,最终会被输出到页面上,会被解析到 out.print中进行输出下面我们对index.jsp进行改造,做一个简单的统计页面访问量的功能:<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>index</title> </head> <body> <%! private int totalVisited = 0; %> <% totalVisited++; %> <%= "客户请求次数:" + totalVisited %> </body> </html>然后再看看生成的java代码public final class index_jsp extends org.apache.jasper.runtime.HttpJspBase implements org.apache.jasper.runtime.JspSourceDependent, org.apache.jasper.runtime.JspSourceImports { private int totalVisited = 0; public void _jspInit() { } public void _jspDestroy() { } public void _jspService(final javax.servlet.http.HttpServletRequest request, final javax.servlet.http.HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException { final java.lang.String _jspx_method = request.getMethod(); final javax.servlet.jsp.PageContext pageContext; javax.servlet.http.HttpSession session = null; final javax.servlet.ServletContext application; final javax.servlet.ServletConfig config; javax.servlet.jsp.JspWriter out = null; final java.lang.Object page = this; javax.servlet.jsp.JspWriter _jspx_out = null; javax.servlet.jsp.PageContext _jspx_page_context = null; try { response.setContentType("text/html;charset=UTF-8"); out.write("\n"); out.write("\n"); out.write("<html>\n"); out.write(" <head>\n"); out.write(" <title>index</title>\n"); out.write(" </head>\n"); out.write(" <body>\n"); out.write(" "); out.write("\n"); out.write("\n"); out.write(" "); totalVisited++; out.write("\n"); out.write("\n"); out.write(" "); out.print( "客户请求次数:" + totalVisited ); out.write("\n"); out.write(" </body>\n"); out.write("</html>\n"); } }jsp内置对象我们在写jsp页面时关注的其实是Servlet的service 方法,谈及jsp内置对象的时候主要关注的是service中定义的相关变量,从生成的代码上来看,我们可以使用的是service方法中的输入参数request和response 再加它事先定义好的9个局部变量。它们的含义如下:HttpServletRequest request: 请求对象,之前在HttpServlet中已经了解了它该如何使用javax.servlet.jsp.PageContext pageContext: 页面的上下文,提供对JSP页面所有对象以及命名空间的访问。可以用它拿到request、cookie、session等一系列页面中可以访问到的对象HttpSession session: 当前会话ServletContext application: servlet上下文,我们可以拿到servlet的相关对象。比如获取当前servlet对象的名称,然后拼接一个路径。这样就不用考虑如何部署的问题JspWriter out: 输出对象HttpServletResponse response: HTTP响应对象Object page: 从定义和初始化值来看,它代表的是当前Servlet对象ServletConfig config: ServletConfig类的实例,获取当前servlet的配置信息Except: 当前异常,只有当jsp页面是错误页面是才能使用这个对象。其他的东西基本上用不上,这里也就不再介绍了。指令通过上面的相关知识点,现在已经能写相关的jsp代码了,但是既然本质上是servlet类,那么java其他的操作,比如导入相关库文件怎么办呢?这就需要用到对应的jsp指令。jsp指令放在 <%@ code %>中,jsp指令主要有3大类:page: 定义网页依赖属性,比如脚本语言、error页面、缓存需求等等include: 包含其他文件,可以利用这个属性事先抽取出页面的公共部分(比如页面的头部导航栏和页脚部分),最后再用include做拼接。taglib: 引入标签库的定义, 这个在使用jstl 和es表达式等第三方jsp扩展库的时候使用每条指令可以有多个属性,page 指令的相关属性如下:属性含义contentType等同于 response.setContentType方法,用于设置响应头的Content-Type属性pageEncoding设置jsp页面自身的编码方式language定义jsp脚本所使用的语言,目前只支持java 语言import导入java包errorPage当前页面发生异常后会自动跳转到指定错误页面isErrorPage标识当前页面是否是错误页面,错误页面中可以使用exception 对象,用来捕获异常include 指令的相关属性如下:属性含义file包含的文件路径taglib 的属性如下:属性含义prefix前缀,它们是自定义的,将来要用lib中的标签时用它作为前缀uri第三方库所在路径
2019年11月17日
4 阅读
0 评论
0 点赞
2019-11-03
Servlet 会话
在网络的七层模型中,会话层位于传输层之上,它定义如何开始、控制和结束一个会话。七层模式目前仅仅处于理论阶段,但是Web中借鉴了其中的一些思路。在Web中浏览器第一次发送请求到服务器开始直到一方断开为止算作一个会话。HTTP协议本身没有状态,那么Web服务如何知道这次请求是否在一个会话中呢?Web提供了Cookie和Session两种技术。服务器在第一次收到请求之后,会在HTTP响应头的Set-Cookie中,设置Cookie值,浏览器收到响应后,保存这个Cookie在本地。后续再进行请求的时候在HTTP的请求头中设置Cookie值,服务器根据此Cookie来识别请求的状态。Cookie值本身是一个键值对,例如 Cookie: name=value;Servlet 使用Cookie在Servlet中,使用Cookie的步骤如下:创建Cookie对象 new Cookie(String name, String value)发送cookie到浏览器 response.addCookie(Cookie)获取浏览器中发送过来的cookie request.getCookies() 返回所有Cookie遍历Cookies 获取所有cookie对象调用Cookie.getName(), Cookie.getValue()获取Cookie中的键和值使用的注意事项如下:一次可以返回多个Cookie,多次调用response.addCookie即可默认情况下浏览器关闭页面后cookie失效,但是可以设置cookie失效时间Cookie虽然可以用来识别一次会话,但是也不能滥用,第一Cookie是存储在浏览器端的,可以被伪造,一般做过爬虫自动登录的都这样干过,第二浏览器对于单个cookie大小有限制,一般是4kb。同时浏览器对于单个域名的cookie也有限制,默认是20个。由于cookie本身是类似于小饼干的小料,一般来说不会把小料作为主菜。SessionCookie一般作为小料,作为会话标识来说,用Session更为常见。与 Cookie相比Session存储在服务器端,Session没有cookie的那些限制。实现原理Session的实现是基于Cookie的。第一次调用request.getSession获取Session,没有Cookie 会在内存中创建一个新的Cookie对象,名称为JSESSION值是一个唯一的ID,作为session的唯一标识在给客户端响应时会包含一个cookie值,Set-Cookie: JSESSION=ID浏览器在下一次访问web中的其他资源时会将cookie作为请求头发送到服务器。服务器会从cookie中取出ID值,并根据ID从内存中查找对应的Session对象使用 HttpSession session = request.getSession(); 来获取一个Session对象函数列表Session 对象常用函数如下:public Object getAttribute(String name); //该方法返回在该 session 会话中具有指定名称的对象,如果没有指定名称的对象,则返回 null。 public Enumeration getAttributeNames(); //该方法返回 String 对象的枚举,String 对象包含所有绑定到该 session 会话的对象的名称。 public long getCreationTime(); //该方法返回该 session 会话被创建的时间,自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 public String getId(); //该方法返回一个包含分配给该 session 会话的唯一标识符的字符串。 public long getLastAccessedTime(); //该方法返回客户端最后一次发送与该 session 会话相关的请求的时间自格林尼治标准时间 1970 年 1 月 1 日午夜算起,以毫秒为单位。 public int getMaxInactiveInterval(); //该方法返回 Servlet 容器在客户端访问时保持 session 会话打开的最大时间间隔,以秒为单位。 public void invalidate(); //该方法指示该 session 会话无效,并解除绑定到它上面的任何对象。 public boolean isNew(); //如果客户端还不知道该 session 会话,或者如果客户选择不参入该 session 会话,则该方法返回 true。 public void removeAttribute(String name); //该方法将从该 session 会话移除指定名称的对象。 public void setAttribute(String name, Object value); //该方法使用指定的名称绑定一个对象到该 session 会话。 public void setMaxInactiveInterval(int interval); //该方法在 Servlet 容器指示该 session 会话无效之前,指定客户端请求之间的时间,以秒为单位。
2019年11月03日
5 阅读
0 评论
0 点赞
2019-10-27
Servlet 常用类
Servlet 是一套标准的接口规范,当用户通过web请求来访问服务器时,由web容器根据配置调用我们实现的对应的servlet对象来提供服务。同时为了方便开发,servlet标准中也提供了许多常用的工具类,比如基本的Request 和Response对象以及其他要说到的常用的类。ServletRequest 对象Servlet接口中的service方法的定义如下:public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;从接口上来看,我们只能在service方法中使用这个对象。当用户请求到达服务器时,服务器会将标准的HTTP请求包封装为一个 ServletRequest对象,并调用service将该对象作为参数传入。也就是说这个对象是与HTTP协议息息相关的下面是它的一些常用方法//请求行相关方法 public String getMethod(); //返回请求的方法 public String getContextPath(); //获取请求的虚拟目录(或者项目所在目录) public String getServletPath(); //获取servlet路径 public String getQueryString();// 获取查询字符串,也就是提交请求url中参数部分 public String getRequestURI(); //获取请求的URI public StringBuffer getRequestURL(); //获取请求的URL public String getProtocol(); //获取协议版本 public String getRemoteAddr(); //获取客户端的ip //请求头相关方法 public String getHeader(String name); //以字符串的形式返回请求头 public java.util.Enumeration<E> getHeaderNames(); //返回所有请求头名称的枚举 public java.util.Enumeration<E> getHeaders(String name); //返回指定请求头中的所有值的枚举 //请求体相关方法 public java.io.BufferedReader getReader() throws java.io.IOException; //获取请求体的字符流 public ServletInputStream getInputStream() throws java.io.IOException; //获取请求体中的字节流一般来说get方法会将请求参数放入到url中,而post则是将其放入到请求体中,根据上面的方法要获得请求参数,需要判断请求是get还是post,如果是get则采用 getQueryString, 如果是post则采用 getReader等方法。为了解决这个问题,Servlet提供了一些通用的方法。public String getParameter(String name); //获取请求参数,根据传入的键名获得请求的键值 public java.util.Map<K, V> getParameterMap(); //获取所有请求参数,以map方式返回 public String[] getParameterValues(String name); //如果请求中一个键对应多个值,可以使用这个方法获取所有的值请求中文乱码的问题一般HTML前端都使用utf-8编码,如果后台使用系统默认的编码则会可能造成乱码。我们可以先指定编码方式为utf-8,代码如下:request.setCharacterEncoding("UTF-8");请求转发为了保证程序模块的合理性,每个servlet 提供指定的功能,而有时候用户需要多个servlet来联合提供服务。这个时候需要使用请求转发。通过 request.getRequestDispatcher 方法来转发到其他servlet。它的特点如下:浏览器地址栏不变只能访问当前服务器的内部资源,也就是同一套web程序中的其他servlet转发是一次HTTP请求域对象每个servlet都有一个service。不同的servlet通过不同的serivce来提供服务。由于是不同的类对象和方法,不同的servlet之间不能进行信息的传递。但是有的时候需要不同的servlet之间进行数据的共享。这个时候可以使用域对象域对象是一个有作用范围的对象,域对象中数据随着对象的消亡而消亡。servlet中有下列几种作用域:一次请求, resquest一次会话, cookie, session使用域对象的 setAttribute 来保存数据, 使用getAttribute 来获取数据。使用removeAtribute 来移除数据response 对象对HTTP 响应的封装,常用的方法如下:public void setStatus(int sc);//设置响应的状态码 public void setHeader(String name, String value); //设置响应头 public java.io.PrintWriter getWriter() throws java.io.IOException; //获取输出的字符流流 public ServletOutputStream getOutputStream() throws java.io.IOException; //获取输出的字节流重定向重定向的原理是利用HTTP协议中的301和302 消息。在HTTP响应头中指定状态码为302,并指定Location字段,浏览器会根据响应再重新发送一次请求。servlet中,提供了专门的方法来实现重定向。public void sendRedirect(String location) throws java.io.IOException;重定向的特点如下:重定向时,浏览器地址栏发生了变化重定向可以访问互联网任意节点的资源重定向是多次请求
2019年10月27日
4 阅读
0 评论
0 点赞
2019-10-13
Servlet
通过前面一系列的博客的梳理,学习了一下Java基础的编程知识,从我自己的感觉上来说,Java与c++的差距并不是很大,Java将c++做了更进一步的抽象,同时丢弃了c++中一些容易出错和难懂的部分。Java的基础语法比起c++来说要简单很多。但是Java与c++一样,不能光学基础语法,还得从应用角度来学习它,使用它来做一些真真的项目。从现在开始博客内容从Java的基础过渡到了Java2E的学习Servlet 简介Servlet 英文全称是Server Applet,也叫做Java Servlet;是用Java编写的服务器端程序。其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类,一般情况下,人们将Servlet理解为后者。Servlet运行于支持Java的应用服务器中。从实现上讲,Servlet可以响应任何类型的请求,但绝大多数情况下Servlet只用来扩展基于HTTP协议的Web服务器。———— 来自维基百科Servlet 容器从上面的定义来看 Servlet 就是一套Java的接口标准,在编写Java Web程序的时候需要遵循这套标准的接口,实现自己的Servlet 接口,并且由服务器端程序来调用,并向其他用户提供服务。目前市面上常见的支持Servlet 标准的Web容器有:Tomcat: 由Apache 基金会的一个项目,由 Apache、Sun和其他公司及个人合作开发而成。Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。WebLogic: WebLogic是美国Oracle公司出品的一个application server;WebLogic是用于开发、集成、部署和管理大型分布式Web应用、网络应用和数据库应用的Java应用服务器,量级比Tomcat 要大上许多,同时也能承载更多用户的使用JBoss:是一个基于J2EE的开放源代码的应用服务器。 JBoss代码遵循LGPL许可,可以在任何商业应用中免费使用;但JBoss核心服务不包括支持servlet/JSP的WEB容器,一般与Tomcat或Jetty绑定使用。WebSphere: 是由IBM遵照开放标准,例如Java EE、XML及Web Services,开发并发行的一种应用服务器Servlet架构一般一个web项目中,主要分为WEB-INFO目录和一些jsp/html页面,其中WEB-INFO中主要包含 classes 目录(java 字节码文件)、web.xml(项目配置文件)、lib目录(项目依赖文件)在web.xml 中会指定哪些url由哪些Servlet 接口的实现类来处理,当url到达web服务器后,由服务器处理并调用对应的Servlet 类;Web容器要实现这个功能,必然会用到反射机制当用户在浏览器中输入对应的url并点击回车后:浏览器会向对应的地址发送 HTTP请求包对应的服务器收到这个请求包,并获取到请求的路径根据请求路径找到对应的项目在项目中查找对应的web.xml 文件,找到这个路径对应的Servlet 类调用Servlet 类并拿到返回值将返回封装到Http 的响应中,响应到浏览器上Servlet使用Servlet普通Servlet 接口定义如下:public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy(); }要回答这些接口函数都是干什么的,首先需要知道 一个Servlet对象的生命周期。当项目所在的Web容器启动之后,容器中的所有项目也随之启动,这个时候项目中所有的Servlet 被创建,当容器正常关闭之前,会停止所有项目并析构回收所有Servlet 资源。init方法:每当Servlet 创建的时候调用一次,并且仅调用这一次。,所有我们可以将一些在项目启动之时需要提前做的操作放到这个方法中service方法:用于提供服务,每当用户通过浏览器或者其他方式访问该servlet时,服务器会产生一个新的线程并调用该方法一次,该方法用户为用户提供服务并返回处理结果。destroy方法:当servlet正常被Web容器关闭的时候会首先调用该方法,用于清理某些资源并做最后的收尾工作。实例为了方便进行Web 开发,JavaEE中提供了一个HttpServlet 类,它封装了基本的Servlet 接口,并提供了一些列的doXXX方法来处理HTTP 协议中几种不同的请求方法,例如doGet和doPost等等,下面的例子就使用这个类来进行编写import java.io.*; import javax.servlet.*; import javax.servlet.http.*; // 扩展 HttpServlet 类 public class HelloWorldServlet extends HttpServlet { private String message; public void init() throws ServletException { // 执行必需的初始化 System.out.println("init........"); message = "Hello World"; } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 设置响应内容类型 response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<h1>" + message + "</h1>"); } public void destroy() { // 什么也不做 } }代码写完后,可以配置对应的web.xml 文件,其中重要的部分如下:<web-app><!--web项目置于次标签内--> <servlet> <servlet-name>HelloWorld</servlet-name> <!--目录名称--> <servlet-class>HelloWorldServlet</servlet-class> <!--对应类名称--> </servlet> <servlet-mapping> <servlet-name>HelloWorldServlet</servlet-name> <url-pattern>/HelloWorld</url-pattern> </servlet-mapping> </web-app> 最终访问 http://localhost:8080/HelloWorld 时会调用HelloWorldServlet 类,并返回helloworld字符串到浏览器中
2019年10月13日
6 阅读
0 评论
0 点赞
2019-09-08
Java 网络编程
Java 中网络编程接口在java.net 包中在使用C/C++进行网络编程时,针对TCP Server端需要这些操作创建SOCKET绑定监听接受连接收取数据包发送数据包TCP Client端需要这些操作创建SOCKET连接Server端发送数据包读取响应包Java中针对Server 端和Client端分别提供了两个类 ServerSocket 和 SocketServerScoket 使用步骤如下:创建ServerSocket 对象并传入一个端口号到构造函数中。在构造的时候会自动创建Socket对象并执行绑定端口、监听端口的操作调用对象的 accept 方法等待连接调用对象的 getInputStream 和 getOutputStream 获取输入输出流,并通过输入输出流来进行收发数据在不用时调用 close 方法关闭套接字Socket 类使用步骤如下:创建 Socket 对象调用 connet 方法连接到指定服务器端口(或者在构造时传入服务器和端口进行连接)调用对象的 getInputStream 和 getOutputStream 获取输入输出流,并通过输入输出流来进行收发数据在不用时调用 close 方法关闭套接字一个普通的TCP通信的实例如下:import java.net.ServerSocket; import java.net.Socket; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; public class TCPServer{ public static void main(String[] args) throws IOException{ ServerSocket server = new ServerSocket(6666); Socket socket = server.accept() ; InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); byte[] bytes = new byte[1024]; int len = is.read(bytes); os.write(("echo:" + new String(bytes, 0, len)).getBytes()); server.close(); socket.close(); } }import java.net.Socket; import java.io.IOException; import java.io.OutputStream; import java.io.InputStream; public class TCPClient{ public static void main(String[] args)throws IOException{ Socket socket = new Socket("127.0.0.1", 6666); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); os.write("hello world!".getBytes()); byte[] bytes = new byte[1024]; int len = is.read(bytes); System.out.println(new String(bytes, 0, len)); socket.close(); } }下面是一个使用TCP协议进行文件传输的例子:import java.net.Socket; import java.io.IOException; import java.io.FileOutputStream; import java.io.InputStream; import java.net.ServerSocket; public class FileUploadServer{ public static void main(String[] args) throws IOException{ ServerSocket server = new ServerSocket(6666); FileOutputStream fos = new FileOutputStream("1.avi"); Socket socket = server.accept(); InputStream is = socket.getInputStream(); byte[] buff = new byte[1024]; int len = 0; while((len = is.read(buff)) != -1){ fos.write(buff, 0, len); } System.out.println("upload success!"); fos.close(); server.close(); socket.close(); } }import java.net.Socket; import java.io.IOException; import java.io.FileInputStream; import java.io.OutputStream; public class FileUploadClient{ public static void main(String[] args) throws IOException{ Socket socket = new Socket("127.0.0.1", 6666); FileInputStream fis = new FileInputStream("1.avi"); OutputStream os = socket.getOutputStream(); int len = 0; byte[] buff = new byte[1024]; while((len = fis.read(buff)) != -1){ os.write(buff, 0, len); } fis.close(); socket.close(); } }最后提供一个简易的http server 的例子import java.net.Socket; import java.net.ServerSocket; import java.io.InputStreamReader; import java.io.InputStream; import java.io.FileInputStream; import java.io.OutputStream; import java.io.BufferedReader; import java.io.IOException; public class HttpServer{ public static void main(String[] args)throws IOException{ ServerSocket server = new ServerSocket(8080); while(true){ Socket socket = server.accept(); new Thread(()->{ try{ BufferedReader bf = new BufferedReader(new InputStreamReader(socket.getInputStream())); OutputStream os = socket.getOutputStream(); String head = bf.readLine(); String path = head.split(" ")[1].substring(1); System.out.println(path); FileInputStream fis = new FileInputStream(path); int len = 0; byte[] buff = new byte[1024]; os.write("HTTP/1.1 200 OK\r\n".getBytes()); os.write("Content-Type:text/html\r\n".getBytes()); os.write("\r\n".getBytes()); while((len = fis.read(buff)) != -1){ os.write(buff, 0, len); } bf.close(); fis.close(); socket.close(); }catch(IOException e){ e.printStackTrace(); } }).start(); } //server.close(); } }
2019年09月08日
4 阅读
0 评论
0 点赞
2019-09-01
Java 注解与单元测试
注解Java注解是在JDK1.5 之后出现的新特性,用来说明程序的,注解的主要作用体现在以下几个方面:编译检查,例如 @Override编写文档,java doc 会根据注解生成对应的文档代码分析,通过注解对代码进行分析[利用反射机制]JDK 中有一些常用的内置注解,例如:Override:检查被该注解修饰的方法是否是重写父类的方法Deprecatedd:被该注解标注的内容已过时SuppressWarnning: 压制警告,传入参数all表示压制所有警告自定义注解JDK中虽然内置了大量注解,但是它也允许我们自定义注解,这样就为程序编写带来了很大的便利,像有些框架就大量使用注解。java注解本质上是一个继承了 java.lang.annotation.Annotation 接口的一个接口,但是如果只是简单的使用关键字 interface来定义接口,仍然不是注解,仅仅是一个普通的接口,在定义注解时需要使用关键字 @interface, 该关键字会默认继承 Annotation 接口,并将定义的接口作为注解使用注解中可以定义方法,这些方法的返回值只能是基本类型、String、枚举类型、注解以及这些类型的数组,我们称这些方法叫做属性。在使用注解时需要注意以下几个事情必须给注解的属性赋值,如果不想赋值可以使用default来设置默认值如果属性列表中只有一个名为value的属性,那么在赋值时可以不用指定属性名称多个属性值之间使用逗号隔开数组属性的赋值使用 {}, 而当数组属性中只有一个值时, {} 可以省略不写元注解元注解是用来描述注解的注解,Java中提供的元注解有下列几个Target描述注解能够作用的位置,即哪些Java代码元素能够使用该注解,注解的源代码如下:@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { ElementType[] value(); }这个注解只有一个value属性,属性需要传入一个 ElementType枚举类型的数组,该枚举类型可以取下列几个值ElementType含义TYPE接口、类(包括注解)、枚举类型上使用FIELD字段声明(包括枚举常量)METHOD方法PARAMETER参数声明CONSTRUCTOR构造函数LOCAL_VARIABLE局部变量声明ANNOTATION_TYPE注解类型声明PACKAGE包声明Retention表示该注解类型的注解保留的时长,主要有3个阶段: 源码阶段,类对象阶段,运行阶段;源码阶段是只只存在与源代码中,类对象阶段是指被编译进 .class 文件中,类对象阶段是指执行时被加载到内存.则默认保留策略为RetentionPolicy.CLASS。它的源码如下:@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }Documented表示拥有该注解的元素可通过javadoc此类的工具进行文档化。源码如下:@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { }Inherited表示该注解类型被自动继承@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { }内置注解解读下面通过几个JDK内置注解的解读来说明注解相关使用Override@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }该注解用于编译时检查,被该注解注释的方法是否是重写父类的方法。从源码上看,它只能在方法上使用,并且它仅仅存在于源码阶段不会被编译进 .class 文件中Deprecatedd@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }用于告知编译器,某一程序元素(例如类、方法、属性等等)不建议使用从源码上看,几乎所有的Java程序元素都可以使用它,而且会被加载到内存中SuppressWarnning@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { String[] value(); }告知编译器忽略特定类型的警告它需要传入一个字符串的数组,取值如下:参数含义deprecation使用了过时的类或方法时的警告unchecked执行了未检查的转换时的警告fallthrough当Switch程序块进入进入下一个case而没有Break时的警告path在类路径、源文件路径等有不存在路径时的警告serial当可序列化的类缺少serialVersionUID定义时的警告finally任意finally子句不能正常完成时的警告all以上所有情况的警告在程序中解析注解一般通过反射技术来解析自定义注解,要通过反射技术来识别注解,前提条件就是注解要在内存中被加载也就是要使它的范围为 RUNTIME;JDK提供了以下常用API方便我们使用返回值方法解释TgetAnnotation(Class annotationClass)当存在该元素的指定类型注解,则返回相应注释,否则返回nullAnnotation[]getAnnotations()返回此元素上存在的所有注解Annotation[]getDeclaredAnnotations()返回直接存在于此元素上的所有注解。booleanisAnnotationPresent(Class<? extends Annotation> annotationClass)当存在该元素的指定类型注解,则返回true,否则返回false实战下面使用一个完整的例子来说明自定义注解以及在程序中使用注解的例子,现在来模仿JUnit 定义一个MyTest的注解,只要被这个注解修饰的方法将来都会被自动执行import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.annotation.ElementType; @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest { }首先定义一个注解,后续来执行用这个注解修饰了的所有方法,通过Target来修饰标明注解只能用于方法上,通过Retention修饰标明注解会被保留到运行期import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class Test { @MyTest public void test1(){ System.out.println("this is test1"); } @MyTest public void test2(){ System.out.println("this is test2"); } public static void main(String[] args) { Method[] methods = Test.class.getMethods(); for (Method method:methods){ if (method.isAnnotationPresent(MyTest.class)){ try { method.invoke(new Test()); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } } } 在测试类中定义了两个测试函数都使用 @MyTest 修饰,在主方法中,首先通过反射机制获取该类中所有方法,然后调用方法的 isAnnotationPresent 函数判断该方法是否被 @Test修饰,如果是则执行该方法。这样以后即使再添加方法,只要被 @MyTest 修饰就会被调用。Junit框架在软件开发中为了保证软件质量单元测试是必不可少的一个环节,Java中提供了Junit 测试框架来进行单元测试一般一个Java项目每一个类都会对应一个test类用来做单元测试,例如有一个Person类,为了测试Person类会定义一个PersonTest类来测试所有代码JUnit 中定义了一些注解来方便我们编写单元测试@Test:测试方法,被该注解修饰的方法就是一个测试方法@Before:在测试方法被执行前会执行该注解修饰的方法@After:在测试方法被执行后会执行该注解修饰的方法除了注解JUnit定义了一些断言函数来实现自动化测试,常用的有如下几个:void assertEquals(boolean expected, boolean actual):检查两个变量或者等式是否平衡void assertTrue(boolean expected, boolean actual):检查条件为真void assertFalse(boolean condition):检查条件为假void assertNotNull(Object object):检查对象不为空void assertNull(Object object):检查对象为空void assertSame(boolean condition):assertSame() 方法检查两个相关对象是否指向同一个对象void assertNotSame(boolean condition):assertNotSame() 方法检查两个相关对象是否不指向同一个对象void assertArrayEquals(expectedArray, resultArray):assertArrayEquals() 方法检查两个数组是否相等这些函数在断言失败后会抛出异常,后续只要查看异常就可以哪些测试没有通过假设先定义一个计算器类,来进行两个数的算数运算public class Calc { public int add(int a, int b){ return a + b; } public int sub(int a, int b){ return a - b; } public int mul(int a, int b){ return a * b; } public float div(int a, int b){ return a / b; } }为了测试这些方法是否正确,我们来定义一个测试类import org.junit.Test; import static org.junit.Assert.assertEquals; public class CalcTest { @Test public void addTest(){ int result = new Calc().add(1,2); assertEquals(result, 3); } @Test public void subTest(){ int result = new Calc().sub(1,2); assertEquals(result, -1); } @Test public void mulTest(){ int result = new Calc().mul(1,2); assertEquals(result, 2); } @Test public void divTest(){ float result = new Calc().div(1,2); assertEquals(result, 0.5, 0.001); //会报异常 } }经过测试发现,最后一个divTest方法 会报异常,实际值是0,因为我们使用 / 来计算两个int时只会保留整数位,也就是得到的是0,与预期的0.5不匹配,因此会报异常
2019年09月01日
6 阅读
0 评论
0 点赞
2019-08-25
Java数据库操作
数据库操作是程序设计中十分重要的一个部分,Java内置JDBC来操作数据库JDBC使用JDBC——Java Database connecting Java数据库连接;本质上JDBC定义了操作数据库的一套接口,作为应用程序的开发人员来说只需要创建接口对应的对象即可,而接口的实现由各个数据库厂商去完成。要在应用程序中使用JDBC,需要根据数据库的不同导入对应的jar包。使用步骤如下:导入相应jar包注册驱动获取数据库连接对象定义sql语句获取执行sql语句的对象执行sql并获取结果集对象从结果集中获取数据释放资源相关对象的描述DriverManager在使用JDBC之前需要先注册驱动,也就是告诉JDBC,我们需要导入哪个jar包,这个工作由DriverManager对象来实现,可以调用它里面的方法 registerDriver 来实现,该方法的定义如下:static void registerDriver(Driver driver);这个方法需要传入一个driver 对象,driver对象是具体的数据库厂商来实现,后续相关操作其实是根据这个driver对象来调用相关代码,实现同一套接口操作不同数据库我们查阅相关实现类的代码如下:public class Driver extends NonRegisteringDriver implements java.sql.Driver { // // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } /** * Construct a new driver and register it with DriverManager * * @throws SQLException * if a database error occurs. */ public Driver() throws SQLException { // Required for Class.forName().newInstance() } }在Driver对象中发现,它在静态代码块中执行了registerDriver方法,也就是说我们只要加载对应的类,类就会自动帮助我们进行注册的操作。所以在第一步注册驱动的代码中可以这样写:Class.forName("org.mariadb.jdbc.Driver"); //加载对应的Driver类到内存中Connection对象注册了驱动之后就是获取数据库的连接对象,在DriverManager中使用getConnection方法获取,它的定义如下:static Connection getConnection(String url); static Connection getConnection(String url, Properties info); static Connection getConnection(String url, String user, String password);上述3个方法中,常用的是第3个,参数分别为: 连接字串、用户名、密码连接字串的格式为: jdbc:数据库类型://数据库IP:端口/数据库名称,比如 jdbc:mariadb://localhost:3306/test获取连接字串的代码如下:Connection conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/study", "root", "root");执行sql语句获取连接对象之后,需要向数据库传递sql语句并执行它,执行sql语句需要使用对象 Statement, 常用的方法如下:boolean execute(String sql); ResultSet executeQuery(String sql); int executeUpdate(String sql);一般可以使用execute来执行相关操作,如果是查询语句,可以使用executeQuery来执行并获取返回的结果集,如果需要执行DELTE、UPDATE、INSERT等语句可以使用executeUpdate来更新数据库我们可以通过 Connection对象的createStatement方法获取一个Statement对象,代码如下:Statement statement = conn.createStatement(); String strSql = "INSERT INTO student VALUES(2, '2b', 28, 78.9, '2017-12-30', NULL)"; statement.execute(strSql); statement.close(); //最后别忘了关闭对象获取返回结果如果我们执行了像insert、delete、update等等语句,可能不需要关注具体的返回结果,但是如果使用的是select语句,则需要获取返回的结果获取select语句返回的结果可以使用 executeQuery 方法,该方法会返回一个结果集对象可以将结果集对象想象成一个二维的数组,保存了查询到的相关数据,每一行代表一条数据,行中的每一列是一个字段的数据。结果集中使用游标来遍历每一行数据。使用get相关函数来获取对应索引的数据。一行遍历完了使用next移动到下一行;其中get相关方法主要有:Blob getBlob(int columnIndex); Blob getBlob(String columnLabel); boolean getBoolean(int columnIndex); boolean getBoolean(String columnLabel); byte getByte(int columnIndex); byte getByte(String columnLabel); byte[] getBytes(int columnIndex); byte[] getBytes(String columnLabel); Date getDate(int columnIndex); Date getDate(int columnIndex, Calendar cal); Date getDate(String columnLabel); Date getDate(String columnLabel, Calendar cal); double getDouble(int columnIndex); double getDouble(String columnLabel); float getFloat(int columnIndex); float getFloat(String columnLabel); int getInt(int columnIndex); int getInt(String columnLabel); long getLong(int columnIndex); long getLong(String columnLabel);在获取了结果之后需要关闭对应对象清理资源,这部分只需要调用对应的cloase方法即可最终一个完整的demo 如下:public class JDBCDemo1 { public static void main(String[] args) { Connection conn = null; Statement statement = null; ResultSet resultSet = null; try { Class.forName("org.mariadb.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mariadb://localhost:3306/test", "root", "root"); String sql = "select * from student"; statement = conn.createStatement(); resultSet = statement.executeQuery(sql); while (resultSet.next()){ int id = resultSet.getInt(1); //注意:这里面的索引是从1开始的 String name = resultSet.getString(2); int age = resultSet.getInt(3); double score = resultSet.getDouble(4); Date birthday = resultSet.getDate(5); Timestamp insertTime = resultSet.getTimestamp(6); System.out.println(id + "\t" + name + "\t" + age + "\t" + score + "\t" + birthday + "\t" + insertTime); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally { try{ if (resultSet != null){ resultSet.close(); } if (statement != null){ statement.close(); } if (conn != null){ conn.close(); } }catch (SQLException e){ e.printStackTrace(); } } } }参数化查询我们知道使用sql拼接的方式来执行sql语句容易造成sql注入漏洞,即使针对某些关键字进行过滤也很难消除这个漏洞,一旦存在sql注入,那么数据库中的数据很容易就会被黑客窃取。而使用参数化查询的方式可以从根本上消除这个漏洞。jdbc中参数化查询使用的对象是 PreparedStatement, 它与Statement对象不同在于,它会提前将sql语句进行编译,后续只会接收固定类型的参数;而Statement只会简单的去执行用户输入的sql语句。在进行参数化查询的时候需要先准备sql语句,但是在查询参数中需要使用 ? 做标记,表示这个位置是一个参数,后续在真正执行前再传入,比如说可以准备这样的sql语句 update student set score = 100 where name = ?。准备好sql语句之后,需要设置对应参数位置的值,我们可以使用 setXxx 方法来设置,setXxx 方法与之前介绍的get方法类似,根据不同的数据类型 Xxx 有不同的取值。设置完参数之后,与Statement 一样,调用对应的execute方法来执行即可.String sql = "update student set score = 100 where name = ?"; ps = conn.prepareStatement(sql); ps.setString(1, "2b"); ps.executeUpdate();数据库连接池在需要频繁操作数据库的应用中,使用数据库连接池技术可以对数据库操作进行一定程度的优化。原理请自行百度。如果要自己实现数据库连接池需要实现 javax.sql.DataSource 的getConnection方法。当然我学习Java只是为了学习一下Web相关的内容,并不想太过于深入,所以自然不会去管怎么实现的,只要调用第三方实现,然后使用就好了。常见的开源的第三方库有: Apache commons-dbcp、C3P0 、Apache Tomcat内置的连接池(apache dbcp)、druid(由阿里巴巴提供)。本着支持国产的心态,这次使用的主要是 druid。druid 连接池需要提供一个配置文件来保存数据库的相关内容driverClassName=org.mariadb.jdbc.Driver url=jdbc:mariadb://localhost:3306/study username=root password=masimaro_1992 # 初始化时连接池中保留连接数 initialSize=5 # 最大连接数 maxActive=10 # 最大时间,超过这个时间没有任何操作则会关闭连接 maxWait=3000在使用时主要需要如下步骤:加载配置文件调用 DruidDataSourceFactory.createDataSource 方法传入 配置,获取到 DataSource 对象调用DataSource.getConnection 方法获取Connection 对象执行后续操作相关代码如下:Connection conn = null; Statement statement = null; Properties properties = new Properties(); try { properties.load(JDBCDemo3.class.getResourceAsStream("druid.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); conn = dataSource.getConnection(); statement = conn.createStatement(); //do something } catch (IOException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); }
2019年08月25日
5 阅读
0 评论
0 点赞
1
2
3
4
...
9