JDBC实现原理与过程分析
JDBC
JDBC (Java Data Base Connect)Java数据库连接
sun公司为了简化开发人员对数据库统一的操作,提供了一个Java操作数据库的规范,俗称JDBC,这些规范的具体实现由具体的厂商去做(MySQL驱动,Oracle驱动,SQL Sever驱动)
简单JDBC程序实现
前提下载并导入数据库驱动包:mysql-connector-java-5.1.25.jar
public class Demo1 {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
//1.加载驱动
Class<?> aClass = Class.forName("com.mysql.jdbc.Driver");
//2. 用户信息
String url = "jdbc:mysql://localhost:3306/xm?useUnicode=true&characterEncoding=utf8&useSSL=true";
String userName = "root";
String password = "XiaoMeng";
//3.驱动连接,获取数据库对象 connection
Connection connection = DriverManager.getConnection(url, userName, password);
//4.获取执行SQL的对象 statement
Statement statement = connection.createStatement();
//5. 利用statement对象,去执行SQL
String sql = "select * from student1";
/*所以的删除和插入都叫更新*/
//6.执行SQL获取结果集(封装了查询结果)
ResultSet resultSet = statement.executeQuery(sql);
//7.结果集的查看
while (resultSet.next()) {
System.out.print("id" + resultSet.getObject("id"));
System.out.print("name" + resultSet.getObject("name"));
System.out.println("grade" + resultSet.getObject("grade"));
}
//8.释放连接
resultSet.close();
statement.close();
connection.close();
}
}
过程分析
1.加载驱动
1.加载驱动
//DriverManager.registerDriver(new com.mysql.jdbc.Driver());注册两次驱动 DriverManager.registerDriver(淘汰)
Class.forName("com.mysql.jdbc.Driver");
//类加载,执行静态代码块(淘汰)
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
2.连接数据库DriverManager,获得数据库对象
//获得用户信息,作为获取数据库对象的参数
//url : 协议://主机地址:端口号/数据库? 参数1&参数2&参数3;
String url = "jdbc:mysql://localhost:3306/xm?useUnicode=true&characterEncoding=utf8&useSSL=true";
String userName = "root";
String password = "XiaoMeng";
//获取数据库对象 connection
Connection connection = DriverManager.getConnection(url, userName, password);
//凡是和数据库相关的操作,connection对象都可以完成 比如:事务操作,commit(),rollback(),setAutoCommit(true)等等
3.获取执行SQL语句的对象,statement (SQL具体执行类Statement 的对象) preparedStatement
// statement or preparedStatement 是执行sql语句的对象
//statement 对象 来 excute(执行)sql 语句
boolean execute = statement.execute(sql);//可以执行任何sql,效率低
ResultSet resultSet = statement.executeQuery(sql); //查询操作,返回ResultSet数据集
int i = statement.executeUpdate(sql);//更新,插入,删除数据都用这个,返回一个受影响的行数
4.查看数据集ResultSet结果 OR 关闭资源(资源关闭的顺序按照先开后关的原则)
//如果不知道列数据类型,就用getObject()
resultSet.getObject(int columnIndex or String columnLabel);
//如果知道列数据类型,就用对应的的类型,例如 列类型为varchar,对应于getString()
resultSet.getString(int columnIndex or String columnLabel);
resultSet.getInt(int columnIndex or String columnLabel);
resultSet.getDate(int columnIndex or String columnLabel);
resultSet.getDouble(int columnIndex or String columnLabel);
......
//原理:迭代器 遍历数据集
while (resultSet.next()) {
System.out.print("id" + resultSet.getInt("id"));
System.out.print("name" + resultSet.getString("name"));
System.out.println("grade" + resultSet.getInt("grade"));
}
resultSet.beforeFirst();//迭代器移动到最前面
resultSet.afterLast();//迭代器移动到最后面
resultSet.next();//迭代器移动到下一行(记录)
resultSet.previous();//迭代器移动到前一行(记录)
resultSet.absolute(5);//迭代器移动到指定行(记录)
// 关闭资源
resultSet.close();
statement.close();
connection.close();//消耗内存资源
JDBC 解耦操作
db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/xm?useUnicode=true&characterEncoding=utf8&useSSL=true
userName=root
password=XiaoMeng
JDBCUtil
public class JDBCUtil {
private static String driver=null;
private static String url=null;
private static String password=null;
private static String userName=null;
static{
try{
InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(is);
driver=properties.getProperty("driver");
url=properties.getProperty("url");
userName=properties.getProperty("userName");
password=properties.getProperty("password");
//加载驱动
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,userName,password);
}
public static void release(Connection connection, Statement statement,ResultSet resultSet)
{
if(resultSet!=null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(statement!=null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(connection!=null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
JDBCTest
public class TestUtil {
public static void main(String[] args) {
Connection connection=null;
Statement statement=null;
ResultSet resultSet=null;
try {
connection = JDBCUtil.getConnection();
statement= connection.createStatement();
String sql="select * from student1";
resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.print("id"+resultSet.getInt(1));
System.out.println();
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JDBCUtil.release(connection,statement,resultSet);
}
}
}
SQL注入
SQL注入是指的是Web应用程序对用户输入数据的合法性没有判断或过滤不严谨,攻击者可以在Web应用程序中事先定义好的查询语句的结尾添加一些额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
sql存在漏洞,会被攻击导致数据泄露,SQL语句被拼接or等
PreparedStatement
防止SQL注入,更加安全有效。
本质: PreparedStatement 防止SQL注入的本质是把传递进来的参数当成字符,
假设其中的参数包含转义字符,会被直接转义,当成普通字符处理,不会对SQL语句产生影响。
具体细节见程序
public class PreparedTest {
public static void main(String[] args) {
Connection connection=null;
try {
connection= JDBCUtil.getConnection();
//使用?占位符代替参数
/*PreparedStatement 防止SQL注入的本质是把传递进来的参数当成字符,
假设其中的参数包含转义字符,会被直接转义当成普通字符处理,不会对SQL语句产生影响。
*/
String sql="insert into student1 (name, grade,DATA)values(?,?,?)";
//预编译SQL,先写sql,不执行。
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1,"小鸟6号");
preparedStatement.setInt(2,70);
preparedStatement.setDate(3,new Date(new java.util.Date().getTime()));
//执行操作,不加参数。
int i = preparedStatement.executeUpdate();
if(i>0)
System.out.println("插入成功");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
Different of Statement ,PreparedStatement
关系:PreparedStatement继承自Statement,都是接口
区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高
详解:
- PreparedStatement:表示预编译的 SQL 语句的对象。
接口:public interface PreparedStatement extends Statement之间的继承关系
SQL 语句被预编译并存储在 PreparedStatement 对象中。然后可以使用此对象多次高效地执行该语句。 - preparedStatement.execute()提交时这里不能再有sql语句,不同于Statement
JDBC操作事务
public class TransactionTest {
public static void main(String[] args) {
Connection connection=null;
PreparedStatement preparedStatement=null;
try {
connection = JDBCUtil.getConnection();
//Java中关闭自动提交就是打开了事务。一气呵成,在SQL中,需要两步1.关闭自动提交 2.打开事务start transaction;。事务完成后以后手动打开自动提交
connection.setAutoCommit(false);
String sql="update shop set money=money+100 where id=1";
preparedStatement = connection.prepareStatement(sql);
preparedStatement.executeUpdate();
String sql1="update shop set money=money-100 where id=2";
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
//业务完成,提交事务
connection.commit();
System.out.println("业务成功");
} catch (SQLException e) {
// try {
// System.out.println("业务失败,回滚");
// //业务失败,回滚。可以不显式定义,默认回滚。
// connection.rollback();
// } catch (SQLException ex) {
// ex.printStackTrace();
// }
e.printStackTrace();
}finally {
JDBCUtil.release(connection,preparedStatement,null);
}
}
}
DBCP(数据库连接池)
简介
数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的 性能低下。 数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并讲这些连接组成一个连接池(简单说:在一个“池”里放了好多半成品的数据库联接对象),由应用程序动态地对池中的连接进行申请、使用和释放。对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。 连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时,我们可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。
数据库连接池工作原理
第一、连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。
第二、连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是:
当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。
当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。
第三、连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反
连接池中的事务处理
我们知道,事务具有原子性,此时要求对数据库的操作符合“ALL-OR-NOTHING”原则,即对于一组SQL语句要么全做,要么全不做。
我们知道当2个线程共用一个连接Connection对象,而且各自都有自己的事务要处理时候,对于连接池是一个很头疼的问题,因为即使Connection类提供了相应的事务支持,可是我们仍然不能确定那个数据库操作是对应那个事务的,这是由于我们有2个线程都在进行事务操作而引起的。为此我们可以使用每一个事务独占一个连接来实现,虽然这种方法有点浪费连接池资源但是可以大大降低事务管理的复杂性。
补充:
- 在正常情况下,不能直接将 ResultSet 记录集 resultSet 直接传递给前台的,因为 ResultSet 不仅无法进行循环遍历(即只能每行遍历,从0至end ,执行一次)我们就需要将 ResultSet 对象进行遍历到 list 中
private static List convertList(ResultSet rs) throws SQLException {
List list = new ArrayList();
ResultSetMetaData md = rs.getMetaData();//获取有关`ResultSet`对象中列的类型和属性的信息的对象。
int columnCount = md.getColumnCount();//获取行的数量
while (rs.next()) {
Map rowData = new HashMap();//声明Map
for (int i = 1; i <= columnCount; i++) {
rowData.put(md.getColumnName(i), rs.getObject(i));//获取键名及值
}
list.add(rowData);
}
return list;
}