第1关:JDBC连接数据库
任务描述
本关任务:使用jdbc
连接数据库并完成创建数据库和创建表的操作。
相关知识
JDBC API
提供以下接口和类:
DriverManager
:此类管理数据库驱动程序列表。可在JDBC
下识别某个子协议的第一个驱动程序,用于建立数据库连接。
Driver
:此接口处理与数据库服务器的通信。我们很少会直接与Driver
对象进行交互。在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。
Connection
:此接口具有用于联系数据库的所有方法。 Connection
对象表示通信上下文,即与数据库的所有通信仅通过连接对象。
Statement
:用于执行静态SQL
语句并返回它所生成结果的对象。一些派生接口还可接受参数,如PrepareStatement
。
ResultSet
:提供检索不同类型字段的方法。(操作对象为Statement
执行SQL
查询后的结果)
SQLException
:此类处理数据库应用程序中发生的任何错误。
使用JDBC
的步骤如下:
加载数据库驱动 → 建立数据库连接(Connection
) → 创建执行SQL
语句的Statement
对象 → 处理执行结果(ResultSet
) → 释放资源
为了完成本关任务,你需要掌握:1.如何加载数据库驱动;2.如何建立数据库连接;3.如何执行编写的SQL
语句;4.释放资源。
加载数据库驱动
驱动加载是为了打开与数据库的通信通道。
在注册驱动前我们需要装载特定厂商的数据库驱动程序,导入mysq-connector-java
的jar
包,方法是在项目中建立lib
目录,在其下放入jar
包。
然后右键jar
包 Build Path
→Add to Build Path
完成jar
包导入。将jar
包导入项目之后我们就开始注册驱动:
Java
加载数据库驱动通常是使用Class
类的静态方法forName()
,语法格式如下:
Class.forName(StringdriverManager)
示例:
try { Class.forName("com.mysql.jdbc.Driver" ); } catch (ClassNotFoundExceptione) { e.printStackT\frace(); }
如果加载成功,会将加载的驱动类注册给DriverManager
;加载失败,会抛出ClassNotFoundException
异常。
建立连接
成功加载完数据库驱动后,就可以建立数据库的连接了,使用DriverManager
的静态方法getConnection()
来实现。如下:
Connectionconn=DriverManager.getConnection(url, user, password);
URL
用于标识数据库的位置,通过URL
地址告诉JDBC
程序连接信息。
若不存在数据库,只建立连接,URL
的写法为:
若存在数据库test
,URL
的写法为:
其中localhost
可以换成IP
地址127.0.0.1
,3306
为MySQL
数据库的默认端口号,user
和password
对应数据库的用户名和密码。
执行编写的SQL
语句
连接建立完毕后,就可以使用Connection
接口的createStatement()
方法来获取Statement
对象;并通过executeUpdate()
方法来执行SQL
语句。
- 创建
statement
对象
try { Statementstatement=conn.createStatement(); } catch (SQLExceptione) { e.printStackT\frace(); }
- 创建数据库
try { Stringsql1="drop database if exists test"; Stringsql2="create database test"; statement.executeUpdate(sql1);//执行sql语句statement.executeUpdate(sql2); } catch (SQLExceptione) { e.printStackT\frace(); }
- 创建表
try { statement.executeUpdate("use test");//选择在哪个数据库中操作Stringsql="create table table1("+"column1 int not null, "+"column2 varchar(255)"+")"; statement.executeUpdate(sql); } catch (SQLExceptione) { e.printStackT\frace(); }
释放资源
Jdbc
程序运行完后,切记要释放程序在运行过程中创建的那些与数据库进行交互的对象,这些对象通常是ResultSet
, Statement
和Connection
对象。
特别是Connection
对象,它是非常稀有的资源,用完后必须马上释放,如果Connection
不能及时、正确的关闭,极易导致系统宕机。
Connection
的使用原则是尽量晚创建,尽量早的释放。
为确保资源释放代码能运行,资源释放代码一定要放在finally
语句中。
finally { try { if(statement!=null) statement.close(); if(conn!=null) conn.close(); } catch (SQLExceptione) { e.printStackT\frace(); } }
编程要求
在右侧编辑器补充代码,完成下列相应任务:
- 加载数据库驱动;【平台数据库连接的用户(
user
)为root
,密码(password
)为123123
】 - 创建数据库
mysql_db
; - 创建表
student
。
student
表结构为:
字段名 | 类型 | 备注 | 约束 |
id | int | 学生id | 非空 |
name | varchar(20) | 学生姓名 | 无 |
sex | varchar(4) | 学生性别 | 无 |
age | int | 学生年龄 | 无 |
测试说明
平台会对你编写的代码进行测试:
测试输入:无
预期输出:
id INT(11)
name VARCHAR(20)
sex VARCHAR(4)
age INT(11)
开始你的任务吧,祝你成功!
实现代码
packagejdbc; importjava.sql.*; publicclassjdbcConn { publicstaticvoidgetConn() { try { // 1.注册驱动Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundExceptione) { e.printStackTrace(); } /********** End **********//********** Begin **********/Connectionconn=null; Statementstatement=null; try { // 2.建立连接并创建数据库和表conn=DriverManager.getConnection("jdbc:mysql://localhost:3306/", "root", "123123"); Stringsql1="drop database if exists mysql_db;"; Stringsql2="create database mysql_db;"; statement=conn.createStatement(); statement.execute(sql1); statement.execute(sql2); statement.execute("use mysql_db"); Stringsql3="create table student(id int not null,name varchar(20),sex varchar(4),age int)"; statement.execute(sql3); } catch (Exceptione) { // TODO 自动生成的 catch 块e.printStackTrace(); } finally { try { if (statement!=null) statement.close(); if (conn!=null) conn.close(); } catch (SQLExceptione) { e.printStackTrace(); } } } }
第2关:JDBC对表中数据的操作
任务描述
本关任务:使用JDBC
完成数据插入和查询。
相关知识
为了完成本关任务,你需要掌握:1.在连接时如何指定数据库;2.向指定表中插入数据;3.遍历表中数据。
指定数据库连接
当我们已经有数据库时,可以直接在连接时指定数据库,如下指定与test_db
数据库建立连接:
Stringurl="jdbc:mysql://localhost:3306/test_db"; Connectionconn=DriverManager.getConnection (url,"root","123123" );
当在连接时指定数据库后,我们就不用编写SQL
语句进行选择数据库了。
向指定表中插入数据
建立连接之后,编写向表中插入数据的sql
语句,使用Statement
对象的executeUpdate()
方法来执行该sql
语句就可向表中修改数据(该方法适用于insert
、update
、delete
的sql
语句),当sql
语句为查询语句时,则使用executeQuery()
方法:
try { Statementstatement=conn.createStatement(); statement.executeUpdate("insert into table1(column1,column2) values(101,'xxx')"); } catch (SQLExceptione) { e.printStackT\frace(); }
PreparedStatement
上述直接使用Statement
向表中插入数据,操作中存在SQL
注入危险,脱离上述表,如下例:
Stringid="5"; Stringsql="delete from tablename where id="+id; Statementst=conn.createStatement(); st.executeQuery(sql);//查询到表中将无数据//如果用户传入的id为“5 or 1=1”,那么将删除表中的所有记录
为预防这种情况的SQL注入,PreparedStatement 有效的防止sql注入(SQL语句在程序运行前已经进行了预编译,当运行时动态的把参数传给PreprareStatement,即使参数里有敏感字符如or '1=1'数据库也会作为参数的一个字段属性值来处理而不会作为一个SQL指令)
PreparedStatement
使用如下:
PreparedStatementstatement=conn.prepareStatement("insert into table1(column1,column2) values(?,?)");//使用占位符来先占个位置statement.setInt(1,101);//占位符顺序从1开始,根据数据库中字段相应的类型存入数据statement.setString(2, "XXX");//也可以使用setObjectstatement.executeUpdate();//每执行一个sql语句就需要执行该方法
查询表中数据
Jdbc
程序中的ResultSet
用于代表Sql
语句的执行结果。
Resultset
封装执行结果时,采用的类似于表格的方式,ResultSet
对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next()
方法,可以使游标指向具体的数据行,然后调用方法获取该行的数据。
//编写查询sql语句PreparedStatementstatement=conn.prepareStatement("select * from table1"); ResultSetresultSet=statement.executeQuery();//将执行结果给ResultSetwhile (resultSet.next()) {//循环判断表中是否还有数据intid=resultSet.getInt(1);//通过列的索引查询Stringname=resultSet.getString("column2");//通过列名查询}
编程要求
在右侧编辑器补充代码,向上一章节中已创建好的数据库mysql_db
中的表student
中插入数据,并将插入的数据进行输出:
id | name | sex | age |
1 | 张三 | 男 | 19 |
2 | 李四 | 女 | 18 |
3 | 王五 | 男 | 20 |
提示:已为你封装好student
类,可在右侧文件夹中查看,此类可直接使用。
测试说明
平台会对你编写的代码进行测试:
测试输入:无
预期输出:
1 张三 男 19
2 李四 女 18
3 王五 男 20
开始你的任务吧,祝你成功!
实现代码
packagejdbc; importjava.sql.*; importjava.util.ArrayList; importjava.util.List; publicclassjdbcInsert { publicstaticvoidinsert() { /********** Begin **********/try { // 加载驱动Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundExceptione) { e.printStackTrace(); } /********** End **********/Connectionconn=null; PreparedStatementstatement=null; /********** Begin **********/// 连接并插入数据try { Stringurl="jdbc:mysql://localhost:3306/mysql_db?useUnicode=true&characterEncoding=utf8"; Stringuser="root"; Stringpassword="123123"; conn=DriverManager.getConnection(url, user, password); Stringsql="insert into student(id,name,sex,age) values (1,'张三','男',19),(2,'李四','女',18),(3,'王五','男',20)"; statement=conn.prepareStatement(sql); statement.executeUpdate(); Stringsql1="select * from student"; ResultSetrs=statement.executeQuery(sql1); Studentstudent=null; while (rs.next()) { intid=rs.getInt(1); Stringname=rs.getString(2); Stringsex=rs.getString(3); intage=rs.getInt(4); student=newStudent(id, name, sex, age); System.out.println( student.getId() +" "+student.getName() +" "+student.getSex() +" "+student.getAge()); } } catch (SQLExceptione) { e.printStackTrace(); } /********** End **********/finally { try { if (statement!=null) statement.close(); if (conn!=null) conn.close(); } catch (SQLExceptione) { e.printStackTrace(); } } } }
第3关:JDBC事务
任务描述
本关任务:按照具体要求编写程序。
相关知识
为了完成本关任务,你需要掌握:1. 什么是事务;2. 事务的基本要素;3.如何开启事务;4.事务的提交和回滚。
事务
假设场景,我们有一个人员管理系统,你要删除一个人员,你即需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就可以构成一个事务!
事务能够控制何时更改提交并应用于数据库。 它将单个SQL
语句或一组SQL
语句视为一个逻辑单元,如果任何语句失败,整个事务将失败。
事务的基本要素(ACID
)
- 原子性(
Atomicity
):一组事务,要么成功;要么撤回; - 一致性(
Consistency
):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A
扣了钱,B
却没收到; - 隔离性(
Isolation
):事务独立运行。一个事务处理后的结果,影响了其他事务,那么其他事务会撤回。事务的100%
隔离,需要牺牲速度;
- 持久性(
Durability
):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
开启事务
开启事物需要启用手动事务支持,而不是使用JDBC驱动程序默认使用的自动提交模式,可调用Connection对象的setAutoCommit()方法。 如果将布尔的false传递给setAutoCommit(),则关闭自动提交,也就相当于开启了事物。 也可以传递一个布尔值true来重新打开它。
Connectionconn=DriverManager.getConnection (url,"root","123123" ); conn.setAutoCommit(false);//关闭自动提交开启事务
提交和回滚
在mysql
数据库中,默认为将每一句sql
都自动提交,当我们将它设置为手动事务支持(也就是已经手动开启事务)时,我们就可以在需要提交的时候进行手动提交:
conn.commit();//提交事务
当有多条sql
语句一次提交时,我们需要考虑到其中sql
是否合法,若其中某一条不合法,其他的sql
是否仍需更改等一系列问题;确保事务的基本要素,我们需要手动调用事务回滚来控制sql
的执行:
try{ Connectionconn=DriverManager.getConnection (url,"root","123123" ); conn.setAutoCommit(false);//开启事务PreparedStatementps=conn.prepareStatement("insert into table1(column1,column2) values(1,'xx1')"); ps.executeUpdate(); ps=conn.prepareStatement("insert in table1(column1,column2) values(1,'xx1')"); ps.executeUpdate(); conn.commit();//提交事务} catch (SQLExceptione) { try { conn.rollback();//回滚事务 回滚到你开始事务之前 } catch (SQLExceptione1) { e1.printStackT\frace(); } }
上述代码执行完毕后,数据库中并不会有数据更新。由于第二条insert
语句语法错误,所以事务回滚,之前的insert
也会失效。通常事务回滚都会放在catch
中来捕获。
开启事务后,一定要跟上 commit
或 rollback
,及时释放可能锁住的数据。
不用rollback()
表面和用了rollback()
效果一样,但是不用rollback()
可能导致被锁住的数据不能及时的释放(需要等事物超时释放),会影响下一次的事物操作。
编程要求
根据提示,在右侧编辑器补充代码,编写一条新增SQL
语句和任意一条错误的SQL
语句,提交事务;要求第一条新增语句在数据库中被修改,其后错误SQL
语句不执行。
新增插入语句具体要求如下: 在mysql_db
数据库student
表中新增一条id
为4
,name
为赵六,sex
为女,age
为21
的数据。
提示:每一条SQL
语句之后都可提交事务。
测试说明
平台会对你编写的代码进行测试:
测试输入:无
预期输出:
1 张三 男 19
2 李四 女 18
3 王五 男 20
4 赵六 女 21
开始你的任务吧,祝你成功!
实现代码
packagejdbc; importjava.sql.*; publicclassjdbcTransaction { publicstaticvoidtransaction() { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundExceptione) { e.printStackTrace(); } Connectionconn=null; PreparedStatementps=null; /********** Begin **********/// 连接数据库并开启事务try { Stringurl="jdbc:mysql://localhost:3306/mysql_db?useUnicode=true&characterEncoding=utf8"; conn=DriverManager.getConnection(url, "root", "123123"); conn.setAutoCommit(false); Stringsql="insert into student(id,name,sex,age) values(4,'赵六','女',21)"; ps=conn.prepareStatement(sql); ps.executeUpdate(); conn.commit(); Stringsql1="daj;ljd"; ps.executeUpdate(); conn.commit(); Stringsql2="select * from student"; ResultSetrs=ps.executeQuery(sql2); Studentstudent=null; while (rs.next()) { intid=rs.getInt(1); Stringname=rs.getString(2); Stringsex=rs.getString(3); intage=rs.getInt(4); student=newStudent(id, name, sex, age); System.out.println( student.getId() +" "+student.getName() +" "+student.getSex() +" "+student.getAge()); } } catch (SQLExceptione) { try { // 事务回滚conn.rollback(); } catch (SQLExceptione1) { e1.printStackTrace(); } } /********** End **********/finally { try { if (ps!=null) ps.close(); if (conn!=null) conn.close(); } catch (SQLExceptione1) { e1.printStackTrace(); } } } }