用Java操作数据库.

前提工作

为当前项目添加驱动

IDEA有内置的数据库工具(貌似ultimate版才有),可以直接在IDEA中像用SSMS,workbench,Navicat等工具一样操作数据库,这个工具也有独立版,叫DataGrip.使用这个工具时,只要注意密码用户名,端口号,而且它会提示下载驱动.

然后把这个下下来的驱动包加入当前项目,File>Project Structure>Dependencies,点加号加进去.

注册驱动,建立连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
//注意不要导错包
public class TestJDBC {
public static void main(String[] args) {

try {
//向DriverManager注册驱动
Class.forName("com.mysql.jdbc.Driver");

// 建立与数据库的Connection连接
// 这里需要提供:
// 数据库所处于的ip:127.0.0.1 (本机)
// 数据库的端口号: 3306 (mysql专用端口号)
// 数据库名称 how2java
// 编码方式 UTF-8
// 账号 root
// 密码 admin

Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/how2java?useSSL=false",
"root", "admin");

System.out.println("连接成功,获取连接对象: " + c);

} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

}
}

useSSL=false这里我没有设置语言,因为我的MySQL默认就是UTF-8,而设置SSL为false是避免等会的警告.

一些SQL的知识

存储机制

MySQL数据库通常支持如下两种存储机制。

  • MylSAM:这是MySQL早期默认的存储机制,对事务支持不够好。
  • InnoDB:InnoDB提供事务安全的存储机制。InnoDB通过建立行级锁来保证事务完整性,并以Oracle风格的共享锁来处理Select语句。系统默认启动InnoDB存储机制,如果不想使用InnoDB表,则可以使用skip-innodb选项。

对比两种存储机制,不难发现InnoDB比MylSAM多了事务支持的功能,而事务支持是Java EE最重要的特性,因此通常推荐使用InnoDB存储机制。如果使用5.0以上版本的MySQL数据库系统,通 常无须指定数据表的存储机制,因为系统默认使用InnoDB存储机制。如果需要在建表时显式指定存储机制,则可在标准建表语法的后面添加下面任意一句。

ENGINE=MyISAM—— 强制使用MylSAM存储机制。

ENGFNE=InnoDB—— 强制使用InnoDB存储机制。

SQL语句基础

标准的SQL语句通常可分为如下几种类型。

  • 查询语句:主要由select关键字完成,查询语句是SQL语句中最复杂、功能最丰富的语句。
  • DML (Data Manipulation Language,数据操作语言)语句:主要由 insert、update 和 delete 三个
    关键字完成。
  • DDL (Data Definition Language,数据定义语言)语句:主要由 create、alter、drop 和 truncate
    四个关键字完成。
  • DCL (Data Control L anguage,数据控制语言)语句:主要由grant和 revoke两个关键字完成。
  • 事务控制语句:主要由commit、rollback和 savepoint三个关键字完成。

