JDBC入门
1,什么是JDBC
Java DataBase Connectivity就是Java数据库连接,使用Java语言来操作数据库。原来我们操作数据库是控制台使用SQL语句来操作数据库,JDBC是用Java语言向数据库发送SQL语句。
①导jar包:驱动;
②加载驱动类:class.forName("类名");
③给出url,username,password,其中url背下来;
④使用DriverManager类来得到Connection对象。
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class Demo1 { /** * @throws ClassNotFoundException * ①没导入驱动包 * ②名称、单词等关键字不正确 * @throws SQLException * ①检测3个参数:url、username、password是否正确 * ②是否开启 */ public static void fun1() throws ClassNotFoundException, SQLException { /** * jdbc四大配置参数: * >driverClassName:com.mysql.jdbc.Driver * >url:jdbc:mysql://localhost:3306/mydb1 * >username:root * >password:无 */ Class.forName("com.mysql.jdbc.Driver");//驱动类加载【注册驱动】 String url="jdbc:mysql://localhost:3306/mysql"; String username="root"; String password=null; Connection conn=DriverManager.getConnection(url,username,password); System.out.println(conn); } } |
2,JDBC原理
早期SUN公司想编写一套可以连接所有数据库的API,但是当他们刚刚开始的时候就发现这是个不可能完成的任务,因为各个厂商的数据库服务器差异太大了。后来SUN开始与数据库厂商们讨论,最终得出的结论是,由SUN提供一套访问数据库的规范(就是一组接口),并且提供连接数据库的协议标准,然后各个数据库厂商会遵循SUN的规范提供一套访问自己公司的数据库服务器的API出现。SUN提供的规范命名为JDBC,而各个厂商提供的-遵循JDBC规范的-可以访问自己数据库的API被称之为驱动。
Class.forName("com.mysql.jdbc.Driver");//驱动类加载【注册驱动】 |
com.mysql.jdbc.Driver driver=new com.mysql.jdbc.Driver(); DriverManager.registerDriver(driver); |
加载类的时候会执行静态代码块,所有的java.sql.Driver实现类,都提供了static块,块内的代码就是把自己注册到DriverManager中。
在jdbc4.0之后,每个驱动jar包中,在META-INF/services目录下提供有名为java.sql.Driver的文件,文件内容就是该接口的实现类名称!
static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException var1) { throw new RuntimeException("Can't register driver!"); } } |
3,JDBC完成增删改查
①数据库连接准备
package cn.itcast.demo1; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.sql.Statement; public class Demo2 { public static void fun1() throws ClassNotFoundException, SQLException { //①准备四大参数;②加载驱动类;③对数据库进行增删改查; String driverClassName="com.mysql.jdbc.Driver"; String url="jdbc:mysql://localhost:3306/mydb1"; //jdbc协议的格式!jdbc:厂商名称:子协议(由厂商自己规定) //对于mysql而言,子协议结构://主机地址:端口号/数据库名称 String username="root"; String password=""; Class.forName(driverClassName); Connection conn=DriverManager.getConnection(url,username,password); //1,通过Connection对象创建Statement>Statement语句发送器,他的功能是想数据库发送sql语句 Statement stmt=conn.createStatement(); //2,调用它的int executeUpdate(String sql),它可以发生DML、DDL String sql="INSERT INTO emp VALUES(NULL,'王武',23,'男')"; int r=stmt.executeUpdate(sql); System.out.println(r); } } |
②数据库DML操作
String sql="INSERT INTO emp VALUES(NULL,'王武',23,'男')"; int r=stmt.executeUpdate(sql); |
String sql="UPDATE emp"; int r=stmt.executeUpdate(sql); |
String sql="UPDATE emp SET ename='红红的夏天',age=18,gender='女' WHERE eid=4";//修改 int r=stmt.executeUpdate(sql); |
String sql="DELETE FROM emp";//删除 int r=stmt.executeUpdate(sql); |
③数据库DDL查询操作
public ResultSet executeQuery(String sql) throws SQLException { /** * 解析ResultSet * ①把光标移动到第一行,可以调用next()完成 */ this.resultSet=this.stmt.executeQuery(sql); while(this.resultSet.next()){ int eid=this.resultSet.getInt(1); String ename=this.resultSet.getString("ename"); int age=this.resultSet.getInt("age"); String gender=this.resultSet.getString("gender"); System.out.println(eid+","+ename+","+age+","+gender); } return this.resultSet; |
4,JDBC代码规范化
package cn.itcast.demo1; import java.sql.*; public class Demo3 { public static void fun3() throws Exception { Connection conn=null;//定义引用对象 Statement stmt=null; ResultSet rs=null; try{ String driverClassName="com.mysql.jdbc.Driver"; // String url="jdbc:mysql://localhost:3306/mydb1"; String url="jdbc:mysql://localhost:3306/" + "mydb1?serverTimezone=GMT%2B8&useSSL=false"; //这里需要修改一下 String username="root"; String password=""; Class.forName(driverClassName); conn=DriverManager.getConnection(url,username,password); //实例化Connection stmt=conn.createStatement();//实例化Statement String sql="SELECT * FROM emp"; rs=stmt.executeQuery(sql); while (rs.next()){ System.out.println(rs.getString(1)+","+ rs.getObject(2)+","+ rs.getString(3)+","+ rs.getObject(4)); } }catch(Exception e){//捕获异常打印 e.printStackTrace(); } finally { if(rs!=null)rs.close();//先判断在关闭 if(stmt!=null)stmt.close();//由子到父关闭 if(conn!=null)conn.close(); } } } |
Connection->Statement->ResultSet->
DriverManager
其实今后我们只需要会用DriverManager的getConnection()方法即可:
①Class.forName(driverClassName);//注册驱动
②String url="jdbc:mysql://localhost:3306/mydb1?serverTimezone=GMT%2B8&useSSL=false";
③String username="root";
④String password="";
⑤Connection conn=DriverManager.getConnection(url,username,password);
上面的代码可能出现的两种异常:
①ClassNotFoundException——这个异常在第一句出现,可能为
·没有导入mysql的jar包;
·把类名称打错,查看的类名不是com.mysql.jdbc.Driver
②SQLException——异常出现在第五句,出现这个异常的就是三个参数的问题
对于DriverManager.registerDriver()方法了解即可,因为今后注册驱动只会使用Class.forName()而不是用前一个方法。
Connection
Connection最为重要的方法就是获取Statement:
·Statement stmt=conn.createStatement();
后面在学习ResultSet()方法时,还需要学习一下下面的方法【方法重载】:
·Statement stmt=conn.createStatement(int,int);//两个参数决定能够生成什么样的结果集
Statement
·int executeUpdate(String sql)执行更新操作,即执行INSERT、UPDATE、DELETE语句,其实这个方法也可以执行CREATE TABLE、ALTER TABLE,以及DROP TABLE等语句,但我们很少会使用JDBC来执行这些语句。
·ResulSet executeQuery(String sql)执行查询操作,返回ResultSet结果集数据。
·boolean execute()执行增、删、改、查所有SQL语句,返回一个布尔类型,表示SQL语句是否有结果。如果execute()执行的是更新语句,那么还要强调用int getUpdateCount()来获取增删改影响的行数;如果执行的是查询语句,需要调用ResultSet getResultSet()来获取查询结果。
5,结果集光标与元数据
executeQuery()光标的位置:
①相对位移,下一行,不可滚动forward_only(只能rs.next());②绝对位移,第几行。
void beforeFirst():把光标放在第一行的前面 void afterLast():把光标放在最后一行的后面 boolean first():把光标放在第一行,返回值表示光标调控是否成功 boolean last() boolean isBeforeFirst():当前光标是否在第一行的前面 boolean isAfterLast() boolean isFirst() boolean isLast() boolean previous():把光标往前挪动一行 boolean next() boolean relative(int row):相对位移,当row为正数的时候,表示向下移动row行,为负数时表示向上移动row行 boolean absolute(int row):绝对位移,把光标移动到指定的行上 int getRow():返回当前光标所在行 |
获取结果集元数
rs.getMetaData()获取元数据,返回值为ResultSetMetaData; int getColumnCount()获取结果数据集 String getColumnName(String colIndex)获取指定列的列名 |
6,结果集的三大特性【滚动、敏感、可更新】
当使用Connection的createStatement时,已经确定了Statement生成的结果集是什么类型。
conn.createStatement();返回值->【不滚动-只有next()相对方法、不敏感、不可更新】
conn.createStatement(int,int);//方法重载 第一个参数: ResultSet.TYPE_FORWARD_ONLY:不滚动 ResultSet.TYPE_SCROLL_INSENSITIVE:滚动结果集,但结果集数据不会跟随数据库变化而变化 ResultSet.TYPE_SCROLL_SENSITIVE:滚动结果集,结果随着数据库变化而变化【实时性!理想!】 第二个参数: CONCUR_READ_ONLY:结果集是只读的,不能通过修改结果集而反向影响数据库 CONCUR_UPDATE:结果集是读写的,可以通过修改结果集而反向影响数据库 |
注意:mysql数据库默认全是可滚动的,没有FORWARD_ONLY的
ResultSet方法:
getInt()、getString()、getDouble()、getBoolean()、getObject()
7,PreparedStatement的用法
PreparedStatement是Statement的子接口,优点:①防SQL攻击;②提高代码的可读性与可维护性;③提高效率。
·范例:一个简单的SQL攻击程序
package cn.itcast.demo4; import javax.swing.plaf.nimbus.State; import java.sql.*; /** * 演示:SQL攻击 * @author Mufasa */ public class Demo4 { /** * 使用username和password查询数据, * 查询出结果返回true否则返回false * @param username * @param age * @return boolean */ public static boolean login(String username,String age) throws ClassNotFoundException, SQLException { String driverClassName="com.mysql.jdbc.Driver"; String url="jdbc:mysql://localhost:3306/mydb1"; String mysqlusername="root"; String mysqlpassword=""; Class.forName(driverClassName); Connection conn= DriverManager.getConnection(url,mysqlusername,mysqlpassword); Statement stmt=conn.createStatement(); String sql="SELECT * FROM emp WHERE ename='"+username+"'and age="+age; System.out.println(sql); ResultSet rs=stmt.executeQuery(sql); return rs.next(); } } |
import cn.itcast.demo4.Demo4; public class Main001 { public static void main(String[] args) throws Exception { // System.out.println(Demo4.login("红红的夏天",18)); System.out.println(Demo4.login("a' or 'a'='a","18 or 'a'='a'"));//SQL攻击 } } |
SELECT * FROM emp WHERE ename='红红的夏天'and age=18//→正常SQL SELECT * FROM emp WHERE ename='a' or 'a'='a'and age=18 or 'a'='a' |
学习使用PreparedStatement的用法:
①给出SQL模板;
②调用Connection中的preparedStatement(String sql模板);
③调用pstmt的setXxx()系列方法为sql模板中的?赋值;
④调用pstmt的executeUpdate()或executeQury()但是它的方法都没有参数。
public static boolean login2(String username,String age) throws ClassNotFoundException, SQLException { String driverClassName="com.mysql.jdbc.Driver"; String url="jdbc:mysql://localhost:3306/mydb1"; String mysqlusername="root"; String mysqlpassword=""; Class.forName(driverClassName); Connection conn= DriverManager.getConnection(url,mysqlusername,mysqlpassword); String sql="SELECT * FROM emp WHERE ename=? and age=?"; //①给出SQL模板 PreparedStatement pstmt=conn.prepareStatement(sql); //②调用prepareStatement pstmt.setString(1,username);//给第一个问号赋值 pstmt.setString(2,age);//给第一个问号赋值 ResultSet rs=pstmt.executeQuery(); //调用查询方法向数据库发送SQL语句,返回ResultSet数据 return rs.next(); } |
8,预处理的原理
服务器的工作:
①校验sql语句的语法【耗时】;
②编译,与函数类似的东西;
③执行,调用函数;
PreparedStatement:
①前提,数据库必须支持预处理【现在几乎没有不支持的】;
②每个pstmt都与一个sql语句绑定在一起,先把sql模板给数据库,数据库先进行校验、再进行编译、执行时只是把参数传递过去;
③二次执行时,无需再次校验语法,直接执行。【类似懒汉-单例-模式】
9,mysql的预编译功能
MySQL执行预编译分为三步【预处理MySQL4.0之后默认关闭】:
①执行预编译语句,例如:prepare myfun from'SELECT * FROM emp WHERE eid=?'
②设置变量,例如:set @str='b1'
③执行语句,例如:execute myfun using @str
如果二次执行myfun,那么就不再需要第一步【预编译语句】
使用Statement执行预编译就是把上面的SQL语句执行一次。
Connection conn=JdbcUtils.getConnection(); Statement stmt=conn.createStatement(); stmt.executeUpdate("prepare myfun from'SELECT * FROM emp WHERE eid=?'"); stmt.executeUpdate("set @str='b1'"); ResulSet rs=stmt.executeQuery("execute myfun using @str"); while(rs.next()){ System.out.println(rs.getString(1)); } stmt.executeUpdate("set @str='b2'");//二次运行 ResulSet rs=stmt.executeQuery("execute myfun using @str"); while(rs.next()){ System.out.println(rs.getString(1)); } rs.close(); stmt.close(0; conn.close(); |
MySQL默认使用PreparedStatement是不能执行预处理编译的,这需要在url中给出useServerPrepStmts=true参数;当使用不同的PreparedStatement对象来执行相同的SQL语句时,还是会出现编译两次的现象,这是因为驱动没有缓存编译后的函数key,导致二次编译,如果希望缓存编译后函数的key,那么就要设置cachePrepStmts参数为true;打开批处理功能,rewriteBatchedStatements=true
例如:
①jdbc:mysql://localhost:3306/mydb1?useServerPrepStmts=true
②jdbc:mysql://localhost:3306/mydb1?useServerPrepStmts=true&cachePrepStmts=true
③jdbc:mysql://localhost:3306/mydb1?useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true
url中的关键字及其值的作用
10,JdbcUtils1.0小工具
JdbcUtils-V1.0【单次初始化配置文件、数据库】
package cn.itcast.demo1; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class JdbcUtils { private static Properties props=null; static {//静态代码段,对props进行初始化 try { InputStream in=JdbcUtils.class.getClassLoader().getResourceAsStream("configure//dbconfig.properties"); props=new Properties(); props.load(in); Class.forName(props.getProperty("driverClassName")); } catch (IOException e) { throw new RuntimeException(); } catch (ClassNotFoundException e) { throw new RuntimeException(); }finally { System.out.println("配置文件加载成功"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(props.getProperty("url"), props.getProperty("mysqlusername"), props.getProperty("mysqlpassword")); } } |
11,面向接口编程
修改项目:
①把UserDao修改为接口,然后把原来的UserDao修改类名为UserDaoImpl
②修改UserService中对UserDao的实例化:private UserDao userDao=DaoFactory.getUserDao()
③创建DaoFactory,提供getUserDao()方法
④使用配置文件来获取实体类的名称
DAO【Data Access Object】模式
DAO模式就是写一个类,把访问数据库的代码封装起来。DAO在数据库与业务【Service】逻辑之间。
·实体域,即操作的对象,例如我们操作的表示user表,那么就需要先写一个User类;
·DAO模式需要先提供一个DAO接口;
·然后提供一个DAO接口的实现类;
·再编写一个DAO工厂,Service通过工厂来获取DAO实现。
12,修改day14的登录案例
开闭原则:允许加,不允许改->需要重新测试
package cn.itcast.demo1; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class JdbcUserDaoImpl implements UserDao { Connection conn=null; PreparedStatement pstmt=null; @Override public void addUser() throws SQLException { try{ conn=JdbcUtils.getConnection(); String sql="INSERT INTO emp VALUES(?,?,?,?)"; pstmt=conn.prepareStatement(sql); pstmt.setString(1,from.getEid()); pstmt.setString(2,from.getEname()); pstmt.setString(3,from.getEage()); pstmt.setString(4,from.getEgender()); pstmt.executeUpdate(); }catch (Exception e){ throw new RuntimeException(e); }finally { if(pstmt!=null) pstmt.close(); if(conn!=null) conn.close(); } } } |
13,Util包下的Date与sql包下的时间类型转换
java.sql的东西不能出现在Dao以外的地方,否则就干扰了Service或者Server类
数据库类型与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类型
时间类似的转换:【sql是util的子类】
·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 date=java.sql.Date() |
14,大数据
目标:把MP3保存到数据库之中!
USE mydb1; CREATE TABLE tab_bin( id INT PRIMARY KEY AUTO_INCREMENT, filename VARCHAR(100), DATA MEDIUMBLOB ); |
import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; import java.util.Properties; public class JdbcUtils { private static Properties props=null; static {//静态代码段,对props进行初始化 try { InputStream in=JdbcUtils.class.getClassLoader().getResourceAsStream("configure/dbconfig.properties"); props=new Properties(); props.load(in); System.out.println(props.getProperty("driverClassName")); Class.forName(props.getProperty("driverClassName")); } catch (IOException e) { throw new RuntimeException(); } catch (ClassNotFoundException e) { throw new RuntimeException(); }finally { System.out.println("配置文件加载成功"); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(props.getProperty("url"), props.getProperty("mysqlusername"), props.getProperty("mysqlpassword")); } } |
import org.apache.commons.io.IOUtils; import javax.sql.rowset.serial.SerialBlob; import java.io.*; import java.sql.*; //import static com.sun.tools.doclint.Entity.copy;
public class Wrmp3 { public static void write_mp3() throws SQLException, IOException { Connection conn=JdbcUtils.getConnection();//①得到Connection String sql="INSERT INTO tab_bin VALUES(?,?,?)";//②给出sql模板 PreparedStatement pstmt=conn.prepareStatement(sql);//创建pstmt pstmt.setInt(1,1); pstmt.setString(2,"RockDrums.mp3"); /** * 有文件需要得到Blob * ①先把文件编程byte[]类型 * ②再使用byte[]创建Blob */ String mp3Path="E:\\RockDrums.mp3"; byte[] bytes=getMp3Content(mp3Path); Blob blob=new SerialBlob(bytes); pstmt.setBlob(3,blob); pstmt.executeUpdate(); } public static void read_mp3() throws SQLException, IOException { Connection conn=JdbcUtils.getConnection(); String sql="SELECT * FROM tab_bin"; PreparedStatement pstmt=conn.prepareStatement(sql); ResultSet rs=pstmt.executeQuery(); if(rs.next()){ Blob blob=rs.getBlob("data"); InputStream in = blob.getBinaryStream(); OutputStream out=new FileOutputStream("E:\\RockDrums_复写版.mp3"); IOUtils.copy(in,out); } } public static byte[] getMp3Content(String filePath) throws IOException { File file = new File(filePath); long fileSize = file.length(); if (fileSize > Integer.MAX_VALUE) { System.out.println("file too big..."); return null; }else { System.out.println(fileSize); } FileInputStream fi = new FileInputStream(file); byte[] buffer = new byte[(int) fileSize]; int offset = 0; int numRead = 0; while (offset < buffer.length && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) { offset += numRead; } // 确保所有数据均被读取 if (offset != buffer.length) { throw new IOException("Could not completely read file " + file.getName()); } fi.close(); return buffer; } } |
import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; public class Main { public static void main(String[] args) throws SQLException, IOException { // Wrmp3.write_mp3(); Wrmp3.read_mp3(); } } |
15,批处理
批处理就是一批一批的处理,而不是一个一个的处理。
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; public class Demo5 { public static void fun5() throws SQLException { Connection conn=JdbcUtils.getConnection(); String sql="INSERT INTO batch_emp VALUES(?,?,?,?)"; PreparedStatement pstmt=conn.prepareStatement(sql); /* 疯狂添加数据*/ for(int i=0;i<10000;i++){ pstmt.setInt(1,i+1); pstmt.setString(2,"emp_"+i); pstmt.setInt(3,18); pstmt.setString(4,i%2==0?"男":"女"); pstmt.addBatch();//添加批 } long start = System.currentTimeMillis(); pstmt.executeBatch();//执行批 long end = System.currentTimeMillis(); System.out.println(end-start); } } |
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb1?useServerPrepStmts=true&cachePrepStmts=true&rewriteBatchedStatements=true mysqlusername=root mysqlpassword= |