1. 前后端交互基础
- 通过超链接跳转传递数据
www.test.com?id=1 与form表单不同的是,uri中的id=1可能是从a标签中直接获取的
- 通过form表单
1.1 form
- aciton:请求服务器资源(URL),对应控制器的Mapping
- name:后端使用,区分唯一(可重名,后端用容器接收)
- id:前端使用,区分唯一
1.2 引用路径的区别
- 1)href:指定资源路径,将当前元素与资源之间建立联系
- 2)src:指向外部资源路径,将该资源应用到当前元素中 (替换作用)
- 3)action:将本页面的请求传递给目标路径
1.3 RESTful
RESTful是一种前后端交互的规范API。可以理解为“一种约定成俗的编程习惯”。
通常情况,只涉及到前后端请求方法上的约定。
本质上是通过幂等来区分
幂等:概念源于离散数学,用于判断两个关系是否幂等。可理解为“重复操作不改变结果”。
GET、PUT、DELETE都是幂等的
- 同一个GET无论多少次,获得的资源都是一样的
- 同一个PUT无论多少次,更新结果依旧一样
- 同一个DELETE结果都是一样的,那就是“该对象”被删除,不会有副作用。
POST非幂等
- 同一个POST,请求多次,每个请求都会各自生成一个结果,因此非幂等。
一般情况下,可以按照功能区分。
增:POST 删:DELETE 改:PUT 查:GET
REST-CRUD设计实例
会涉及到一点点后端控制器的知识
功能 | 请求Uri | 对应返回视图 | 请求方式 | 访问后的操作 |
---|---|---|---|---|
查找所有user | users | userlist | Get | 从数据库获得数据,在页面显示所有user |
来到添加页面 | user | useradd | get | 从数据库中获取必要的提示数据(比如可以填写的部门)在页面展示 |
添加user | user | 重定向:userlist | post | 提交后从user中实现Dao增加,然后回到userlist显示最新数据 |
来到修改页面(回显) | User/1 | useradd | get | 利用PathVariable查询数据,然后将其回显到ueradd页面 |
修改user | User | 重定向:userlist | put | 在useradd中修改完毕后put到user,user中更新后返回到userlist显示最新数据 |
删除一个user | User/1 | 重定向:userlist | delete | 利用PathVariable实现删除,并返回uselist显示最新数据 |
添加页面和修改页面可以一页两用: 浏览器单击“添加”按钮的时候,只能回显部分必要的提示数据(model为null); 单击“修改”按钮的时候,能通过@pathvariable(Spring注解)查询到对应model:可通过model是否为null,分别展示不同的细节。
由于浏览器版本: 对于put、delete请求,需要在post表单内使用进行请求转换(固定写法) <input type=“hidden” name="_method" value=“delete”/>
在springboot2.x以上,Delete请求还得配置 spring.mvc.hiddenmethod.filter.enabled=true
1.4 GET和POST在报文上的区别
GET:URL后跟参数 POST:报文中空一行,再跟参数
1.5 URL中的特殊字符
序号 | 特殊字符 | 含义 | 十六进制值 |
---|---|---|---|
1. |
| URL 中 号表示空格 |
|
2. | 空格 | URL中的空格可以用 号或者编码 |
|
3. | / | 分隔目录和子目录 | / |
4. | ? | 分隔实际的 URL 和参数 | ? |
5. | % | 指定特殊字符 | % |
6. | # | 表示书签 | # |
7. | & | URL 中指定的参数间的分隔符 | & |
8. | = | URL 中指定参数的值 | = |
中文会自动进行编码
2. 手写web服务器
流程如下
2.1 获取请求协议
- 创建serversocket
- 建立连接,获取客户端的socket
- 通过输入流获取请求协议
- 分解请求协议中的内容
2.2 返回响应协议
- 准备内容
- 获取字节数组长度
- 拼接响应协议,注意空格与换行
- r : return 到当前行的最左边。
- n: newline 向下移动一行,并不移动左右。
- Linux中n表示回车 换行;
- Windows中rn表示回车 换行。
- Mac中r表示回车 换行。
- 使用输出流输出
封装式代码结构:
- 内部: - 动态添加content - 根据字节码拼接响应头 - 根据状态码返回相应协议
- 外部: - 传入content和状态码
2.3 Servlet,XML
- 通过url获取配置文件,反射成所需的class,再用servlet处理业务。
- 使用servlet(response,request),解耦代码,实现封装
以上内容作为总结性的文字参考,没有具体代码。 具体代码可查看尚硅谷java300集进行学习
3. JDBC的使用
JDBC只是Java提供的对外接口,具体的实现由数据库公司来实现。比如mysql或Oracle。
3.1 建立连接
代码语言:javascript复制//1.加载驱动类
Class.forName("com.mysql.jdbc.Driver");
//2.使用manager链接数据库,实际时使用socket进行远程连接,较耗时,真正开发中都是用连接池来管理连接对象
Connection con = DriverManager.getConnection("jdbc:mysql://localhost:3306/study?serverTimezone=UTC","root","123456");
凡是引入mysql-url的(无论是直接使用JDBC还是框架中引入mysql),都要加上serverTimezone=UTC,否则报错!
3.2 常用接口
- 1.Statement:用于执行静态的SQL语句并返回它生成结果的对象,
- a)三种实现类
- i.Statement:只能发送不带参数的简单sql语句,一般只用于批处理
- 1.addBatch(“sql语句”),插入语句
- 2.executeBatch(),执行批量插入
- ii.preparedStatement:效率高,防止SQL注入,普通需求使用这个
- iii.CallableStatement:继承自ii,由方法prePareCall创建,用于调用存储过程
- i.Statement:只能发送不带参数的简单sql语句,一般只用于批处理
- b)常用Statement方法
- i.Execute(),运行所有语句,返回是否有结果集
- ii.executeQuery();运行select语句,然后resultSet结果集
- iii.executeUpdate();运行insert/update/delete操作,返回更新行数
- a)三种实现类
- 2.ResultSet接口:
- a)Statement执行sql语句时返回resultset结果集
- b)Resultset提供的检索不同类型字段的方法,常用的有
- i.getString();获得在数据库里面时varchar、char等类型对象
- ii.getFloat();
- iii.getDate();
- iv.getBoolean();
- 3.依序关系使用对象及连接
- a)Resultset->statement->connection
3.3 事务
将多个事情组成一个事件集,这个事件集内的所有事件要么同时执行成功,要么同时失败,则称为事务。 是数据库操作的一个执行单元。
mysql默认自动提交事务,且每条语句都在单独的事务中 mysql相关操作
- start transaction 开启事务
- Rollback 回滚事务
- Commit 提交事务
ACID
- A:atomicity原子性
- 象征事件集是一个整体,不可分可,同生共死(要么…要么…)。
- C:consistency一致性
- 无论执行失败或成功,事务执行前后,数据应当是完整的,一致的。
- I:isolation隔离性
- 通过数据串行化,使得在同一时间仅有一个请求用于同一数据。保证在并发的情况下,多个用户对同一个数据进行操作的时候,不会相互影响。
- D:durability持久性
- 在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。即使数据库发生故障也不应该对其有任何影响。
事务的隔离级别
隔离级别分为:
- (脏读)READ UNCOMMITTED
- (不可重复读)READ COMMITTED
- (虚读幻读)REPEATABLE READ
- (序列化)SERIALIZABLE
不看定义,我们通过名字来理解。 个人以为英语更容易解释:
首先确定一个前提,既然称为“隔离”,那肯定至少有两个对象,一个“被隔离者”,一个“未被隔离者”。 因此,隔离级别都是用于描述两个事务之间的关系。
read uncommitted --> 读取了未提交的(数据)(oracle默认):一个事务读取了另一个事务未提交的数据,这就是“脏”(引用于汉语中的“贪脏了不属于你的东西”)
read committed --> 读取了已提交的(数据):这个比较难理解,事实上他还有个发生前提:先读了一个数据,然后再读的时候,才读取了已提交的。也就是说,在这个过程中,有第二者插手。 例子 A读取到存款还有10元,准备进行消费; B此时消费了5元; A消费的时候,本来刚刚查询的是10元,但是消费的时候(也进行了查询操作)却余额不足,两次一样的查询数据却不一样此时就是读取了已提交的,因此也称为“不可重复读”(重复读出现了不一致数据)
mysql默认级别是repeatable read,是可以解决这个问题的,所以初学者可能有疑惑,为什么会发生这种事情。因为这个级别,是允许两个事务同时对数据进行修改的
repeatable read --> 可重复读(mysql默认):既然可重复读,也就是说,当一个事务在进行读取的时候,不允许被其他事务修改。 还是上面那个例子: A读取到存款还有10元,准备进行消费; B此时想进行消费(修改操作),但是由于A正在操作,因此B操作失败。 A成功进行消费。 repeatable read用于锁定修改操作(Update),但是无法锁定插入操作(Insert) 当A进行重复读的时候,若在重复读期间,第三方进行了插入操作(这是被允许的),此时就出现了“幻觉”。 如: A查询存款为10元;但他有点不放心,决定再查一次 此时B存了5元; A刷新查询,发现变成了15!刚才出现幻觉了吗?(幻读)
serializable --> 序列化:此处的序列化跟java的对象序列化不是一个东西。“序列”最常见的含义是“有顺序的列”,因此,他的意思就是指,所有事务按顺序执行,一旦对一个数据开启了事务,其他人就只能排队等候。 这种自然能够排除任何数据错误,同样的,效率最低,几乎不使用。
- 1、READ UNCOMMITTED: 赃读、不可重复读、虚读都有可能发生。
- 2、READ COMMITTED: 避免赃读。不可重复读、虚读都有可能发生。(oracle默认的)
- 4、REPEATABLE READ:避免赃读、不可重复读。虚读有可能发生。(mysql默认)
- 8、SERIALIZABLE: 避免赃读、不可重复读、虚读。 级别越高,性能越低,数据越安全
JDBC中的事务隔离级别设置
设置隔离级别:必须在开启事务之前。 Connection.setTransactionIsolation(int level);
JDBC中的事务
事务开始于:
- 执行一条DML语句(INSERT、UPDATE或DELETE)
- 前一个事务结束后,又输入了另一条DML语句
事务结束于:
- 执行COMMIT(提交)或ROLLBACK(回滚)语句
- 执行DDL语句(create、alter、drop和 truncate),将自动执行COMMIT。
- 断开数据库连接
- 执行DML语句,该语句失败了,则会自动执行ROLLBACK。
注意: JDBC默认自动提交事务,若设为手动提交,只有提交之后才会真正执行DDL语句。 因此: commit()才是事务的结束,execute和statement不是。
一般来说,通过try-catch,在catch中回滚。
JDBC使用事务
- Connection.setAutoCommit(false); 开启事务
- Connection.rollback(); 回滚事务
- Connection.commit(); 提交事务
Savepoint
java.sql提供的方法,通过Savepoint可以实现部分提交、部分回滚。
需求:AB(必须),CD(可选)
代码语言:javascript复制Connection conn = null;
Savepoint savepoint = null; //保存点,记录操作的当前位置,之后可以回滚到指定的位置。(可以回滚一部分)
try{
//1 获得连接
conn = ...;
//2 开启事务
conn.setAutoCommit(false);
A
B
savepoint = conn.setSavepoint();
C
D
//3 提交事务
conn.commit();
} catche(){
if(savepoint != null){ //CD异常
// 回滚到CD之前
conn.rollback(savepoint);
// 提交AB
conn.commit();
} else{ //AB异常
// 回滚AB
conn.rollback();
}
}
3.4 JDBC中的特殊对象
CLOB
Character Large Object,顾名思义,用于存储大量的文本数据。
大字段有些特殊,不同的数据库处理的方式不一样,大字段的操作常常以流的方式来处理,而非一般的字段,一次即可读出数据。
mysql中对应的相关类型:
- TINYTEXT
- TEXT
- MEDIUMTEXT
- LONGTEXT
BLOB
Binary Large Object,顾名思义,用于存储大量的二进制数据
也是通过流来处理
mysql中对应的相关类型:
- TINYBLOB
- BLOB
- MEDIUMBLOB
- LONGBLOB
3.5 数据库连接池
原理
"池"这个概念,不应该陌生。接触最早的“池”就是常量池:用于存放常量,当程序使用该常量的时候,直接从常量池中取,而不是新建一个。因此多个常量(如String指向的字符串常量)具有相同地址。 因此,“连接池”是用于存放数据库连接的,当程序需要连接的时候,就从连接池中获取,不需要就放回。
目的:与数据库建立连接实际上非常耗费资源,提前构建连接池能够大大提高数据交互效率。
常用数据库连接池
DBCP(DataBase Connection Pool)数据库连接池,是java数据库连接池的一种,由Apache开发,通过数据库连接池,可以让程序自动管理数据库连接的释放和断开。
C3P0是一个开源的JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
由于Spring的缘故,因此博主学习的时候用的是C3P0。但是不同的连接池的使用方法基本上没啥差别。
目前在用阿里的druid
使用步骤:
- 添加jar包
- 编写配置文件c3p0-config.xml,放在classpath中,或classes目录中
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<default-config>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day12</property>
<property name="user">root</property>
<property name="password">123456</property>
<property name="initialPoolSize">10</property>
<property name="maxIdleTime">30</property>
<property name="maxPoolSize">100</property>
<property name="minPoolSize">10</property>
</default-config>
</c3p0-config>
配置的属性可以通过顾名思义理解(如maxPoolSize就是指池中最大连接数),在此不再赘述,不懂得可以百度。
<property name=“driverClass”>com.mysql.jdbc.Driver</property> 这句话在高版本的框架会警告已经完成自动配置,可以不写
3. 编写工具类,使用
此处无需耗费过多的时间,掌握连接池的原理,以及xml配置生效原理即可,后面学习框架配置会更加简单。