在 SQL命令中也可能需要使用标识符,标识符可用于定义表名、列名,也可用于定义变量等。这 些标识符的命名规则如下。

  • 标识符通常必须以字母开头。

  • 标识符包括字母、数字和三个特殊字符(#_$)。

  • 不要使用当前数据库系统的关键字、保留字,通常建议使用多个单词连缀而成,单词之间以_
    分隔。

  • 同一个模式下的对象不应该同名,这里的模式指的是外模式。

    什么是外模式?

    作者:刘慰

    链接:https://www.zhihu.com/question/38737183/answer/93294527

    来源:知乎

    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

    这张图表示数据库管理系统(DBMS)从三个层次来管理数据:外部层次(External Level)、概念层次(Conceptual Level)和内部层次(Internal Level)。其中外部层次的数据是用户所看到的数据,所以又叫『用户』层次;概念层次的数据是DBMS中全局管理(可以简单地理解为所有的)数据,及数据之间的约束,所以又叫『逻辑』层次;内部层次的数据是存储在介质上的数据,包括存储路径、存储方式、索引方式等,所以又叫『物理』层次。

    而『模式』,就是『对于数据的结构性描述』,所以对于不同层次的数据,就对应着不同层次的模式,分别是:外模式(用户模式)、模式(概念模式/逻辑模式)与内模式(物理模式)。

    在一个数据库系统中,『模式』与『内模式』都只能有一个,但『外模式』可以有很多个。原因是『模式』是对DBMS中所有数据的全局性的结构描述,所以只能有一种形式。举个例子:一个学校的数据库中要存放所有学生、课程与选课记录的信息。我们可以用三张表:学生信息表、课程信息表与选课记录表来存放所有的数据。但是存放学生的数据的时候,我们只能够选取某一种特定的结构对学生的信息进行描述,而不能多种不同的结构共存(比如两张不同结构的学生表,存放的都是所有学生的信息)。所以『模式』只能有一个。同样,我们在介质上存放数据的时候,也只能选取某一种特定的存储路径(数据文件在哪个文件夹下)、存储方式(二进制/文本)、索引方式(聚簇索引,普通索引)等等,可以理解为一种数据结构,和具体的计算机语言没什么关系,也不能够像程序一样执行。

    至于『外模式』,是以『视图』的形式展现给用户的,所以我猜你对于外模式理解错误的原因,就是理解错误了『视图』的概念。这里的『视图』不是我们平时说的 UI ,而是指数据库中的视图,又叫『虚表』,是相对于『基本表』来说的。这里的『基本表』指的就是我们在建立数据库的时候需要创建的那些数据表(用 CREATE TABLE 创建出来的),而视图,是对这些基本表中的数据的某种展现形式,是这些数据的一个子集,但不限于某张表中的数据的子集,也可以是多张表联合起来进行显示的一组数据。比如在一个学校的教务系统中,我们希望每个学生只能够看到自己的成绩,普通老师可以看到他/她教的学生的成绩,系主任可以看到这个系中所有学生的成绩,校长可以看到学校中每个学生的成绩。那么我们可以基于所有学生的成绩数据,针对于不同权限的用户选取不同的子集,这个子集就是所谓的『视图』。但需要注意的是,『视图』本身并不『实际』存放数据,而只是对于如何选取数据的一种描述,这也是所谓的『虚』。当全局数据改变的时候,视图中的数据也会随之改变。通过外模式,可以让不同的用户(包括应用程序)看到他们希望看到的(比如可以同时看到学生本人的信息及他/她的所有成绩与对应的课程的信息,而这些数据实际上是分别存在于不同的数据表中的),或是我们希望他们看到的数据(通过不同的子集来实现)。

    总而言之,三种层次的模式都是对于数据的描述,只不过描述的层次不同。

DDL语句

DDL语句是操作数据库对象的语句,包括创建(create),删除(drop)和修改(alter)数据库对象。最基本的数据库对象是数据表,数据表是存储数据的逻辑单元。但数据库里绝不仅包括数据表,数据库里可包含如下表所示的几种常见的数据库对象。

1.创建表的语法

1
2
3
4
5
create table 表名
(
columnName datatype,
...
);

2.修改表的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
--增加列定义
alter table 表名
add
(
columnName datatype,
...
);
--修改列定义
alter table 表名
modify columnName datatype;
/*
add的必须是原表不存在的,modify的必须是原表存在的
MySQL的modify不支持一次修改多个表,Oracle的可以,只要像add一样用括号将多个字段包起来
MySQL的想要修改多个就要多个modify
modify时注意修改后的字段属性不能和已存在的数据冲突
修改默认值只会修改新增记录的默认值,原来的没有影响
*/
--删除列
alter table 表名
drop columnName;
--MySQL支持的重命名表
alter table 表名
rename to 新表名;
--MySQL支持的重命名列名
alter table 表名
change old_column_name new_column_name datatype;

3.删除表的语法

drop table 表名

4.truncate表

truncate 表名可以清空表的数据,但是保留了表的结构.

在information_scheme数据库中的tables表保存了数据库实例中的所有数据表,可以查询该表来获取表信息.

数据库约束

大部分数据库支持下面5种完整性约束。

  • NOT NULL:非空约束,指定某列不能为空。

  • UNIQUE:唯一约束,指定某列或者几列组合不能重复。

  • PRIMARY KEY:主键,指定该列的值可以唯一地标识该条记录。

  • FOREIGN KEY:外键,指定该行记录从属于主表中的一条记录,主要用于保证参照完整性。

  • CHECK:检查,指定一个布尔表达式,用于指定对应列的值必须满足该表达式。(MySQL不支持)

    可以在information_schema数据库的TABLE_CONSTRAINTS表查看约束信息

索引

索引通过快速路径访问方法来快速定位数据,加速查询.索引从属于数据表,和数据表一样属于数据库对象,不能独立存在,必须属于某个表.

创建索引有两种方式。

  • 自动:当在表上定义主键约束、唯一约束和外键约束时,系统会为该数据列自动创建对应的索
    引。
  • 手动:用户可以通过create index…语句来创建索引。

删除索引也有两种方式。

  • 自动:数据表被删除时,该表上的索引自动被删除。
  • 手动:用户可以通过drop index…语句来删除指定数据表上的指定索引。

索引的作用类似于书的目录,几乎没有一本书没有目录,因此几乎没有一个表没有索引。一个表中可以有多个索引列,每个索引都可用于加速该列的查询速度。
正如书的目录总是根据书的知识点来建立一样,因为读者经常要根据知识点来查阅一本书。类似的,通常为经常需要查询的数据列建立索引,可以在一列或者多列上创建索引。创建索引的语法格式如下:

1
2
create index index_name
on table_name (column[,column]...);

索引也会带来两点坏处,1.每当数据表发生变化时,数据库系统需要维护索引,因此会有一定开销,2.索引本身也要占地方

视图

视图其实更像是向外展示数据的手段,它是将数据库中的一些数据查询出来给人看.是一个便利的展示数据集的手段.

1
2
3
create or replace view 视图名
as
subquery;
1
2
3
4
create or replace view view_test
as
select tescher_name,class_name from school_table
with check option;--强制只读视图数据

DML语句

DML语句由insert into、update 和delete from三个命令组成。

比较简单,略过.

单表查询

不少内容都在数据库课讲过,下面只简明的提一下

1.concat用于将查询出来的结果拼接起来,as用于给结果的列起个别名(或者用空格).对于MySQL来说,如果在算数表达式中使用null,整个表达式结果就是null

1
2
3
4
SELECT
CONCAT( `name`, hp ) AS DATA
FROM
`jdbctest`
1
select teacher_id MY_ID , concat (teacher_name , teacher_id) teacher

也可以给表起别名.

2.like和下划线(一个字符),百分号(任意个字符)实现模糊查询,还有正则模式

3.order by和desc降序排列

数据库函数

函数被分为单行函数和多行函数,单行函数对每行输入值单独计算,每行得 到一个计算结果返回给用户;多行函数对多行输入值整体计算,最后只会得到一个结果。

单行函数

  • 单行函数的参数可以是变量、常量或数据列。单行函数可以接收多个参数,但只返回一个值
  • 单行函数会对每行单独起作用,每行(可能包含多个参数)返回一个结果。
  • 使用单行函数可以改变参数的数据类型。单行函数支持嵌套使用,即内层函数的返回值是外层函数的参数。

大致分为以下几类:

  • 日期时间函数
  • 数值函数
  • 字符函数
  • 转换函数
  • 其他函数
    • 位函数
    • 流程控制函数
    • 加密解密函数
    • 信息函数

一些例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
--选出teacher_table表中teacher_name列的字符长度
select char_length(teacher_name) from teacher_table;
--计算teacher_name列的字符长度的sin值
select sin(char_length(teacher_name))
from teacher_table;
--计算1.57的Sin值,约等于1
select sin(1.57);
--为指定日期添加一定的时间
--在这种用法下interval是关键字,需要一个数值,还需要一个单位
SELECT DATE_ADD('1998-01-02', interval 2 MONTH);
--这种用法更简单
select ADDDATE('1998-01-02', 3);
--获取当前日期
select CURDATE();
--获取当前时间
select curtime();
--下面的MD5是MD5加密函数
select MD5('testing');

SQL提供了如下几个处理null的函数。

  • ifnull(exprl,expr2 ):如果exprl为null,则返回expr2, 否则返回exprl。
  • nullif(exprl,expr2):如果erprl和expr2相等,则返回null,否则返回exprl。
  • if(exprl,expr2,expr3):有点类似于?:三目运算符,如果exprl为true ,不等于0,且不等于null, 则返回expr2, 否则返回expr3。
  • isnull(exprl):判断exprl是否为null,如果为null则返回true ,否则返回false。

另一个case函数:

1
2
3
4
5
case value 
when compare_valuel then resultl
when compare_value2 then result2
...
else result end

case函数用value和后面的compare_valuel、compare_value2、…依次进行比较,如果value和指定的 compare valuel相等,则返回对应的result1, 否则返回else后的result。例如如下SQL语句:

1
2
3
4
5
select student_name , case java_teacher 
when 1 then 'Java 老师'
when 2 then 'Ruby 老师'
else '其他老师’
end from student_table;

在第二种用法中,condition1, condition2都是一个返回boolean值的条件表达式,因此这种用法更
加灵活。例如如下SQL语句:

1
2
3
4
5
6
7
8
SELECT
*,
CASE
WHEN hp < 410 THEN
'low_hp' ELSE 'high_hp'
END AS LEVEL
FROM
`jdbctest`;

分组和组函数(多行函数)

组函数也就是前面提到的多行函数,组函数将一组记录作为整体计算,每组记录返回一个结果,而 不是每条记录返回一个结果.常用的组函数有如下5个。

  • avg([distinct|all]expr):计算多行expr的平均值,其中,expr可以是变量、常量或数据列,但其
    数据类型必须是数值型。还可以在变量、列前使用distinct或 all关键字,如果使用distinct,则表明不计算重复值;all用和不用的效果完全一样,表明需要计算重复值.

  • count({ *|[distinct|all]expr}):计算多行expr的总条数,其中,expr可以是变量、常量或数据列,
    其数据类型可以是任意类型;用星号(*)表示统计该表内的记录行数;distinct表示不计算重复值。

  • max(expr):计算多行expr的最大值,其中expr可以是变量、常量或数据列,其数据类型可以 是任意类型。

  • min(expr):计算多行expr的最小值,其中expr可以是变量、常量或数据列,其数据类型可以是 任意类型。

  • sum([distinct|all]expr): 计算多行expr的总和,其中,expr可以是变量、常量或数据列,但其数据类型必须是数值型;distinct表示不计算重复值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    select
    avg( ifnull( info, 0 ))--mysql中null会导致表达式结果为null
    from
    jdbctest
    where
    id<6;
    select
    hp,count(*)--查询hp字段,统计出表格的行数,按照hp分组,且升序排列
    from
    jdbctest
    group by
    hp asc;

    对分组的结果进行过滤:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select
    hp,
    count( hp )
    from
    jdbctest
    group by
    hp asc
    having
    count( hp )>= 2;

多表连接查询

需要选择的数据并不是来自一个表,而是来自多个数据表,这就需要使用多表连接查询。

多表连接查询有两种规范,较早的SQL92规范支持如下几种多表连接查询。

  • 等值连接。
  • 非等值连接。
  • 外连接。
  • 广义笛卡儿积。

SQL99规范提供了可读性更好的多表连接语法,并提供了更多类型的连接查询。SQL99支持如下几种多表连接查询。

  • 交叉连接。
  • 自然连接。
  • 使用using子句的连接。
  • 使用on子句的连接。
  • 全外连接或者左、右外连接。
  1. SQL92的连接查询
    SQL92的多表连接语法比较简洁,这种语法把多个数据表都放在from之后,多个表之间以逗号隔 开;连接条件放在where之后,与查询条件之间用and逻辑运算符连接。如果连接条件要求两列值相等, 则称为等值连接,否则称为非等值连接;如果没有任何连接条件,则称为广义笛卡儿积。
1
2
3
4
5
6
7
8
select
jdbctest.*,
jdbctest1.name
from
jdbctest,
jdbctest1
where
jdbctest.id = jdbctest1.id;

大致的流程是:

1
2
3
4
5
6
7
8
for t in jdbctest
{
for s in jdbctest1
{
if (jdbctest.id = jdbctest1.id)
sout s+t
}
}

如果没有where后面的条件,得到的就是广义笛卡尔积.

  1. SQL99的连接查询

    交叉连接(cross join ):交叉连接效果就是SQL92中的广义笛卡儿积,所以交叉连接无须任何连接条件.

    自然连接(natural join ) : 自然连接表面上看起来也无须指定连接条件,但自然连接是有连接条件的,自然连接会以两个表中的同名列作为连接条件;如果两个表中没有同名列,则自然连接与交叉连接效果完全一样—因为没有连接条件。

    using子句连接:using子句可以指定一列或多列,用于显式指定两个表中的同名列作为连接条件。假设两个表中有超过一列的同名列,如果使用natural join ,则会把所有的同名列当成连接条件;使用using子句,就可显式指定使用哪些同名列作为连接条件。

    on子句连接:这是最常用的连接方式,SQL 99语法的连接条件放在on子句中指定,而且每个 on子句只指定一个连接条件。这意味着:如果需要进行N表连接,则需要有N-1个join…on对。

    剩下的左外连接,右外连接,全连接

    • LEFT JOIN关键字会从左表那里返回所有的行,即使在右表中没有匹配的行。

    • RIGHT JOIN 关键字会右表那里返回所有的行,即使在左表中没有匹配的行。

    • 只要其中某个表存在匹配,FULL JOIN 关键字就会返回行。

子查询

子查询就是指在查询语句中嵌套另一个査询,子查询可以支持多层嵌套。对于一个普通的查询语句而言,子查询可以出现在两个位置。

  • 出现在from语句后当成数据表,这种用法也被称为行内视图,因为该子查询的实质就是一个临时视图。
  • 出现在where条件后作为过滤条件的值。

集合运算

select语句查询的结果是一个包含多条数据的结果集,类似于数学里的集合,可以进行交(intersect)、 并 (union)和差 (minus)运算,select查询得到的结果集也可能需要进行这三种运算。 为了对两个结果集进行集合运算,这两个结果集必须满足如下条件。

  • 两个结果集所包含的数据列的数量必须相等。
  • 两个结果集所包含的数据列的数据类型也必须一一对应

实际上MySQL只支持union运算,其他两种都只能用子查询或多表连接的方式实现.

创建语句

在连接后创建相应的SQL语句:

1
2
3
Statement s = c.createStatement();
String sql = "insert into jdbctest values(2,'ye',15,'C1')";
s.execute(sql);

关闭连接

数据库的连接是有限资源,相关操作结束后,养成关闭数据库的好习惯

先关闭Statement

后关闭Connection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC {
public static void main(String[] args) {
Connection c = null;
Statement s = null;
try {
Class.forName("com.mysql.jdbc.Driver");

c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
"root", "******");

// 注意:使用的是 java.sql.Statement
// 不要不小心使用到: com.mysql.jdbc.Statement;
s = c.createStatement();

String sql = "insert into jdbctest values(2,'ye',15,'C1')";
s.execute(sql);

} catch (ClassNotFoundException | SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (s != null) {
try {
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (c != null) {
try {
c.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}

}
}

或者用try(){}风格:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;

public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
"root", "******");
Statement s = c.createStatement();) {
String sql = "insert into jdbctest values(2,'ye',15,'C1')";
s.execute(sql);

} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

练习

向DailyTest数据库的JDBCtest表插入一百个hero记录.

truncate table dailytest.jdbctest使用这条命令可以清空jdbctest表的数据,便于测试.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Random;

public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
"root", "******");
Statement s = c.createStatement()) {
for (int i = 0; i < 100; i++) {
int maxh = 450;
int minh = 350;
int maxd = 80;
int mind = 30;
String name = "英雄" + i;
int hp = new Random().nextInt(maxh) % (maxh - minh + 1) + minh;
int damage = new Random().nextInt(maxd) % (maxd - mind + 1) + minh;
s.execute("insert into jdbctest values(null,'" + name + "','" + hp + "','" + damage + "')");
}

} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

