01_JDBC基本使用
一、JDBC概述
1.1数据的持久化
持久化(persistence):**把数据保存到可掉电式存储设备中以供之后使用。**大多数情况下,特别是企业级应用,数据持久化意味着将内存中的数据保存到硬盘上加以”固化”,而持久化的实现过程大多通过各种关系数据库来完成。
2.Java中的数据存储技术
- 在Java中,数据库存取技术可分为如下几类:
JDBC直接访问数据库
JDO (Java Data Object )技术
第三方O/R工具,如Hibernate, Mybatis 等 - JDBC是java访问数据库的基石,JDO、Hibernate、MyBatis等只是更好的封装了JDBC。
3.JDBC介绍
- JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)
使用这些类库可以以一种标准的方法、方便地访问数据库资源。 - JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。
- JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。
- 如果没有JDBC,那么Java程序访问数据库时是这样的:
- 有了JDBC,Java程序访问数据库时是这样的:
4.JDBC体系结构
- JDBC接口(API)包括两个层次:
①面向应用的API: Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)。
②面向数据库的API: Java Driver API,供开发商开发数据库驱动程序用。
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。
5.JDBC程序编写步骤
补充:ODBC(Open Database Connectivity,开放式数据库连接),是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由 ODBC 驱动程序将调用转换成为对特定的数据库的调用请求。
二、获取数据库连接
1.要素一:Driver接口实现类
1.1.Driver接口介绍
- java.sql.Driver 接口是所有 JDBC 驱动程序需要实现的接口。这个接口是提供给数据库厂商使用的,不同数据库厂商提供不同的实现。
- 在程序中不需要直接去访问实现了 Driver 接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
Oracle的驱动:oracle.jdbc.driver.OracleDriver
mySql的驱动:com.mysql.jdbc.Driver
1 获取数据库链接驱动
oracle的驱动包:
mysql的驱动包:
2 创建一个java project
3 在工程目录下 创建一个目录 libs
4 将数据库的驱动包拷贝到libs目录下
5 将数据库驱动包添加到类路径下
2.要素二:URL
- JDBC URL 用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。
- JDBC URL的标准由三部分组成,各部分间用冒号分隔。
jdbc:子协议:子名称
协议: JDBC URL中的协议总是jdbc
子协议: 子协议用于标识一个数据库驱动程序
子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名
举例:
几种常用数据库的 JDBC URL**
- MySQL的连接URL编写方式:
jdbc:mysql://主机名称:mysql服务端口号/数据库名称?参数=值&参数=值
mysql: jdbc:mysql://localhost:3306/zb?useUnicode=true&characterEncoding=utf8
- Oracle 11g的连接URL编写方式:
jdbc:oracle:thin:@主机名称:oracle服务端口号:数据库名称
jdbc:oracle:thin:@192.168.112.128:1521:orcl
3.要素三:用户名和密码
- user,password可以用“属性名=属性值”方式告诉数据库
- 可以调用 DriverManager 类的 getConnection() 方法建立到数据库的连接
4.数据库连接方式举例
1.连接方式一:
@Test
public void connectionTest() throws SQLException {
1.提供java.sql.Driver接口实现类的对象
Driver driver = new com.mysql.jdbc. Driver();
//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/lanqiao";
//3.提供Properties的对象,指明用户名和密码
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "root");
//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, pro);
System.out.println(conn);
}
访问时可能存在的问题:
解决方法(赋予权限):
grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option;
flush privileges;
方式一存在是的应用程序和Mysql的jdbc的实现类发生了直接的耦合
2.连接方式二
@Test
public void connectionTest2() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//1.实例化Driver
String driverStr = "com.mysql.jdbc.Driver";//Driver类的全类名
Class clazz = Class.forName(driverStr);
Driver driver = (Driver) clazz.newInstance();
//2.提供url,指明具体操作的数据
String url = "jdbc:mysql://localhost:3306/lanqiao";
//3.提供Properties的对象,指明用户名和密码
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "root");
//4.调用driver的connect(),获取连接
Connection conn = driver.connect(url, pro);
System.out.println(conn);
}
使用反射机制 来解除耦合
说明:相较于方式一,这里使用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思想。
3.连接方式三:DriverManager使用注册驱动
@Test
public void connectionTest3() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "root";
String driverName = "com.mysql.jdbc.Driver";
//2.实例化Driver
Class clazz = Class.forName(driverName);
Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
DriverManager.registerDriver(driver);
//4.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
说明:使用DriverManager实现数据库的连接。体会获取连接必要的4个基本要素。
4.连接方式四
@Test
public void connectionTest4() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//定义URL
String url = "jdbc:mysql://localhost:3306/lanqiao";
//创建driver
String driverStr = "com.mysql.jdbc.Driver";//Driver类的全类名
//加载mysql的Dirver
Class.forName(driverStr);
//通过驱动来获取链接
Properties pro = new Properties();
pro.setProperty("user", "root");
pro.setProperty("password", "root");
Connection conn = DriverManager.getConnection(url, pro);
System.out.println(conn);
}
@Test
public void connectionTest4() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException {
//1.数据库连接的4个基本要素:
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "abc123";
String driverName = "com.mysql.jdbc.Driver";
//2.加载驱动 (①实例化Driver ②注册驱动)
Class.forName(driverName);
//Driver driver = (Driver) clazz.newInstance();
//3.注册驱动
//DriverManager.registerDriver(driver);
/*
可以注释掉上述代码的原因,是因为在mysql的Driver类中声明有:
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
*/
//3.获取连接
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println(conn);
}
存在的问题 :连接属性采用了硬编码的方式
5.连接方式五(最终版)
说明:使用配置文件的方式保存配置信息,在代码中加载配置文件
使用配置文件的好处:
①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程。
解决硬编码的问题
建立配置属性文件
jdbc.properties
url=jdbc:mysql://localhost:3306/lanqiao
driver =com.mysql.jdbc.Driver
user=root
password=root
jdbc链接数据库的最终版
@Test
public void connectionTest5() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
//第一步加载配置文件
InputStream in =this.getClass().getClassLoader().getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
//定义URL
String url = pro.getProperty("url");
//从配置文件中获取链接属性
String driverStr = pro.getProperty("driver");//Driver类的全类名
String user = pro.getProperty("user");
String password=pro.getProperty("password");
//加载mysql的Dirver
Class.forName(driverStr);
Connection conn = DriverManager.getConnection(url, user,password);
System.out.println(conn);
}
注意:
必须将配置文件放在src目录下,否则会出现空指针异常和配置文件异常
链接oracle数据库
#oracle
oracle.url=jdbc:oracle:thin:@192.168.112.128:1521:orcl
oracle.driver =oracle.jdbc.driver.OracleDriver
oracle.user=hr
oracle.password=hr
@Test
public void connectionTest6() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException, IOException {
//第一步加载配置文件
InputStream in =this.getClass().getClassLoader().getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
//定义URL
String url = pro.getProperty("oracle.url");
//创建driver
String driverStr = pro.getProperty("oracle.driver");//Driver类的全类名
//创建用户名和密码
String user = pro.getProperty("oracle.user");
String password=pro.getProperty("oracle.password");
//加载mysql的Dirver
Class.forName(driverStr);
Connection conn = DriverManager.getConnection(url, user,password);
System.out.println(conn);
}
说明:使用配置文件的方式保存配置信息,在代码中加载配置文件
使用配置文件的好处:
①实现了代码和数据的分离,如果需要修改配置信息,直接在配置文件中修改,不需要深入代码
②如果修改了配置信息,省去重新编译的过程。
三、使用PreparedStatement实现CRUD操作
- 数据库连接被用于向数据库服务器发送命令和 SQL 语句,并接受数据库服务器返回的结果。其实一个数据库连接就是一个Socket连接。
- 在 java.sql 包中有 3 个接口分别定义了对数据库的调用的不同方式:
①Statement: 用于执行静态 SQL 语句并返回它所生成结果的对象。
②PrepatedStatement: SQL 语句被预编译并存储在此对象中,可以使用此对象多次高效地执行该语句。
③CallableStatement: 用于执行 SQL 存储过程
1.使用Statement操作数据表的弊端
获取连接:
public static Connection getConnection() throws ClassNotFoundException, IOException, SQLException {
InputStream in = Class.forName("org.lanqiao.jdbc.StatementTest").getClass().getClassLoader().getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
String driver = pro.getProperty("driver");
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, user, password);
return conn;
}
插入数据:
//插入数据
@Test
public void teststmtInsert() throws ClassNotFoundException, IOException, SQLException {
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "INSERT into student(stu_id,stu_age,stu_sex,stu_name)" +
"VALUES(5,20,'女','wangba')";
boolean res = stmt.execute(sql);
System.out.println(res );
}
如果出现中文乱码 请使用一下URL
mysql.url=jdbc:mysql:///lanqiao?useUnicode=true&characterEncoding=utf8
更新数据:
//执行更新
@Test
public void teststmtUpdate() throws ClassNotFoundException, IOException, SQLException {
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "update student set stu_name = '花花',cls_id = 1 where stu_id = 5";
boolean res = stmt.execute(sql);
System.out.println(res );
}
删除数据:
//删除数据
@Test
public void teststmtDelete() throws ClassNotFoundException, IOException, SQLException {
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "delete from student where stu_id = 2";
boolean res = stmt.execute(sql);
System.out.println(res );
}
@Test
public void testStmtDelete() throws Exception {
// 获取数据库链接
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "delete from emp where empno=1017";
int res = stmt.executeUpdate(sql);//返回值表示受影响行数
System.out.println(res);
}
获取自动生成的主键 一般用在mysql、oracle中的新增操作 :
// 执行更新
@Test
public void testStmtInsert() throws Exception {
// 获取数据库链接
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "INSERT into emp(ename,job,mgr,hiredate,sal,COMM,deptno)"
+ " values('李四','经理',1001,now(),5000,500,10)";
int res = stmt.executeUpdate (sql,Statement.RETURN_GENERATED_KEYS);
System.out.println(res);
//获取生成的主键
ResultSet rs=stmt.getGeneratedKeys();
while(rs.next()) {
int key = rs.getInt(1);//列的索引从1开始
System.out.println(key);
}
}
执行查询
返回单行记录
// 执行查询 查询单个对象
@Test
public void testStmtQuery() throws Exception {
// 获取数据库链接
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "select * from emp where empno=1001";
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
String ename = rs.getString("ename");
String job = rs.getString(3);
Date hiredate = rs.getDate("hiredate");
double sal = rs.getDouble("sal");
System.out.println(ename+"--"+job+"--"+hiredate+"-"+sal);
}
}
// 执行查询 查询多行记录
@Test
public void testStmtQuery2() throws Exception {
// 获取数据库链接
Connection conn = StatementTest.getConnection();
Statement stmt = conn.createStatement();
String sql = "select * from emp ";
ResultSet rs = stmt.executeQuery(sql);
while(rs.next()) {
String ename = rs.getString("ename");
String job = rs.getString(3);
Date hiredate = rs.getDate("hiredate");
double sal = rs.getDouble("sal");
System.out.println(ename+"--"+job+"--"+hiredate+"-"+sal);
}
}
SQL注入(弊端):
//使用statement的问题
//模拟用户登录 通过用户输入用户名和密码 查询该用户是否存在 如果存在则说明用户名和密码正确
//此时可以正常登录
@Test
public void login() throws Exception {
Scanner sc = new Scanner(System.in);
System.out.println("请输入您的用户名:");
String username = sc.nextLine();
System.out.println("请输入密码");
String password = sc.nextLine();
Connection conn = getConnection();
String sql = "select * from users where username='"+username+"' and password='"+password+"'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
users ur = null;
while(rs.next()) {
//将查询结果封装为一个对象
ur = new Users();
ur.setUid(rs.getInt("uid"));
ur.setUsername(rs.getString("username"));
ur.setAge(rs.getInt("age"));
ur.setBirthday(rs.getDate("birthday"));
ur.setRealName(rs.getString("realname"));
}
if(ur != null) {
System.out.println("登录成功");
}else {
System.out.println("登录失败");
}
sc.close();
}
user实例类
应用表的结构与数据
第一种:
第二种:
使用statement 会存在sql注入的风险
ResultSet与ResultSetMetaData
获取ResultSet中的列的数量类型及属性
ResultSetMetaData rsmd = rs.getMetaData();//获取结果集的元数据信息
System.out.println("结果集中列的数量:" + rsmd.getColumnCount());
for(int i = 1 ; i <=rsmd.getColumnCount() ;i++) {
String columnName = rsmd.getColumnName(i);
String columnLable = rsmd.getColumnLabel(i);
String columnType = rsmd.getColumnTypeName(i);
int columnSize = rsmd.getColumnDisplaySize(i);
System.out.println(columnName +"--"+columnLable+"--"+columnType+"--"+columnSize);
}
查询代码的重构
//查询单个对象
public <T> T getObject(String sql, Class<T> clazz) {
// 第一步加载配置文件
InputStream in = null;
Connection conn = null;
Statement stmt =null;
ResultSet rs = null;
T t = null;//保存结果的对象
try {
in = this.getClass().getClassLoader()
.getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
// 定义URL
String url = pro.getProperty("mysql.url");
// 创建driver
String driverStr = pro.getProperty("mysql.driver");// Driver类的全类名
// 创建用户名和密码
String user = pro.getProperty("mysql.user");
String password = pro.getProperty("mysql.password");
// 加载mysql的Dirver
Class.forName(driverStr);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return t;
}
解决试题中的属性名称和数据库的字段名程不一致的情况:可以使用数据库字段的别名来解决
//查询多个对象 返回集合
public <T> List<T> getObjectList(String sql, Class<T> clazz) {
// 第一步加载配置文件
InputStream in = null;
Connection conn = null;
Statement stmt =null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
T t = null;//保存结果的对象
try {
in = this.getClass().getClassLoader()
.getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
// 定义URL
String url = pro.getProperty("mysql.url");
// 创建driver
String driverStr = pro.getProperty("mysql.driver");// Driver类的全类名
// 创建用户名和密码
String user = pro.getProperty("mysql.user");
String password = pro.getProperty("mysql.password");
// 加载mysql的Dirver
Class.forName(driverStr);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
list.add(t);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return list;
}
资源关闭 异常处理:
//查询多个对象 返回集合
public <T> List<T> getObjectList(String sql, Class<T> clazz) {
// 第一步加载配置文件
InputStream in = null;
Connection conn = null;
Statement stmt =null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
T t = null;//保存结果的对象
try {
in = this.getClass().getClassLoader()
.getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
// 定义URL
String url = pro.getProperty("mysql.url");
// 创建driver
String driverStr = pro.getProperty("mysql.driver");// Driver类的全类名
// 创建用户名和密码
String user = pro.getProperty("mysql.user");
String password = pro.getProperty("mysql.password");
// 加载mysql的Dirver
Class.forName(driverStr);
conn = DriverManager.getConnection(url, user, password);
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
list.add(t);
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(rs !=null) {
try {
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(stmt !=null) {
try {
stmt.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(conn !=null) {
try {
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(in!=null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
}
}
}
return list;
}
代码的再次重构
1 将获取数据库链接及释放链接的方法 提炼出一个工具类
①获取连接以及释放资源的工具类
package org.lanqiao.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class JDBCUtil {
// 获取链接
public static Connection getConnnection() {
// 第一步加载配置文件
InputStream in = null;
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
in = JDBCUtil.Class.getClassLoader().getSystemResourceAsStream("jdbc.properties");
Properties pro = new Properties();
pro.load(in);
// 获取链接属性
String url = pro.getProperty("mysql.url");
String driverStr = pro.getProperty("mysql.driver");// Driver类的全类名
String user = pro.getProperty("mysql.user");
String password = pro.getProperty("mysql.password");
// 加载mysql的Dirver
Class.forName(driverStr);
conn = DriverManager.getConnection(url, user, password);
} catch (Exception e) {
// TODO: handle exception
}
return conn;
}
// 释放资源
public static void releaseSource(ResultSet rs,Statement stmt,Connection conn) {
if(rs !=null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(stmt !=null) {
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}finally {
if(conn !=null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
}
}
}
②结果处理的工具类
package org.lanqiao.utils;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class ResultHandler {
public static <T> T getObject(String sql, Class<T> clazz) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
T t = null;
try {
conn = JDBCUtil.getConnection();
stmt = conn.createStatement();
rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return t;
}
//查询多个对象 返回集合
public static <T> List<T> getObjectList(String sql, Class<T> clazz) {
// 第一步加载配置文件
Connection conn = null;
Statement stmt =null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
T t = null;//保存结果的对象
try {
conn = JDBCUtil.getConnection();
stmt = conn.createStatement();
//设置SQL语句的参数
rs = stmt.executeQuery(sql);
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
list.add(t);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.releaseSource(rs, stmt, conn);
}
return list;
}
}
2.PreparedStatement的使用
解决SQL注入的问题
**Statement :**用于执行静态SQL语句并返回其生成的结果的对象。
**PreparedStatement:**表示预编译的SQL语句的对象。
SQL语句已预编译并存储在PreparedStatement对象中。 然后可以使用该对象多次有效地执行此语句。 就是解决SQL注入的根本
通用结果处理的工具类
package org.lanqiao.utils;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class ResultHandler2 {
//查询单个对象
public static <T> T getObject(String sql, Class<T> clazz,Object ... objs) {
// 第一步加载配置文件
Connection conn = null;
PreparedStatement pstmt =null;
ResultSet rs = null;
T t = null;//保存结果的对象
try {
conn= JDBCUtil.getConnection();
pstmt = conn.prepareStatement(sql);
//设置SQL语句的参数
for(int i =0 ; i < objs.length;i++) {
pstmt.setObject(i+1, objs[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.releaseSource(rs, pstmt, conn);
}
return t;
}
//查询多个对象 返回集合
public static <T> List<T> getObjectList(String sql, Class<T> clazz,Object ...objs) {
// 第一步加载配置文件
Connection conn = null;
PreparedStatement pstmt =null;
ResultSet rs = null;
List<T> list = new ArrayList<>();
T t = null;//保存结果的对象
try {
conn = JDBCUtil.getConnection();
pstmt = conn.prepareStatement(sql);
//设置SQL语句的参数
for(int i =0 ; i < objs.length;i++) {
pstmt.setObject(i+1, objs[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
//使用反射机制 来创建对象
while(rs.next()) {
t = clazz.newInstance();
for(int i = 1 ; i <= rsmd.getColumnCount();i++) {
String label = rsmd.getColumnLabel(i);
Field field = clazz.getDeclaredField(label);//使用别名来构建对象
field.setAccessible(true);
field.set(t, rs.getObject(i));
}
list.add(t);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchFieldException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
JDBCUtil.releaseSource(rs, pstmt, conn);
}
return list;
}
}
测试
@Test
public void insertTest() throws SQLException {
Connection conn = JDBCUtil.getConnection();
String sql = "insert into emp(ename,job,hiredate,sal)values(?,?,?,?)";//?是占位符
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置参数
pstmt.setString(1, "张小三");
pstmt.setString(2, "程序员");
pstmt.setDate(3, new Date(new java.util.Date().getTime()));
pstmt.setDouble(4, 6000.00);
//执行sql语句
int res = pstmt.executeUpdate();
System.out.println(res);
}
@Test
public void queryTest() throws SQLException {
Connection conn = JDBCUtil.getConnection();
String sql = "select uid,username,password,realname realName,age,birthday from users where username=? and password=?";//?是占位符
Users user = ResultHandler2.getObject(sql, Users.class, "admin","123456");
System.out.println(user);
}
针对模糊查询的sql的写法
第一种
@Test
public void likeQueryTest() throws SQLException {
Connection conn = JDBCUtil.getConnection();
String sql = "select uid,username,password,realname realName,age,birthday from users where username like concat('%',?,'%')";//?是占位符
List<Users> list = ResultHandler2.getObjectList(sql, Users.class, "s");
for(Users user : list) {
System.out.println(user);
}
}
第二种
@Test
public void likeQueryTest() throws SQLException {
Connection conn = JDBCUtil.getConnection();
String sql = "select uid,username,password,realname realName,age,birthday from users where username like ?";//?是占位符
List<Users> list = ResultHandler2.getObjectList(sql, Users.class, "%s%");
for(Users user : list) {
System.out.println(user);
}
}
可以有效防止SQL注入
小结:
JDBC:实现数据的持久化
JDBC是Java所提供的一套访问数据库的接口(规范)
接口都位于java.sql包下
接口有数据库厂商实现 --数据库的驱动包
JDBC的使用步骤:
- 获取数据库的驱动包
- 将数据库的驱动包导入到项目中(类路径下)
- 配置连接属性
jdbc.properties
url:jdbc:mysql://localhost:3306/databaseName
driver:
com.mysql.jdbc.Driver/oracle.jdbc.dirver.OracleDriver
user:username
password:password
- 加载(注册)驱动
class.forName(驱动的全类名);
- 获取链接:
Connection conn =DriverManager.getconnection(url,userr.password);
- 获取执行对象
Statement/PreparedStatement
Statement stmt = conn.createStatement();
PreparedStatement pstmt = conn.preparedStatement()
Statement /PreparedStatement 区别
Statement :执行静态sql (sql是通过拼串的形式拼接出来的) 存在SQL注入的风险
PreparedStatement :执行预编译sql 可以有效的预防SQL注入
- 执行sql
调用stmt/pstmt的executeUpdatet()/ executeQuery()
- 如果执行的是查询操作 则需要进行结果集的处理
ResultSet表示结果集
next()判断是否有记录
getXXX(index/name)获取相应的记录的值
ResultSetMetaData使用它可以获取结果集对象的一些描述信息
getColumnCount()
getColumnName()
getColumnLable()
getColumnType()
- 释放资源
四、 操作BLOB类型字段
数据库存储中一些特殊数据:
大文本数据 text(mysql) 字符类型 clob(oralce)
大二进制数据 blob 二进制数据
@Test
public void insetBlob() throws SQLException, FileNotFoundException {
//获取链接
Connection conn = JDBCUtil.getConnection();
String sql = "insert into users(username,password,realname,age,birthday,photo) values(?,?,?,?,?,?)";
//获取执行对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置参数
pstmt.setString(1, "jack");
pstmt.setString(2,"123456");
pstmt.setString(3, "小五");
pstmt.setInt(4, 23);
pstmt.setDate(5, new Date(new java.util.Date().getTime()));
InputStream in = new FileInputStream(new File("src/l.jpg"));
pstmt.setBlob(6, in);
//执行sql
pstmt.executeUpdate();
//释放资源
JDBCUtil.releaseSource(null, pstmt, conn);
}
//查询Blob
@Test
public void queryBlob() throws SQLException, IOException {
//获取链接
Connection conn = JDBCUtil.getConnection();
String sql = "select photo from users where uid=?";
//获取执行对象
PreparedStatement pstmt = conn.prepareStatement(sql);
//设置参数
pstmt.setInt(1, 5);
//执行sql
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
Blob photo = rs.getBlob("photo");
InputStream in = photo.getBinaryStream();
OutputStream out = new FileOutputStream(new File("src/123.jpg"));
byte[] buf = new byte[1024];
int len =0;
while((len = in.read(buf))!= -1) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
//释放资源
JDBCUtil.releaseSource(rs, pstmt, conn);
}
虽然数据库提供了对于大数据的存储和访问技术 但是在实际开发中并不会这样使用。这样使用除非是有特殊要求。
开发中一般的做法:
不会在数据库中直接保存文件数据 而只是在数据库中保存文件的存储路径就可以了。
Text 文本数据(练习)
五、CallableStatement (在程序中来调用存储过程和函数)
存储过程和函数的区别:返回值
public interface CallableStatement extends PreparedStatement
用于执行SQL存储过程的界面。 JDBC API提供了存储过程SQL转义语法,允许以标准方式为所有RDBMS调用存储过程。
此转义语法包含一个结果参数和不包含结果参数的表单。 如果使用,结果参数必须注册为OUT参数。 其他参数可用于输入,输出或两者。
参数按顺序依次引用,第一个参数为1。
{?= call <procedure-name>[(<arg1>,<arg2>, ...)]}
{call <procedure-name>[(<arg1>,<arg2>, ...)]}
//调用存储过程
@Test
public void proTest() throws SQLException {
//获取链接
Connection conn = JDBCUtil.getConnection();
//获取执行对象
String sql = "{call pro1(?,?)}";
CallableStatement cs = conn.prepareCall(sql);
//设置参数
cs.setInt(1, 20);
//注册输出参数
cs.registerOutParameter(2,JDBCType.INTEGER);
//执行存储过程
cs.execute();
int num = cs.getInt(2);
System.out.println(num);
JDBCUtil.releaseSource(null, cs, conn);
}
//调用函数
@Test
public void funTest() throws SQLException {
//获取链接
Connection conn = JDBCUtil.getConnection();
//获取执行对象 调用函数 第一个问好 表示返回值 第二个问号 表示输入参数
String sql = "{?=call fun(?)}";
CallableStatement cs = conn.prepareCall(sql);
//设置参数
cs.setInt(2, 10);
//注册输出参数
cs.registerOutParameter(1,JDBCType.INTEGER);
//执行存储过程
cs.execute();
int num = cs.getInt(1);//获取返回值或者输出参数的值
System.out.println(num);
JDBCUtil.releaseSource(null, cs, conn);
}
存储过程和函数 相较于普通sql 那么他们的优劣
从执行过程:
- 函数和存储过程 是存储在数据库服务端的
普通sql是在程序中 执行的时候 才会发送给数据库服务端 - 函数和存储过程在服务端 没有发送的过程 所以效率高
普通sql需要将sql语句发送到服务端 所以效率低 - 将简单sql编写在程序中
复杂的sql 则可以通过存储过程或函数来实现。可以简化开发人员对sql 的复杂操作
六、 批量插入(了解)
1 批量执行SQL语句
当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率
JDBC的批量处理语句包括下面三个方法:
- addBatch(String):添加需要批量处理的SQL语句或是参数;
- executeBatch():执行批量处理语句;
- clearBatch():清空缓存的数据
通常我们会遇到两种批量执行SQL语句的情况: - 多条SQL语句的批量处理;
- 一个SQL语句的批量传参;
2 高效的批量插入
举例:向数据表中插入20000条数据
数据库中提供一个goods表。创建如下:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
2.1 实现层次一:使用Statement
Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
for(int i = 1;i <= 20000;i++){
String sql = "insert into goods(name) values('name_' + "+ i +")";
st.executeUpdate(sql);
}
2.2 实现层次二:使用PreparedStatement
long start = System.currentTimeMillis();
Connection conn = JDBCUtils.getConnection();
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 20000;i++){
ps.setString(1, "name_" + i);
ps.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//82340
JDBCUtils.closeResource(conn, ps);
2.3 实现层次三
/*
* 修改1: 使用 addBatch() / executeBatch() / clearBatch()
* 修改2:mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
* ?rewriteBatchedStatements=true 写在配置文件的url后面
* 修改3:使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
*
*/
@Test
public void testInsert1() throws Exception{
long start = System.currentTimeMillis();
Connection conn = JDBCUtils.getConnection();
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 1000000;i++){
ps.setString(1, "name_" + i);
//1.“攒”sql
ps.addBatch();
if(i % 500 == 0){
//2.执行
ps.executeBatch();
//3.清空
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//20000条:625 //1000000条:14733
JDBCUtils.closeResource(conn, ps);
}
2.4 实现层次四
/*
* 层次四:在层次三的基础上操作
* 使用Connection 的 setAutoCommit(false) / commit()
*/
@Test
public void testInsert2() throws Exception{
long start = System.currentTimeMillis();
Connection conn = JDBCUtils.getConnection();
//1.设置为不自动提交数据
conn.setAutoCommit(false);
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 1000000;i++){
ps.setString(1, "name_" + i);
//1.“攒”sql
ps.addBatch();
if(i % 500 == 0){
//2.执行
ps.executeBatch();
//3.清空
ps.clearBatch();
}
}
//2.提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));//1000000条:4978
JDBCUtils.closeResource(conn, ps);
}