JDBC
一、JDBC概述
- JDBC(Java Database Connection)为java开发者使用数据库 提供了统一的编程接口,它由一组java类和接口组成。是java 程序与数据库系统通信的标准API。JDBC API 使得开发人员 可以使用纯java的方式来连接数据库,并执行操作。
- sun公司由于不知道各个主流商用数据库的程序代码,因此无 法自己写代码连接各个数据库,因此,sun公司决定,自己提 供一套api,凡是数据库想与Java进行连接的,数据库厂商自 己必须实现JDBC这套接口。而数据库厂商的JDBC实现,我们 就叫他此数据库的数据库驱动。
- JDBC访问数据库流程
- 加载一个Driver驱动
- 创建数据库连接(Connection)
- 创建SQL命令发送器Statement
- 通过Statement发送SQL命令并得到结果
- 处理结果(select语句)
- 关闭数据库资源
二、JDBC常用接口
- Driver接口
- Driver接口由数据库厂家提供,对于java开发者而言,只需要使用 Driver接口就可以了。
- 在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序。不 同的数据库有不同的装载方法。
- 驱动:就是各个数据库厂商实现的Sun公司提出的JDBC接口。 即对 Connection等接口的实现类的jar文件
- 装载MySql驱动Class.forName(“com.mysql.jdbc.Driver”);
- 装载Oracle驱动 Class.forName(“oracle.jdbc.driver.OracleDriver”);
- DriverManager接口
- DriverManager是JDBC的管理层,作用于用户和驱动程序之间。
- DriverManager跟踪可用的驱动程序,并在数据库和相应的驱动程序 之间建立连接。
- Connection接口
- Connection与特定数据库的连接(会话),在连接上下文中执行 SQL 语句并返回结果。
- DriverManager的getConnection()方法建立在JDBC URL中定义的数 据库Connection连接上
- 连接MYSQL数据库:Connection con = DriverManager.getConnection(“jdbc:mysql://host:port/database”,“user”, “password”);
- 连接ORACLE数据库:Connection con = DriverManager.getConnection(“jdbc:oracle:thin:@host:port:databse”,“us er”,“password”);
- Statement接口
- 用于执行静态 SQL 语句并返回它所生成结果的对象。
- 三种Statement类
- Statement: 由createStatement创建,用于发送简单的SQL语句。(不带参数的)
- PreparedStatement: 继承自Statement接口,由prepareStatement创建,用于发送含有一个或多 个输入参数的sql语句。PreparedStatement对象比Statement对象的效率更 高,并且可以防止SQL注入。我们一般都用PreparedStatement.
- CallableStatement: 继承自PreparedStatement 。由方法prePareCall创建,用于调用存储过程。
- 常用的Statement方法:
- PreparedStatement接口
- 它是Statement接口的子接口;
- 强大之处:防止SQL攻击、提高代码的可读性、可维护性、提高效率
- 用法:
• 给出SQL模板!
• 调用Connection的PreparedStatement prepareStatement(String sql模板);
• 调用pstmt的setXxx()系列方法sql模板中的?赋值!
• 调用pstmt的executeUpdate()或executeQuery(),但它的方法都没有参数。 - 预处理的原理
- 服务器的工作:
• 校验sql语句的语法!
• 编译:把SQL语句变成一个与函数相似的东西!
• 执行:调用函数 - PreparedStatement:
• 前提:连接的数据库必须支持预处理!几乎没有不支持的!
• 每个pstmt都与一个sql模板绑定在一起,先把sql模板给数据库,数据库先进行校验,再进行编译。执行时只是把参数传递过去而已!
• 若二次执行时,就不用再次校验语法,也不用再次编译!直接执行!
- 服务器的工作:
- 什么是SQL攻击
在需要用户输入的地方,用户输入的是SQL语句的片段,最终用户输入的SQL片段与我们DAO中写的SQL语句合成一个完整的SQL语句!例如用户在登录时输入的用户名和密码都是为SQL语句的片段!
- ResultSet接口
- ResultSet对象是executeQuery()方法的返回值,它被称为结果集,它代表符合SQL语句条件的 所有行,并且它通过一套getXXX方法(这些get方法可以访问当前行中的不同列)提供了对这 些行中数据的访问。
- ResultSet里的数据一行一行排列,每行有多个字段,且有一个记录指针,指针所指的数据行 叫做当前数据行,我们只能来操作当前的数据行。我们如果想要取得某一条记录,就要使用 ResultSet的next()方法 ,如果我们想要得到ResultSet里的所有记录,就应该使用while循环。
- ResultSet对象自动维护指向当前数据行的游标。每调用一次next()方法,游标向下移动一行。
- 初始状态下记录指针指向第一条记录的前面,通过next()方法指向第一条记录。循环完毕后 指向最后一条记录的后面。
- 处理方法总结
三、批处理(Batch)
- 对于大量的批处理,建议使用Statement,因为PreparedStatement的预编译空间有限 ,当数据量特别大时,会发生异常。
- 使用示例:
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
//加载驱动类
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","123456");
conn.setAutoCommit(false); //设为手动提交
long start = System.currentTimeMillis();
stmt = conn.createStatement();
for(int i=0;i<20000;i++){
stmt.addBatch("insert into t_user (username,pwd,regTime) values ('gao"+i+"',666666,now())");
}
stmt.executeBatch();
conn.commit(); //提交事务
long end = System.currentTimeMillis();
System.out.println("插入20000条数据,耗时(毫秒):"+(end-start));
}
四、事物
-
事物基本概念
-
一组要么同时执行成功,要么同时执行失败的SQL语句。是数据库操 作的一个执行单元!
-
事务开始于: 连接到数据库上,并执行一条DML语句(INSERT、UPDATE或DELETE)。 或者前一个事务结束后,又输入了另外一条DML语句。
-
事务结束于:
- 执行COMMIT或ROLLBACK语句。
- 执行一条DDL语句,例如CREATE TABLE语句;在这种情况下,会自动执 行COMMIT语句。
- 执行一条DCL语句,例如GRANT语句;在这种情况下,会自动执行 COMMIT语句。
- 断开与数据库的连接。
- 执行了一条DML语句,该语句却失败了;在这种情况中,会为这个无效的 DML语句执行ROLLBACK语句。
-
jdbc处理事务的代码格式:
try { con.setAutoCommit(false);//开启事务 … … con.commit();//try的最后提交事务 } catch() { con.rollback();//回滚事务 }
-
-
事物的四大特点
- atomicity(原子性):表示一个事务内的所有操作是一个整体,要 么全部成功,要么全失败;
- consistency(一致性):表示一个事务内有一个操作失败时,所有的更改过的数据都必须回滚到修改 前的状态;
- isolation(隔离性) :事务查看数据时数据所处的状态,要么是另一并发事务修改它之前的状态, 要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。
- durability(持久性):持久性事务完成之后,它对于系统的影响是永久性的。
-
三种并发读问题
- 脏读:读到另一个事务的未提交更新数据,即读取到了脏数据;
- 不可重复读:对同一记录的两次读取不一致,因为另一事务对该记录做了修改;
- 幻读(虚读):对同一张表的两次查询不一致,因为另一事务插入了一条记录;
-
不可重复读和幻读的区别
- 不可重复读是读取到了另一事务的更新;
- 幻读是读取到了另一事务的插入(MySQL中无法测试到幻读);
-
四种事物隔离级别
4个等级的事务隔离级别,在相同数据环境下,使用相同的输入,执行相同的工作,根据不同的隔离级别,可以导致不同的结果。不同事务隔离级别能够解决的数据并发问题的能力是不同的。- 读取未提交(Read Uncommitted)
- 可能出现任何事务并发问题
- 性能最好
- 读取已提交(Read Committed)
- 防止脏读,没有处理不可重复读,也没有处理幻读;
- 性能比REPEATABLE READ好
- 可重复读(Repeatable Read)
- 防止脏读和不可重复读,不能处理幻读问题;
- 性能比SERIALIZABLE好
- 序列化(serializable)
- 不会出现任何并发问题,因为它是对同一数据的访问是串行的,非并发访问的
- 性能最差
- 读取未提交(Read Uncommitted)
-
jdbc设置隔离级别
con. setTransactionIsolation(int level),可选参数如下
- Connection.TRANSACTION_READ_UNCOMMITTED;
- Connection.TRANSACTION_READ_COMMITTED;
- Connection.TRANSACTION_REPEATABLE_READ;
- Connection.TRANSACTION_SERIALIZABLE。
五、CLOB与BLOB
- CLOB
-
用于存储大量的文本数据
-
大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的 方式来处理的。而非一般的字段,一次即可读出数据。
-
Mysql中相关类型
- TINYTEXT最大长度为255字符的TEXT列。
- TEXT[(M)]最大长度为65,535字符的TEXT列。
- MEDIUMTEXT最大长度为16,777,215字符的TEXT列。
- LONGTEXT最大长度为4,294,967,295或4GB字符的TEXT列。
-
使用示例
PreparedStatement ps = null; ResultSet rs = null; Reader r = null; try { //加载驱动类 Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","123456"); // ps = conn.prepareStatement("insert into t_user (username,myInfo) values (?,?) "); // ps.setString(1, "xxxx"); // ps.setClob(2, new FileReader(new File("d:/a.txt"))); //将文本文件内容直接输入到数据库中 // 将程序中的字符串输入到数据库的CLOB字段中 // ps.setClob(2, new BufferedReader(new InputStreamReader(new ByteArrayInputStream("aaaabbbbbb".getBytes())))); // 读取数据库中的CLOB数据 ps = conn.prepareStatement("select * from t_user where id=?"); ps.setObject(1, 101024); rs = ps.executeQuery(); while(rs.next()){ Clob c = rs.getClob("myInfo"); r = c.getCharacterStream(); int temp = 0; while((temp=r.read())!=-1){ System.out.print((char)temp); } } }
-
- BLOB
-
用于存储大量的二进制数据
-
大字段有些特殊,不同数据库处理的方式不一样,大字段的操作常常是以流的 方式来处理的。而非一般的字段,一次即可读出数据。
-
Mysql中相关的类型
- TINYBLOB最大长度为255字节的BLOB列。
- BLOB[(M)]最大长度为65,535字节的BLOB列。
- MEDIUMBLOB最大长度为16,777,215字节的BLOB列。
- LONGBLOB最大长度为4,294,967,295或4GB字节的BLOB列。
-
使用示例
Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; InputStream is = null; OutputStream os = null; try { //加载驱动类 Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testjdbc","root","123456"); // ps = conn.prepareStatement("insert into t_user (username,headImg) values (?,?) "); // ps.setString(1, "xxx"); // ps.setBlob(2, new FileInputStream("d:/icon.jpg")); // ps.execute(); ps = conn.prepareStatement("select * from t_user where id=?"); ps.setObject(1, 101026); rs = ps.executeQuery(); while(rs.next()){ Blob b = rs.getBlob("headImg"); is = b.getBinaryStream(); os = new FileOutputStream("d:/a.jpg"); int temp = 0; while((temp=is.read())!=-1){ os.write(temp); } } }
-
六、处理日期的相关类型:
- 数据库类型与java中类型的对应关系:
DATE->java.sql.Date:表示年月日
TIME->java.sql.Time:表示时分秒
TIMESTAMP->java.sql.Timestamp:表示年月日时分秒 - 领域对象(domain)中的所有属性不能出现java.sql包下的东西!即不能使用java.sql.Date.但ResultSet#getDate()返回的是java.sql.Date()、PreparedStatement#setDate(int, Date),其中第二个参数也是java.sql.Date
- 类型转换:
java.util.Date ->java.sql.Date、Time、Timestamp
- 把util的Date转换成毫秒值
- 使用毫秒值创建sql的Date、Time、Timestamp
java.util.Date date = new java.util.Date();
long l = date.getTime();
java.sql.Date sqlDate = new java.sql.Date(l);
java.sql.Date、Time、Timestamp ->java.util.Date
这一步不需要处理了:因为java.util.Date是java.sql.Date的父类;
PS:在mysql数据库中可以直接传入字符串形式的时间毫秒值
从proerties配置文件读取连接信息:
static Properties pros = null; //可以帮助读取和处理资源文件中的信息
static {
pros = new Properties();
try {
pros.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
} catch (IOException e) {
e.printStackTrace();
}
}
//使用pros.getProperty("key")获取连接的url、driver、username、pwd