注意:在SQL语句中使用Java变量要这样写:'" + name + "'.

查询

查询不能使用Statement的execute方法,因为查询需要返回查询的结果.使用Statement的executeQuery方法,将结果返回给一个ResultSet对象.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.sql.*;

/**
* @author YL
*/
public class TestRetrieve {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
"root", "******");
Statement s = c.createStatement()) {
ResultSet rs = s.executeQuery("select * from jdbctest where hp regexp '.[345].'");
while (rs.next()) {
int id = rs.getInt("ID");
String name = rs.getString(2);
int hp = rs.getInt(3);
int damage = rs.getInt(4);
System.out.printf("%d\t%s\t%d\t%d%n", id, name, hp, damage);
}
System.out.println("exit");
} catch (SQLException e) {
e.printStackTrace();
}
}
}

"select * from jdbctest where hp regexp '.[345].'",这里我是用了MySQL的正则模式,表示查出hp为三位数,且中间数字是3,4,5的字段的全部信息.

注意:这里rs.getXxx是从1开始算的.

判断密码是否正确

判断密码问题应该是在某张表中找是否有符合的记录,而不是将数据读出来比较.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import java.sql.*;

public class TestJDBC {
public static void main(String[] args) {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
try (Connection c = DriverManager
.getConnection(
"jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
"root", "******");
Statement s = c.createStatement()) {
String name = "aaa";
String password = "password";
ResultSet rs = s.executeQuery("select * from jdbctest1 where name='" + name + "'and password='" + password + "'");
if (rs.next()) {
System.out.println("right");
}else {
System.out.println("wrong");
}


} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

使用预编译语句

当遇到有些需要重复使用某些结构相同的查询语句时,重复的编译会浪费大量时间,因此可以使用PreparedStatement,具体后面解释.PreparedStatement还有其他优势.

  • 避免了字符串的拼接

    在前面的例子中我们可以看出,写SQL语句有个非常大的问题就是各种双引号,单引号套在一起很容易出错.而PreparedStatement可以使用占位符来替换.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    String sql="insert into hero values(null,?,?,?)";
    try(Connection c=...
    PreparedStatement ps=c.prepareStatement(sql)){
    ps.setInt(1,"bbb");
    ps.setString(2,"password");
    ps.execute();
    }
    //这里设置参数是从1开始计算的,如果第一个字段是自增长的ID则不算做第一个,我这就是如此,那个null表示自增长ID

  • 性能优势

    在前面的例子中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    for (int i = 0; i < 100; i++) {
    int maxh = 450;
    int minh = 350;
    int maxd = 80;
    int mind = 30;
    String name = "英雄" + i;
    int hp = new Random().nextInt(maxh) % (maxh - minh + 1) + minh;
    int damage = new Random().nextInt(maxd) % (maxd - mind + 1) + minh;
    s.execute("insert into jdbctest values(null,'" + name + "','" + hp + "','" + damage + "')");
    }

    同样结构的语句每次都要编译处理一次,如果使用预编译语句,则在固定部分只要编译一次,每次只要将参数传给数据库,无需编译,网络传输量也更小.

  • 防止SQL注入式攻击

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import java.sql.*;
    import java.util.Scanner;

    public class TestJDBC {
    public static void main(String[] args) {
    try {
    Class.forName("com.mysql.jdbc.Driver");
    } catch (ClassNotFoundException e) {
    e.printStackTrace();
    }
    String name;
    String password;
    Scanner scanner = new Scanner(System.in);
    name = scanner.nextLine();
    password = scanner.nextLine();
    try (Connection c = DriverManager
    .getConnection(
    "jdbc:mysql://127.0.0.1:3306/dailytest?useSSL=false",
    "root", "******");
    Statement s = c.createStatement()) {
    ResultSet rs = s.executeQuery("select * from jdbctest1 where name='" + name + "'and password='" + password + "'");
    if (rs.next()) {
    System.out.println("right");
    } else {
    System.out.println("wrong");
    }


    } catch (SQLException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    }
    }
    }

    在上面的例子中,如果我输入'or true or',SQL语句拼接后就是select * from jdbctest1 where name='' or trur or'' and password='',这就是产生了一个必真条件,导致结果为right.

execute与executeUpdate的区别

同:

execute与executeUpdate的相同点:都可以执行增加,删除,修改

异:

不同1:
execute可以执行查询语句
然后通过getResultSet,把结果集取出来
executeUpdate不能执行查询语句
不同2:
execute返回boolean类型,true表示执行的是查询语句,false表示执行的是insert,delete,update等等
executeUpdate返回的是int,表示有多少条数据受到了影响