1.JDBC
1.1.数据库驱动
1.1.1.数据库驱动的概念
??????数据库厂商提供的用来操作数据库的jar包就叫做数据库的驱动。 ??????把某个java项目打包成jar文件,提供给其他人使用。。 使用的时候,解压jar文件,生成一个文件夹,里面存放的就是编译好的每个包下面的类文件。。
1.2.JDBC
1.2.1.JDBC的概念
??????JDBC(Java DataBase Connectivity)就是Java数据库连接,说白了就是用Java语言来操作数据库。 ??????由于不同的数据库厂商提供的数据库驱动各不相同,在使用不同数据库时需要学习对应数据库驱动的api,对于开发人员来说学习成本十分的高。 ??????于是sun提供了JDBC的规范,也就是jar包,本质上一大堆的接口,要求不同的数据库厂商提供的驱动都实现这套接口,这样以来开发人员只需要学会JDBC这套接口,所有的数据库驱动作为这套接口的实现,就都会使用了。
1.2.2.JDBC包
??????JDBC主要是由 java.sql 和javax.sql包组成的,并且这两个包已经被集成到J2SE的规范中了,这意味着,只要一个普通的java程序就可以使用JDBC。 ??????要注意的是,在开发数据库程序时,除了如上的两个包,还需要手动的导入具体的数据库驱动。 ??????注意: jdbc只是一套接口,具体操作数据库的代码都在接口的实现类中,也就是数据库驱动中,开发时还是要导入具体的数据库驱动.
1.3.六个步骤实现JDBC
??????在导入Connection、Statement、ResultSet包的时候,导的都是接口而不是实现类,是为了程序更加通用,不管是mysql还是Oracle数据库或者其他数据库都可以接收住。
Connection conn = null;
Statement stat = null;
ResultSet rs = null;
try {
DriverManager.registerDriver(new Driver());
conn=DriverManager.getConnection("jdbc:mysql:///mydb1",user="root",password="root");
stat = conn.createStatement();
rs = stat.executeQuery("select * from user");
while(rs.next()){
String name = rs.getString("name");
Date date = rs.getDate("birthday");
System.out.println(name+":"+password);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(rs != null){
try {rs.close();} catch(SQLException e)
{e.printStackTrace();} finally{rs = null;}
}
if(stat != null){
try {stat.close();} catch (SQLException e)
{e.printStackTrace();} finally{stat = null;}
}
if(conn != null){
try {conn.close();} catch (SQLException e){
e.printStackTrace();}finally{conn = null;}
}
}
1.4.JDBC API详解
1.4.1.注册数据库驱动
??????使用DriverManager.registerDriver(new Driver())方式注册数据库有两个缺点,首先,通过观察mysql的中Driver接口的实现类发现在静态代码块中注册驱动的逻辑,所以这种方式会造成驱动被注册两次。另外,这种方式导致了程序和具体的数据库驱动绑死在了一起,程序的灵活性比较低。 ??????所以推荐使用:Class.forName(“com.mysql.jdbc.Driver”);的方式注册数据库驱动。 使用这种方式程只是和数据库驱动的全限定名绑死在一起,后期可以将全限定名提取到配置文件中。
获取数据库连接
Connection conn = DriverManager.getConnection(url,name,psw);
1.4.2.数据库URL
??????URL用于标识数据库的位置,程序员通过URL地址告诉JDBC程序连接哪个数据库,URL的写法为:
jdbc:mysql://localhost:3306/test ?参数名=参数值
??????常用数据库URL地址的写法:
/*Oracle写法:*/
jdbc:oracle:thin:@localhost:1521:sid
/*SqlServer写法:*/
jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=sid
/*MySql:*/
jdbc:mysql://localhost:3306/sid
/*如果是是访问本机的数据库,localhost可以省略不写,如果在配置数据库的时候,端口号默认的是3306,那么3306也可以省略不写。
Mysql的url地址的简写形式: */
jdbc:mysql:///sid
1.4.3.Connection
??????Jdbc程序中的Connection,它用于代表数据库的连接,Connection是数据库编程中最重要的一个对象,客户端与数据库所有交互都是通过connection对象完成的,这个对象的常用方法:
createStatement()
prepareStatement(sql):
prepareCall(sql)
setAutoCommit(boolean autoCommit)
commit()
rollback()
1.4.4.Statement
??????Jdbc程序中的Statement对象用于向数据库发送SQL语句, Statement对象常用方法:
executeQuery(String sql)
executeUpdate(String sql)
返回一个int值,代表影响的行数。。
execute(String sql)
addBatch(String sql)
executeBatch()
1.4.5.ResultSet
??????Jdbc程序中的ResultSet用于代表Sql语句的执行结果。Resultset封装执行结果时,采用的类似于表格的方式。ResultSet 对象维护了一个指向表格数据行的游标,初始的时候,游标在第一行之前,调用ResultSet.next() 方法,可以使游标指向具体的数据行,进行调用方法获取该行的数据。 Result的next()方法。不仅用于判断是否有下一行语句,还有把光标下移的功能 ResultSet既然用于封装执行结果的,所以该对象提供的都是用于获取数据的get方法:
getObject(int index) (传入的参数是列的下标)
getObject(string columnName) (传入的参数是列的名称)
获取指定类型的数据,例如:
getString(int index)
getString(String columnName)
getInt(columnIndex)
getInt(columnLabel)
getDouble(columnIndex)
getDouble(columnLabel)
next():移动到下一行
Previous():移动到前一行
absolute(int row):移动到指定行
beforeFirst():移动resultSet的最前面。(第一行的前面)
afterLast() :移动到resultSet的最后面。 (最后一行的后面)
1.4.6.释放资源
??????Jdbc程序运行完后,切记要释放程序在运行过程中,创建的那些与数据库进行交互的对象,这些对象通常是ResultSet, Statement和Connection对象。 ??????释放资源的时候注意顺序:要按先 ResultSet 结果集,后 Statement,最后 Connection 的顺序关闭资源,因为 Statement 和 ResultSet 是需要连接时才可以使用的,所以在使用结束之后有可能其它的 Statement 还需要连接,所以不能现关闭 Connection。 ??????特别是Connection对象,它是非常稀有的资源,用完后必须马上释放,如果Connection不能及时、正确的关闭,极易导致系统宕机。Connection的使用原则是尽量晚创建,尽量早的释放。 ??????为确保资源释放代码能运行,资源释放代码也一定要放在finally语句中。
import java.io.FileInputStream;
import java.sql.*;
import java.util.Properties;
public class JDBCUtils {
private static Properties prop = new Properties();
private JDBCUtils(){}
static{
try {
ClassLoader classLoader = JDBCUtils.class.getClassLoader();
String path = classLoader.getResource
("config.properties").getPath();
prop.load(new FileInputStream(path));
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static Connection getConnection(){
Connection conn = null;
try {
String driverClass = prop.getProperty("driverClass");
String jdbcurl = prop.getProperty("jdbcurl");
String user = prop.getProperty("user");
String password = prop.getProperty("password");
Class.forName(driverClass);
conn = DriverManager.getConnection(jdbcurl,user,password);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
return conn;
}
public static void close(Connection conn,Statement stat,ResultSet rs){
if(rs != null){
try {rs.close();}
catch (SQLException e) {e.printStackTrace();}finally{rs = null;}
}
if(stat != null){
try {stat.close();}
catch(SQLException e){e.printStackTrace();}finally{stat= null;}
}
if(conn != null){
try {conn.close();}
catch(SQLException e){e.printStackTrace();}finally{conn=null;}
}
}
}
1.5.SQL注入攻击
1.5.1 Sql注入攻击演示
select * from user where username='张三'#'' and password=''
select * from user where username='张三' or '2=2' and password=''
发现无需输入密码,直接登录,发现无需密码登录了进去。这就是发生了SQL注入问题。
1.5.2 Sql注入攻击的原理
?????? 由于jdbc程序在执行的过程中sql语句在拼装时使用了由页面传入参数,如果用户,恶意传入一些sql中的特殊关键字或者特殊符号,会导致sql语句意义发生变化,从而造成一些意外的操作,这种攻击方式就叫做sql注入。 ??????如何防止SQL注入攻击呢? ??????这时候就需要用到PreparedStatement对象。
1.6 PreparedStatement
1.6.1.PreparedStatement对象
??????PreparedStatement是Statement的子接口,不同的是,PreparedStatement使用预编译机制,在创建PreparedStatement对象时就需要将sql语句传入,传入的过程中参数要用?替代,这个过程回导致传入的sql被进行预编译,然后再调用PreparedStatement的setXXX将参数设置上去,由于sql语句已经经过了预编译,再传入特殊值也不会起作用了。 ??????PreparedStatement使用了预编译机制,sql语句在执行的过程中效率比Statement要高。 ??????PreparedStatement使用了 “?”通配符省去了字符串的拼接,使代码更加优雅。
PreparedStatement的优点:(如果SQL语句有参数) (1)可以防止sql注入攻击 通过PreparedStatement对象发送SQL,是先把SQL语句的骨架发送给数据库编译并确定下来,后面发送的只能是参数的值,不能影响SQL语句的骨架,即使参数中包含SQL关键字或者特殊符号,也只会当成普通的文本来处理。 (2)通过方法来设置参数,省去了拼接SQL语句的麻烦 (3)可以提高程序的效率: 通过PreparedStatement对象发送的SQL语句(骨架)到数据库编译后会被数据缓存下来,如果下次执行的SQL与缓存中的相匹配,就不用再编译而是直接使用缓存中的语句,可以减少SQL语句编译的次数,提高程序执行的效率。
Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。
如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。
Statement对象发送的sql语句到数据库之后也会编译,但是Statement对象是先拼接好再发送SQL到数据库,如果每次参数不同,整条sql语句也就不同了,所以每次都需编译。
如果sql语句中没有参数,那么PreparedStatement对象和Statement对象的功能就是相差无几的,不存在注入问题。
2.批处理
2.1.批处理业务场景
??????概述: ??????假设现有一大堆的SQL语句要到数据库中去执行,如果一条一条发送,有多少条就要发送多少次,效率低下。 ??????可以通过批处理提高发送SQL语句的效率:可以将这一大堆的SQL语句添加到一个批中,一次性将批发送给数据库, 数据库收到后打开批, 依次执行其中sql语句, 这样可以减少sql语句发送的次数, 从而提高程序执行的效率!
2.1.1.批处理业务场景
??????当需要向数据库发送一批SQL语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC的批处理机制,以提升执行效率。
2.1.2.Statement方式实现批处理
Statement.addBatch(sql)
执行批处理SQL语句
executeBatch()方法:执行批处理命令
clearBatch()方法:清除批处理命令。 将该批中的语句清除,再次放入新的执行的语句
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
String sql1 = "insert into per(name,password)values('kk','123')";
String sql2 = "update user set password='123' where id=3";
st = conn.createStatement();
st.addBatch(sql1);
st.addBatch(sql2);
st.executeBatch();
} finally{
JdbcUtil.free(conn, st, rs);
}
采用Statement.addBatch(sql)方式实现批处理: 优点: ??????可以在一次批处理中发送多条结构不同的SQL语句。 缺点: ??????不能防止SQL注入 ??????SQL语句没有预编译,效率较低。 ??????当向数据库发送多条结构相同的语句时,SQL语句的骨架每次都要编写。
2.1.3.PreparedStatement方式实现批处理
PreparedStatement.addBatch()
conn = JdbcUtil.getConnection();
String sql = "insert into person(name,password)values(?,?)";
ps = conn.prepareStatement(sql);
conn.setAutoCommit(false);
for(int i=0;i<50;i++){
ps.setString(1, "aaa" + i);
ps.setString(2, "123" + i);
ps.addBatch();
}
}
ps.executeBatch();
conn.commit();
采用PreparedStatement.addBatch()实现批处理: 优点: ??????可以防止sql注入。 ??????发送的是预编译后的SQL语句,执行效率高。 ??????当发送多条结构相同的sql时,SQL语句的骨架可以只发送一次。 缺点: ??????不能在一次批处理中添加结构不同的sql语句。 ??????因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
3.连接池
3.1.连接池概述
??????用户每次请求都需要向数据库获得链接,而数据库创建连接通常需要消耗相对较大的资源,创建时间也较长。(使用连接并没有消耗多少资源,浪费多长时间。)假设网站一天10万访问量,数据库服务器就需要创建10万次连接,极大的浪费数据库的资源,并且极易造成数据库服务器内存溢出、宕机。 频繁的开关连接相当的耗费资源,所以我们可以设置一个连接池,在程序启动时就初始化一批连接,在程序中共享,需要连接时从池中获取,用完再还回池中,通过池共享连接,减少开关连接的次数,提高程序的效率。 Sun公司为连接池提供 javax.sql.DataSource接口,要求连接池去实现,所以连接池也叫数据源。 我们可以自己实现这个接口来实现一个连接池。
import java.io.PrintWriter;
import java.sql.*;
import java.util.LinkedList;
import java.util.List;
import javax.sql.DataSource;
public class MyPool implements DataSource {
private static List<Connection> pool=new LinkedList<Connection>();
static{
try{
Class.forName("com.mysql.jdbc.Driver");
for(int i=0;i<5;i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
pool.add(conn);
}
}catch (Exception e) {e.printStackTrace();
throw new RuntimeException(e);
}
}
public Connection getConnection() throws SQLException {
if(pool.isEmpt()){
for(int i=0;i<3;i++){
Connection conn = DriverManager.getConnection("jdbc:mysql:///day11","root","root");
pool.add(conn);
}
}
return pool.remove(0);
}
private void retConn(Connection conn){
try {
if(conn!=null && !conn.isClosed()){
pool.add(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
public Connection getConnection(String username, String password)
throws SQLException {return null;}
public PrintWriter getLogWriter() throws SQLException {}
public int getLoginTimeout() throws SQLException {return 0;}
public void setLogWriter(PrintWriter out) throws SQLException {}
public void setLoginTimeout(int seconds) throws SQLException {}
public boolean isWrapperFor(Class<?> iface) throws SQLException
{return false;}
public<T> T unwrap(Class<T> iface) throws SQLException
{return null;}
}
当从连接池中获取连接,需要在使用完后不能关闭连接,而是要调用retConn方法将连接还回池中。
|