什么是JDBC
JDBC(Java Database Connectivity,Java数据库连接)是Oracle-Sun公司设计的一套专门用于java程序操作数据库的接口。使用java程序发送sql语句到数据库服务器端执行,这叫用到了JDBC技术!
组成JDBC的2个包:java.sql(核心基础包) 和 javax.sql(扩展包) 已经包含在JaveSE中,所以不用导入,开发时只需要导入JDBC的实现类即数据库驱动包。
JDBC的核心API(java.sql.*和javax.sql.*)
|- Driver接口: 数据库驱动程序的接口,所有具体数据库厂商的驱动程序需要实现此接口。
Connection connect(String url, Properties info) 用于获取数据库连接
|- DriverManager类:驱动管理器类,用于管理所有注册的驱动程序
static void registerDriver(driver) 注册驱动类对象
static Connection getConnection(url,user,password) 获取连接对象
|- Connection接口:表示java程序和数据库的连接对象。
Statement createStatement() 创建静态sql语句对象
PreparedStatement prepareStatement(String sql) 创建预编译的sql语句对象
CallableStatement prepareCall(String sql) 创建存储过程的sql语句对象
void setAutoCommit(boolean autoCommit) 设置事务是否自动提交。
void commit() 在此连接上提交事务
void rollback() 在此连接上回滚事务
|- Statement接口:用于执行静态sql语句
int executeUpdate(String sql) 执行静态sql更新语句(create/alter/drop/insert/update/delete)
ResultSet executeQuery(String sql) 执行静态sql查询语句(select)
boolean execute(String sql) 执行任意sql语句
void addBatch(String sql) 把指定sql语句添加到批处理中。
int[] executeBatch() 执行一批sql语句。若全部执行成功,则返回更新计数组成的数组。
void clearBatch() 清空此 Statement 对象的当前 SQL 命令列表。
|- PreparedStatement接口:用于执行预编译的sql语句(是Statement的子接口)
int executeUpdate() 执行预编译的sql更新语句(DDL,DML)
ResultSet executeQuery() 执行预编译的sql查询语句(DQL)
|- CallableStatement接口:用于执行sql存储过程的接口(call xxx)(是PreparedStatement的子接口)
ResultSet executeQuery() 执行存储过程的sql语句
对应类型 getXXX(int OUT参数位置) 获取输出参数的值
|- ResultSet接口:结果集对象。存储数据库查询结果,用该对象进行数据遍历。
boolean next() 把光标移动到下一行。如果下一行有数据,返回true,否则返回false
boolean next() 移动到下一行
boolean previous() 移动到前一行
boolean absolute(int row) 移动到指定行
void beforeFirst() 移动resultSet的最前面
void afterLast() 移动到resultSet的最后面
对应类型 getXXX(int index) 根据列索引获取列字段值(较高效但不建议)
对应类型 getXXX(String columnName) 根据列字段名(不区分大小写)获取列字段值
Object getObject(int index) 根据列索引获取列字段值(较高效但不建议)
Object getObject(String columnName) 根据列字段名(不区分大小写)获取列字段值
常用数据类型转换表
SQL类型 | Jdbc对应方法 | 返回类型 |
BIT(1) bit(n) | getBoolean getBytes() | Boolean byte[] |
TINYINT | getByte() | Byte |
SMALLINT | getShort() | Short |
Int | getInt() | Int |
BIGINT | getLong() | Long |
CHAR,VARCHAR,LONGVARCHAR | getString() | String |
Text(clob) Blob | getClob getBlob() | Clob Blob |
DATE | getDate() | java.sql.Date |
TIME | getTime() | java.sql.Time |
TIMESTAMP | getTimestamp() | java.sql.Timestamp |
PreparedStatement与Statement区别
1)语法不同: Statement只能执行静态的sql(sql可拼接),PreparedStatement还能执行预编译的sql。
2)效率不同: PreparedStatement可以使用sql缓存区,效率比Statment高。(MySQL没有sql缓存功能)
3)安全性不同: PreparedStatement可以有效防止sql注入,而Statment不能。
sql注入行为:例如登陆用户名采用 rose' OR 1=1 -- 导致sql语句变为:
SELECT * FROM user_list WHERE NAME='rose' OR 1=1 -- ' AND PASSWORD='123456';
推荐使用PreparedStatement
注册驱动程序的三种方法
1)直接使用驱动程序连接
//1.创建驱动程序的实现类对象
Driver driver = new com.mysql.jdbc.Driver(); //新版本
//Driver driver = new org.gjt.mm.mysql.Driver(); 旧版本
//2.连接数据库
Properties prop = new Properties();
prop.setProperty("user", user);//用户名
prop.setProperty("password", password);
Connection conn = driver.connect(url, prop);
2)使用驱动管理类DriverManager
//1.创建驱动程序的实现类对象
Driver driver = new com.mysql.jdbc.Driver(); //mysql
//Driver driver = new com.oracle.jdbc.Driver(); //oracle
//2.注册驱动程序
DriverManager.registerDriver(driver);//有问题:重复注册了两次驱动程序
//3.连接数据库
Connection conn = DriverManager.getConnection(url, user, password);
3)通过反射获取类的对象注册驱动程序(推荐)
//1.注册驱动程序
Class.forName("com.mysql.jdbc.Driver");//只注册一次(执行Driver类中的静态代码块)
//2.使用驱动管理类获取连接
Connection conn = DriverManager.getConnection(url, user, password);
常见数据库的连接参数
JDBC的URL=协议名+子协议名+数据源名。数据源名也可能包含用户与口令等信息;这些信息也可单独提供。
URL格式:jdbc:数据库协议://主机地址:端口/数据库名
Oracle:port默认1521
驱动:oracle.jdbc.driver.OracleDriver
URL:jdbc:oracle:thin:@machine_name:port:dbname
MySQL:port默认3306
驱动:com.mysql.jdbc.Driver
URL:jdbc:mysql://machine_name:port/dbname?characterEncoding=UTF-8
MySQL8:port默认3306
驱动:com.mysql.cj.jdbc.Driver
URL:jdbc:mysql://machine_name:port/dbname?characterEncoding=UTF-8&serverTimezone=GMT%2B8
SQL Server:port默认1433
驱动:com.microsoft.jdbc.sqlserver.SQLServerDriver
URL:jdbc:microsoft:sqlserver://<machine_name><:port>;DatabaseName=<dbname>
DB2:port默认5000
驱动:com.ibm.db2.jdbc.app.DB2Driver
URL:jdbc:db2://<machine_name><:port>/dbname
注:如遇错误Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value ...
追加参数&serverTimezone=GMT%2B8
Statement | PreparedStatment | CallableStement | ||
1)导包 | MySQL:mysql-connector-java-5.1.7-bin.jar / Oracle:ojdbc5.jar | |||
2)注册驱动程序 | Class.forName(驱动类名称); | |||
3)获取连接对象 | Connection conn = DriverManger.getConnection(url, user, password); | |||
4)准备sql语句 | 静态sql | 静态sql、动态sql | 静态sql、动态qsl(存储过程sql) | |
5)创建Statement对象 | conn.createStatement() | conn.preparedStaement(sql); | conn.preparedCall(sql) | |
6)如果是预编译的sql,给参数赋值 | 参数赋值 | |||
7)执行sql语句 | DDL+DML:返回int | stmt.executeUpdate(sql); | ps.executeUpdate(); | |
DQL:返回ResultSet | stmt.executeQuery(sql); | ps.executeQuery(); | cs.executeQuery(); | |
8)处理结果(int 或 ResultSet) | 判断或取值 | |||
9)关闭资源 | rs.close(); / stmt.close(); / conn.close(); |
代码实例
jdbc.properties文件: |
#mysql (Abbreviated form of URL for localhost: jdbc:mysql:///databaseName) url=jdbc:mysql://localhost:3306/databasename?useUnicode=true&characterEncoding=UTF-8 user=root password=root driverClass=com.mysql.jdbc.Driver #oracle (orcl is an SID Name) #url=jdbc:oracle:thin:@localhost:1521:orcl #user=eric #password=123456 #driverClass=oracle.jdbc.driver.OracleDriver #SqlServer: jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=sid |
package com.twx.util; 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.Properties; /** * jdbc工具类 */ public class JdbcUtil { private static String url = null; private static String user = null; private static String password = null; private static String driverClass = null; static { try { /** * 使用类路径方式读取properties配置文件,参见路径问题 */ // 创建properties对象 Properties prop = new Properties(); // 构造输入流,使用类路径方式加载文件 InputStream in = JdbcUtil.class.getResourceAsStream("/jdbc.properties"); // 加载输入流 prop.load(in); // 读取配置文件的内容 url = prop.getProperty("url"); user = prop.getProperty("user"); password = prop.getProperty("password"); driverClass = prop.getProperty("driverClass"); /** * 注册驱动程序 * JDBC规范要求Driver类在使用前必须向DriverManger注册自己。注册过程在Driver类的静态类已经实现。也就是说只要类被加载,就完成了向驱动管理器的注册。 */ Class.forName(driverClass); } catch (Exception e) { e.printStackTrace(); } } /** * 获取连接 */ public static Connection getConnection() { try { Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * 释放资源 */ public static void closeConnection(ResultSet rs, Statement stmt, Connection conn) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } if (stmt != null) { try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } } |
/** * Statement用法示例(创建对象、准备、执行、处理结果) */ /** * 通过jdbc执行DML语句(insert/update/delete) * @author APPle * */ public void testPreparedStatement() { Connection conn = null; Statement stmt = null; ResultSet rs = null; String sql = null; try { conn = JdbcUtil.getConnection();//连接 //增加 stmt = conn.createStatement();//创建Statment对象 sql = "INSERT INTO student(NAME,gender) VALUES('张三','男')"; //准备sql int count = stmt.executeUpdate(sql); //执行sql,返回结果 System.out.println("影响了"+count+"行");//处理结果 //删除 stmt = conn.createStatement();//创建Statment对象 int id=2; sql = "DELETE FROM student WHERE id="+id+""; //准备sql int count = stmt.executeUpdate(sql); //执行sql,返回结果 System.out.println("影响了"+count+"行");//处理结果 //修改 stmt = conn.createStatement();//创建Statment对象 String name = "陈六"; int id=2; sql = "UPDATE student SET NAME='"+name+"' WHERE id="+id+""; //准备sql int count = stmt.executeUpdate(sql); //执行sql,返回结果 System.out.println("影响了"+count+"行");//处理结果 //查询 stmt = conn.createStatement();//创建Statement对象 sql = "SELECT * FROM student";//准备sql rs = stmt.executeQuery(sql); //执行sql,返回结果集 //迭代结果集 while(rs.next()){//移动光标到下一行 //使用列名称获取值,不推荐使用列索引 int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t"+name+"\t"+gender+"\t"); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ //关闭资源 JdbcUtil.closeConnection(rs, stmt, conn); // 缺参填null } } /** * PreparedStatement用法示例(准备、预编译、赋值、执行、处理结果) */ public void testPreparedStatement() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; String sql = null; try { conn = JdbcUtil.getConnection();//连接 //增加 sql = "insert into student(name,age) values(?,?)"; //准备 ps = conn.prepareStatement(sql); 预编译 ps.setString(1, "李四");//赋值 ps.setInt(2, 18); //赋值 int count = ps.executeUpdate();//执行 System.out.println("影响了"+count+"行");//处理结果 //删除 sql = "delete from student where id=?"; //准备 ps = conn.prepareStatement(sql); 预编译 ps.setInt(1, 9); //赋值 int count = ps.executeUpdate();//执行 System.out.println("影响了"+count+"行");//处理结果 //修改 sql = "update student set name=? where id=?"; //准备 ps = conn.prepareStatement(sql); 预编译 ps.setString(1, "王五");//赋值(字符串) ps.setInt(2, 9); //赋值(整型) int count = ps.executeUpdate();//执行 System.out.println("影响了"+count+"行");//处理结果 //查询 sql = "select * from student where id=?"; //准备 ps = conn.prepareStatement(sql); //预编译 ps.setInt(1,2); //赋值 rs = ps.executeQuery();//执行 while(rs.next()){//处理结果 int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(name +"\t"+ gender +"\t"); } } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally { JdbcUtil.closeConnection(rs, stmt, conn); // 缺参填null } } /** * CallableStatement用法示例 */ public void testCallableStatement(){ Connection conn = null; CallableStatement cs = null; ResultSet rs = null; try { conn = JdbcUtil.getConnection(); //执行带有输入参数的存储过程 String sql = "CALL pro_findById(?)"; //准备 cs = conn.prepareCall(sql); //预编译 cs.setInt(1, 6); //赋值 rs = cs.executeQuery();//执行 while(rs.next()){//输出 int id = rs.getInt("id"); String name = rs.getString("name"); String gender = rs.getString("gender"); System.out.println(id+"\t "+name+"\t "+gender +"\t"); } //执行带有输出参数的存储过程 String sql = "CALL pro_findById2(?,?)";//准备。第一参为输入参数,第二参为输出(OUT)参数 cs = conn.prepareCall(sql); //预编译 cs.setInt(1, 6);//设置输入参数 cs.registerOutParameter(2, java.sql.Types.VARCHAR); //注册输出参数(OUT参数位置。OUT参数的数据库中类型) cs.executeQuery(); //执行。结果不是返回到结果集中,而是返回到输出参数中 /** * 得到输出参数的值(结果不是返回到结果集中,而是返回到输出参数中) * 索引值: 预编译sql中的输出参数的位置 */ String result = cs.getString(2); //获取存储过程中的输出参数值(OUT参数位置) System.out.println(result); //输出 } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally { JdbcUtil.closeConnection(rs, stmt, conn); } } |
JDBC进行批处理
mysql数据库既不支持PreparedStatement优化,也不支持批处理优化
oracle数据库既支持PreparedStatement优化,也支持批处理优化
Statement批处理:
void addBatch(String sql) 添加sql到缓存区(暂时不发送)
int[] executeBatch() 执行批处理命令,发送所有缓存区的sql
void clearBatch() 清空sql缓存区
public static void testByStaementBatch(){ Connection conn = null; Statement stmt = null; try{ conn = JdbcUtil.getConnection(); stmt = conn.createStatement(); for(int i=1;i<=2000;i++){ //把sql添加到缓存区 stmt.addBatch("INSERT INTO student VALUES("+i+",'张三',20,'男')"); //每20条发送sql if(i%20==0){ //执行批处理命令 stmt.executeBatch(); //清空缓存区 stmt.clearBatch(); } } }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(stmt, conn); } } |
PreparedStatement批处理:
void addBatch() 添加参数到缓存区
int[] executeBatch() 执行批处理命令,发送所有缓存区的sql
void clearBatch() 清空sql缓存区
public static void testByPreparedStaementBatch(){ Connection conn = null; PreparedStatement stmt = null; try{ conn = JdbcUtil.getConnection(); stmt = conn.prepareStatement("INSERT INTO student VALUES(?,?,?,?)"); for(int i=1;i<=2000;i++){ //参数赋值 stmt.setInt(1, i); stmt.setString(2, "张三"); stmt.setInt(3, 20); stmt.setString(4, "男"); //把参数添加到缓存区 stmt.addBatch(); //每20次发送一次参数 if(i%20==0){ //执行批处理命令 stmt.executeBatch(); //清空缓存区的参数 stmt.clearBatch(); } } }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(stmt, conn); } } |
JDBC获取自增长值
1)使用Connection的重载的方法创建一个能获取自动生成的键的PreparedStatement对象
PreparedStatement ps = comm.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
注:Statement.RETURN_GENERATED_KEYS:可以返回自动增长值;Statement.NO_GENERATED_KEYS:不能返回自动增长值。
2)获取自增长的值
ResultSet rs = ps.getGeneratedKeys();
Integer id = null;
if(rs.next()){
id = rs.getInt(1);//得到第一行第一列的值
}
/** * 使用jdbc获取自增长的值 */ public void getAutoIncrementValue() { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ conn = JdbcUtil.getConnection(); /** * 同时插入一部门和该部门一员工的数据 */ String deptSql = "insert into dept(name) values(?)"; String empSql = "insert into employee(name,deptId) values(?,?)"; /** * 如果获取自增长的生成的值? * 1)设置是否可以返回自增长的值 * RETURN_GENERATED_KEYS: 可以返回自增长生成值 * NO_GENERATED_KEYS:不能返回自增长的值 */ stmt = conn.prepareStatement(deptSql,Statement.RETURN_GENERATED_KEYS); //参数赋值 stmt.setString(1, "软件维护部"); //执行插入部门的sql stmt.executeUpdate(); /** * 2)获取自增长的值 */ rs = stmt.getGeneratedKeys();//注意:该ResultSet只包含了自增长的字段 int deptId = 0; if(rs.next()){ deptId = rs.getInt(1); } /** * 2)将自增长的值插入员工的deptId */ stmt = conn.prepareStatement(empSql); stmt.setString(1, "李四"); stmt.setInt(2, deptId); stmt.executeUpdate(); System.out.println("添加成功"); }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } |
JDBC存取大数据和文件
在实际开发中,程序需要把大文本或二进制数据保存到数据库。
大数据也称之为LOB(Large Objects),LOB又分为:
clob和blob
clob用于存储大文本。在MySQL中不用clob,用text。
blob用于存储二进制数据,例如图像、声音、二进制文等。
字符串 | 大文本数据 | 字节 | 大字节文件 | 枚举/集合 | |
mysql | char 指定大小 varchar(约64K) | tinytext text mediumtext longtext | bit | tinyblob(255byte) blob(约64K) MEDIUMBLOB(约16M) longblob(约4GB) | enum / set |
oracle | char 指定大小 varchar2(约64K) | clob | bit | blob |
尺寸限制:
类型 | 最大尺寸 | 存储需求 |
char(M) | M字节 | M字节 |
varchar(M) | M字节 | L+1字节 |
tinyblob / tinytext | 2^8-1字节 | L+1字节 |
blob / text | 2^16-1字节 | L+2字节 |
mediumblob / mediumtext | 2^24-1字节 | L+3字节 |
longblob / longtext | 2^32-1字节 | L+4字节 |
enum("value1", "value2", …) | 2^16-1个成员 | 1或2字节 |
set("value1", "value2", …) | 64个成员 | 1、2、3、4或8字节 |
使用JDBC处理大文本
对于MySQL中的Text类型,可调用如下方法设置:
preparedStatement.setCharacterStream(index, reader, length);//注意length长度须设置,并且设置为int型
对于MySQL中的Text类型,可调用如下方法获取:
String string = resultSet.getString(i);
Reader reader = resultSet. getCharacterStream(i);
Reader reader = resultSet.getClob(i).getCharacterStream();
对于MySQL中的BLOB类型,可调用如下方法设置:
preparedStatement. setBinaryStream(i, inputStream, length);
对MySQL中的BLOB类型,可调用如下方法获取:
InputStream in = resultSet.getBinaryStream(i);
InputStream in = resultSet.getBlob(i).getBinaryStream();
Oracle中大数据处理
Oracle定义了一个BLOB字段用于保存二进制数据,但这个字段并不能存放真正的二进制数据,只能向这个字段存一个指针,然后把数据放到指针所指向的Oracle的LOB段中,LOB段是在数据库内部表的一部分。
因而在操作Oracle的Blob之前,必须获得指针(定位器)才能进行Blob数据的读取和写入。
如何获得表中的Blob指针呢? 可以先使用insert语句向表中插入一个空的blob(调用oracle的函数empty_blob()),这将创建一个blob的指针,然后再把这个empty的blob的指针查询出来,这样就可得到BLOB对象,从而读写blob数据了。
Oracle中LOB类型的处理
1、插入空blob
insert into test(id,image) values(?,empty_blob());
2、获得blob的cursor
select image from test where id= ? for update;
Blob b = rs.getBlob(“image”);
注意: 须加for update,锁定该行,直至该行被修改完毕,保证不产生并发冲突。
3、利用 io,和获取到的cursor往数据库读写数据
注意:以上操作需开启事务。
JDBC存取字符文件实例
/** * 保存大文本数据到数据库(clob) */ public void saveClob(){ Connection conn = null; PreparedStatement stmt = null; try{ conn = JdbcUtil.getConnection(); String sql = "insert into news(title,content) values(?,?)"; stmt = conn.prepareStatement(sql); stmt.setString(1, "昨天是父亲节,全国各地都在纷纷庆祝!!!"); //创建输入字符流加载本地字符文件 FileReader reader = new FileReader(new File("e:/Demo1.java")); //设置text字段,把输入字符流传入 stmt.setClob(2, reader); int count = stmt.executeUpdate(); System.out.println("影响了"+count+"行"); }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, null); } } /** * 从数据库中读取大文本数据(clob) */ public void readClob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ conn = JdbcUtil.getConnection(); String sql = "select * from news where id=?"; stmt = conn.prepareStatement(sql); stmt.setInt(1, 1); rs = stmt.executeQuery(); if(rs.next()){ //通过text字段获取输入字符流写出到文件中 Clob clob = rs.getClob("content"); Reader reader = clob.getCharacterStream(); Writer writer = new FileWriter(new File("e:/news.txt")); char[] buf = new char[1024]; int len = 0; while( (len=reader.read(buf))!=-1){ writer.write(buf, 0, len); } writer.close(); reader.close(); } }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } |
JDBC存取字节文件实例
/** * 保存文件到数据库(blob) */ public void saveClob(){ Connection conn = null; PreparedStatement stmt = null; try{ conn = JdbcUtil.getConnection(); String sql = "update news set attachments=? where id=?"; stmt = conn.prepareStatement(sql); //创建输入字节流加载本地文件 InputStream in = new FileInputStream(new File("e:/news.wmv")); //设置字节流参数,把输入字节流传入,注意不要超过数据库接收数据包大小限制和字段的长度限制 stmt.setBlob(1, in); //注意不要超过数据库接收数据包大小限制和字段的长度限制 stmt.setInt(2, 1); int count = stmt.executeUpdate(); System.out.println("影响了"+count+"行"); }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, null); } } /** * 注意: mysql数据库默认情况下,只能存储不超过1m的文件,这是由于max_allowed_packet变量的限制 * 可以修改 %mysql%/my.ini 文件, 修改或添加max_allowed_packet变量,然后重启mysql即可! */ /** * 从数据库中读取文件(blob) */ public void readClob(){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ conn = JdbcUtil.getConnection(); String sql = "select * from news where id=?"; stmt = conn.prepareStatement(sql); stmt.setInt(1, 1); rs = stmt.executeQuery(); if(rs.next()){ //通过blob字段获取输入字节流写出到文件中 Blob blob = rs.getBlob("attachments"); InputStream in = blob.getBinaryStream(); OutputStream out = new FileOutputStream("e:/news.wmv"); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ out.write(buf, 0, len); } out.close(); in.close(); } }catch(Exception e){ e.printStackTrace(); }finally{ JdbcUtil.close(conn, stmt, rs); } } |
-
- 事务处理
MySQL事务操作命令和JDBC事务操作API
MySQL | JDBC | |
设置是否自动提交事务(关闭自动提交以开启事务/开启自动提交以关闭事务) | set autocommit =0 / 1 | conn.setAutoCommit(false/true); |
开启事务 | start transaction; | conn.setAutoCommit(false); |
提交事务,一旦提交事务不能回滚 | commit; | conn.commit(); |
回滚事务。回滚到事务的起始点 | rollback; | conn.rollback();//回滚后必须要提交conn.commit(); |
设置回滚点 | savepoint sp; | Savepoint sp = conn.setSavepoint(); |
回滚至某点 | rollback to sp; | conn.rollback(sp);//回滚后必须要提交conn.commit(); |
示例:
/** * 模拟银行转账:从eric的账户转2000元到rose的账户上 */ public void testTransfer(){ //从eric账户上扣除2000元 String delSql = "UPDATE account SET BALANCE=BALANCE-2000 WHERE NAME='eric'"; //向rose账户打入2000元 String addSql = "UPDATE account SET BALANCE=BALANCE+2000 WHERE NAME='rose'"; Connection conn = null; PreparedStatement stmt = null; try{ conn = JdbcUtil.getConnection();//获取连接 conn.setAutoCommit(false); //开启事务,向数据库发送命令set autocommit=0; stmt = conn.prepareStatement(delSql); //预编译delSQL stmt.executeUpdate();//执行delSQL int i = 100/0; //发生异常 stmt = conn.prepareStatement(addSql); //预编译addSql stmt.executeUpdate();//执行addSql conn.commit();//提交事务,向数据库发送命令commit; }catch(Exception e){ e.printStackTrace(); try { conn.rollback(); //回滚事务,向数据库发送命令rollback; } catch (SQLException e1) { e1.printStackTrace(); } }finally{ JdbcUtil.close(conn, stmt, null); } } |
/** * 自定义的连接池 * @author APPle * */ public class MyPool { //连接参数 private static String url = "jdbc:mysql://localhost:3306/day19"; private static String user = "root"; private static String password = "root"; private static String driverClass = "com.mysql.jdbc.Driver"; //使用集合存储多个连接对象 LinkedList<Connection> pool = new LinkedList<Connection>(); /** * 连接池的初始连接数 */ private int initSize = 5; /** * 连接池的最大连接数 */ private int maxSize = 8; /** * 连接池中的当前连接数,用于记录连接中有几个连接对象? */ private int currentSize = 0; public MyPool(){ /** * 创建若干个连接对象,放入到集合中 */ for(int i=1;i<=initSize;i++){ pool.addLast(createConnection()); currentSize++; } } /** * 注册驱动 */ static{ try { Class.forName(driverClass); } catch (ClassNotFoundException e) { throw new RuntimeException("注册数据库驱动失败", e); } } /** * 创建连接方法 */ private Connection createConnection(){ try { return DriverManager.getConnection(url, user, password); } catch (SQLException e) { throw new RuntimeException("连接数据库失败", e); } } /** * 对外提供获取连接的方法 */ public Connection getConnection(){ // 1)判断连接池中是否还有连接对象,若有,直接返回一个 if(pool.size()>0){ return pool.removeFirst(); } // 2)如果初始连接数不足的情况下,需要获取新的连接对象。但不能超过最大连接数 if(currentSize<maxSize){ Connection conn = createConnection(); currentSize++; return conn; } // 3)超过最大连接数时,需要处理(抛出异常或让用户等待) throw new RuntimeException("超过了最大连接数,请稍后再来"); } /** * 对外提供释放连接的方法(放回到连接池) */ public void releaseConnection(Connection conn){ pool.addLast(conn); } } |
解决Connection.close方法释放连接问题
1)静态代理:
1.1 创建Connection接口的实现类(代理类)
1.2 在代理类中,重写close方法
注意: 使用静态代理,需要把原有对象的方法全部实现。
2)动态代理:
使用jdk自带的动态代理类
Proxy类:
static Object newProxyInstance( 用于创建代理类对象方法。
ClassLoader loader, 参数一:类加载器,可以使用当前项目的任意类获取类加载器
Class<?>[] interfaces, 参数二:代理类需要实现的接口
InvocationHandler h) 参数三: 指定代理类代理了之后要如何处理
InvocationHandler接口: 代理类的处理程序接口
Object invoke( 调用代理类对象的方法
Object proxy, 代理类对象实例
Method method, 当前正在调用哪个方法
Object[] args 当前调用方法时传入的参数
)
如果以上自定义连接要拿到实际应用中使用,还需要不断优化。
1)比如线程并发问题
2)达到最大连接数应该先等待,再抛出异常
3)当连接空闲超过一定时间,连接池要回收连接
....
所以这时就使用别人写的连接池工具!!!
DBCP ( Datatabse Connection Pool)是Apache组织的开源产品,是Tomcat的服务器的连接池默认实现。
编程步骤
导包
mysql-connector-java-5.1.7-bin.jar Mysql的JDBC驱动包(按需选择)
ojdbc5.jar Oracle的JDBC驱动包(按需选择)
commons-dbcp-1.4.jar DBCP核心包
commons-pool-1.5.6.jar DBCP辅助包
maven导包:
<dependency> <groupId>commons-dbcp</groupId> <artifactId>commons-dbcp</artifactId> <version>1.4</version> </dependency> |
写代码
方法一:代码中设置参数的方式
@Test public void test1(){ try { //1.创建连接池对象 BasicDataSource ds = new BasicDataSource(); //2.设置连接参数 ds.setUrl("jdbc:mysql://localhost:3306/day19"); ds.setUsername("root"); ds.setPassword("root"); ds.setDriverClassName("com.mysql.jdbc.Driver"); //3.设置连接池参数 ds.setInitialSize(5);//初始连接数 ds.setMaxActive(8);//最大连接数 ds.setMaxWait(3000);//超过最大连接数时,最大等待时间 ds.setMaxIdle(3000);//最大空闲时间 //4.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn.hashCode()); if(i==5){ //释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池) conn.close(); } } } catch (SQLException e) { e.printStackTrace(); } } |
方法二:使用加载配置文件的方式
1)配置文件(dbcp.properties)
url=jdbc:mysql://localhost:3306/day19 username=root password=root driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=8 maxWait=3000 maxIdle=3000 |
2)程序
@Test public void test2(){ try { Properties prop = new Properties(); prop.load(Demo.class.getResourceAsStream("/dbcp.properties")); //1.创建连接池对象 BasicDataSource ds = (BasicDataSource)BasicDataSourceFactory.createDataSource(prop); //2.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn.hashCode()); if(i==5){ //释放连接(不是真正的关闭连接对象,而是把连接对象放回连接池) conn.close(); } } }catch (Exception e) { e.printStackTrace(); } } |
C3P0是Hibernate内置默认的连接池工具。
编程步骤
导包
mysql-connector-java-5.1.7-bin.jar Mysql的JDBC驱动包(按需选择)
ojdbc5.jar Oracle的JDBC驱动包(按需选择)
c3p0-0.9.1.2.jar C3P0核心包
写代码
/** * 使用C3p0连接池 * 注意: DataSource接口是Sun公司设计的用于规范连接池实现的接口。 */ public class Demo { /** * 方式二(推荐):使用配置文件方式读取参数信息 * 注意: c3p0会默认查询类路径的c3p0-config.xml文件,文件名不能错!! */ @Test public void test1(){ try { //1.创建连接池对象 ComboPooledDataSource ds = new ComboPooledDataSource(); //2.设置连接参数 ds.setDriverClass("com.mysql.jdbc.Driver"); ds.setJdbcUrl("jdbc:mysql://localhost:3306/day19"); ds.setUser("root"); ds.setPassword("root"); //3.设置连接池参数 ds.setInitialPoolSize(5);// 初始化连接数 ds.setMaxPoolSize(8);//最大连接数 ds.setCheckoutTimeout(3000);//最大等待时间 //4.获取连接对象 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn); if(i==5){ /** * 把连接对象放回连接池 */ conn.close(); } } } catch (Exception e) { e.printStackTrace(); } } /** * 方式二:使用配置文件方式读取参数信息 * 注意: c3p0会默认查询类路径的c3p0-config.xml文件,文件名不能错!! */ @Test public void test2(){ try { //1.创建连接池对象(方式一: 使用默认配置(default-config)) //ComboPooledDataSource ds = new ComboPooledDataSource(); //2.创建连接池对象(方式二: 使用命名配置(named-config:mysql_day18)) ComboPooledDataSource ds = new ComboPooledDataSource("mysql_day18"); //2.获取连接 for(int i=1;i<=9;i++){ Connection conn = ds.getConnection(); System.out.println(conn); if(i==5){ /** * 把连接对象放回连接池 */ conn.close(); } } } catch (SQLException e) { e.printStackTrace(); } } } |
Druid是目前最好的数据库连接池,它包括三个部分:
基于Filter-Chain模式的插件体系。
DruidDataSource 高效可管理的数据库连接池。
SQLParser
Druid的优势
1、可以监控数据库访问性能。Druid内置了一个强大的StatFilter插件,能够详细统计SQL的执行性能,这有助于线上分析数据库访问性能。
2、数据库密码加密。直接把数据库密码写在配置文件中不安全。DruidDruiver和DruidDataSource都支持PasswordCallback。
3、SQL执行日志.Druid提供了不同的LogFilter,能够支持Common-Logging、Log4j和JdkLog,你可以按需选择相应的LogFilter,监控你应用的数据库访问情况。
4、扩展JDBC,如果你要对JDBC层有编程的需求,可以通过Druid提供的Filter机制,很方便编写JDBC层的扩展插件。
druid配置数据库连接使用密文密码
dataSource配置
<!-- 基于Druid数据库链接池的数据源配置 -->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性driverClassName、 url、user、password -->
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<!-- 配置初始化大小、最小、最大 -->
<!-- 通常来说,只需要修改initialSize、minIdle、maxActive -->
<property name="initialSize" value="2" />
<property name="minIdle" value="2" />
<property name="maxActive" value="30" />
<property name="testWhileIdle" value="false" />
<!-- 配置获取连接等待超时的时间 -->
<property name="maxWait" value="5000" />
<!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
<property name="minEvictableIdleTimeMillis" value="30000" />
<!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
<property name="timeBetweenEvictionRunsMillis" value="60000" />
<!-- 解密密码必须要配置的项 -->
<property name="filters" value="config" />
<property name="connectionProperties" value="config.decrypt=true" />
</bean>
jdbc.properties配置
## JDBC set
jdbc.url=jdbc\:mysql\://localhost\:3306/edu_demo?useUnicode\=true&characterEncoding\=utf-8
jdbc.username=root
jdbc.password=Obsbr4gd1oVyYr+k4KQdUMNYgKMWdDibsNJTabnph+yPmxjc6tUrT1GNsPDqa9ZvTF9QvaRD86H+Zn/H+yz2jA\=\=
如何更换密文密码?
生成密文密码需要准备druid的jar包,然后通过命令行生成:
准备jar包(示例使用 druid-0.2.23.jar),放到某目录下,且在该目录下打开命令窗口,输入命令:
java -cp druid-0.2.23.jar com.alibaba.druid.filter.config.ConfigTools 你要设置的密码
回车后,就会看到生成后的密文密码了,复制出来就可以替换jdbc.properties中的原有密码了!
BeanUtils工具可以方便开发者操作javabean对象。
JavaBean规范:必须有无参的构造方法、属性私有化、提供公开的getter和setter方法
BeanUtils工具的作用
1)拷贝一个javabean对象的属性
2)从一个javabean对象拷贝所有属性到另一个javabean对象
3)从一个map集合中拷贝所有数据到javabean对象中。
编程步骤
导包
commons-beanutils-1.8.3.jar 核心包
commons-logging-1.1.3.jar 辅助包
写代码操作JavaBean
1)对JavaBean的属性进行赋值
/** * 1)对javabean的属性操作 */ @Test public void test1() throws Exception{ /* //1.创建javabean对象 Student student = new Student(); //2.赋值 student.setId(1); student.setName("eric"); student.setGender(true); student.setScore(79.43); student.setBirth(new Date()); */ //1.使用反射创建javabean对象 Object student = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.对javabean进行赋值 /** * 该方法是给avabean的一个属性赋值 * 参数一:需要赋值的javabean对象 * 参数二: 需要赋值的属性名称 * 参数三: 属性值 * 注意: * 1)如果基本数据类型(Integer,Double,Float,Long,Char,Boolean),可以由字符串类型自动转换过去 */ /** * 注册一个日期转换器 * 参数一: 注册的转换器对象 * 参数二: 转换后的目标类型 */ ConvertUtils.register(new DateLocaleConverter(), java.util.Date.class); BeanUtils.copyProperty(student, "name", "jacky");//给Student的name属性赋值 BeanUtils.copyProperty(student, "id", "2"); BeanUtils.copyProperty(student, "gender", "true"); BeanUtils.copyProperty(student, "score", 84.78); BeanUtils.copyProperty(student, "birth", "2014-10-01"); //默认情况下,不能把字符串的日期转换为Date类型 System.out.println(student); } |
2)对JavaBean的对象赋值
/** * 拷贝整个javabean的对象(所有属性一次性拷贝) * @throws Exception */ @Test public void test2() throws Exception{ Student student = new Student(); student.setId(1); student.setName("eric"); student.setGender(true); student.setScore(79.43); student.setBirth(new Date()); //1.使用反射构造对象 Object student2 = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.把student的属性拷贝到student2中 /** * 参数一: 目标的javabean * 参数二: 拷贝的源javabean */ BeanUtils.copyProperties(student2, student); System.out.println(student2); } |
3)把Map数据赋值给JavaBean对象
/** * 把map集合的数据拷贝到javabean中 * @throws Exception */ @Test public void test3() throws Exception{ Map map = new HashMap(); map.put("id", 2); map.put("name", "rose"); map.put("gender", true); map.put("score", 90.32); map.put("birth", new Date()); //1.使用反射构造对象 Object student2 = Class.forName("gz.itcast.d_beanutils.Student").newInstance(); //2.把map数据拷贝到student2中 BeanUtils.copyProperties(student2, map); System.out.println(student2); } |
应用案例
在web项目中,经常需要拷贝请求中的参数到JavaBean对象中,这时可以使用BeanUtils工具,把请求的Map结构的所有参数一次拷贝到javaben中。
/** * 优化后的方法,请求参数拷贝到javabean对象中 * @param request * @param obj */ public static void copyRequestToBean(HttpServletRequest request,Object obj){ System.out.println("优化后的拷贝方法..."); //1.得到请求的所有参数 /** * map: 键是参数的名称,值是参数内容 */ Map requestMap = request.getParameterMap(); //2.把请求map对象拷贝到obj中 try { BeanUtils.copyProperties(obj, requestMap); }catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } |
使用元数据,可以编写更通用的jdbc代码。
简介
三种元数据 | 名称 | 封装了哪些信息 | 获取方式 |
DataBaseMetaData | 数据库元对象 | 封装了数据库相关信息: 数据库的版本、驱动程序的版本 | 从Connection对象中获取DataBaseMetaData |
ParameterMetaData | 参数元对象 | 封装了sql参数相关信息: 参数数量、参数类型 | 从Statement对象中获取ParameterMetaData |
ResultSetMetaData | 结果集元对象 | 封装了结果集相关信息: 列数量、列名称 | 从ResultSet中获取ResultSetMetaData |
编程步骤
应用:抽取两个通用的jdbc方法
1)通用的修改方法(insert、update、delete):update() 用于任何表的任何更新操作
2)通用的查询方法,查询后返回List集合:find() 无论查询什么表,都能返回我们需要的List集合
/** * jdbc的工具方法 */ public class DBUtil { private static ComboPooledDataSource ds = new ComboPooledDataSource(); /** * 通用的更新方法 * 不同更新操作的不同点: * 1)sql语句不同 * 2)参数列表不同 */ public static void update(String sql,Object[] values){ Connection conn = null; PreparedStatement stmt = null; try { //获取连接 conn = ds.getConnection(); //预编译sql stmt = conn.prepareStatement(sql); //设置参数 ParameterMetaData md = stmt.getParameterMetaData(); //得到参数数量 int count = md.getParameterCount(); if(values!=null){ for(int i=1;i<=count;i++){ stmt.setObject(i, values[i-1]); } } //执行sql stmt.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } /** * 通用的查询方法 * 不同点: * 1)sql语句不同 * 2)参数不同 * 3)List集合中的对象不同 */ public static List find(String sql,Object[] values,Class clazz){ Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; List list = new ArrayList(); try { //获取连接 conn = ds.getConnection(); //预编译sql stmt = conn.prepareStatement(sql); //设置参数 ParameterMetaData md = stmt.getParameterMetaData(); int count = md.getParameterCount(); if(values!=null){ for(int i=1;i<=count;i++){ stmt.setObject(i, values[i-1]); } } //执行sql,得到结果集 rs = stmt.executeQuery(); //得到结果集的元数据 ResultSetMetaData rsmd = rs.getMetaData(); //得到列数据 int columnCount = rsmd.getColumnCount(); //遍历结果集 while(rs.next()){ //创建一个javabean对象 Object obj = clazz.newInstance(); //把每行的结果放入javabean对象 //遍历每列 for(int i=1;i<=columnCount;i++){ //得到列名称(从结果集元数据中) String columnName = rsmd.getColumnName(i); //得到列内容(从结果集中) Object value = rs.getObject(columnName); //把列内容赋值给javabean /** * 约定前提: 数据库表的字段名称和javabean的属性名称保持一致!!!! */ BeanUtils.copyProperty(obj, columnName, value); } //把javabean放入list集合 list.add(obj); } return list; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } finally{ if(stmt!=null) try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } if(conn!=null) try { conn.close(); } catch (SQLException e) { e.printStackTrace(); throw new RuntimeException(e); } } } } |
DBUtils是Apache出品的对JDBC代码简单封装的工具,提供了一些通用的JDBC操作方法。
核心API
QueryRunner类:通过此类可以执行更新操作或者查询操作
update(...) 更新(DDL、DML)
query(...) 查询(DQL)
ResultSetHandler接口:把结果集封装成不同的对象
Object handle(ResultSet rs) 封装结果集到Object
常用的实现类
ArrayHandler: 把结果集的第一行的数据封装成对象数组。
ArrayListHandler:把结果集的每一行数据封装对象数组,把这个对象数组放入List中。
BeanHandler: 把结果集的第一行数据封装成javabean。
BeanListHandler: 把结果集的每一行数据封装成javabean,把这个javabean放入LIst中。
ScalarHandler: 把结果集的第一行第一列取出。通常用于聚合函数查询。例如 count()、max()
如果表的字段名称和javabean的属性名称不一致时,需要自定义ResultSetHandler的实现类。
编程步骤
导包
commons-dbutils-1.2.jar
写代码
更新操作
/** * 使用dbutils工具 * @author APPle * */ public class Demo1 { @Test public void testInsert() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner对象 QueryRunner qr = new QueryRunner(ds); //2.执行操作 //qr.update("INSERT INTO student(NAME,age,address) VALUES('张三11',20,'广州天河')"); qr.update("INSERT INTO student(NAME,age,address) VALUES(?,?,?)", new Object[]{"eric11",20,"广州天河"}); } @Test public void testInsert2() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); Connection conn = ds.getConnection(); QueryRunner qr = new QueryRunner(); qr.update(conn,"INSERT INTO student(NAME,age,address) VALUES('张三22',20,'广州天河')"); //手动关闭连接 conn.close(); } } |
查询操作
/** * dbutils执行查询操作 * @author APPle * */ public class Demo2 { /** * ArrayHandler: 把结果集的第一行的数据封装成对象数组。 */ @Test public void test1() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Object[] arr = (Object[])qr.query("select * from student where id=?", new ArrayHandler(),new Object[]{2}); for(Object obj:arr){ System.out.println(obj); } } /** * ArrayListHandler: 把结果集的每一行数据封装对象数组,把这个对象数组放入List中 * @throws Exception */ @Test public void test2() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql List<Object[]> list = (List<Object[]>)qr.query("select * from student", new ArrayListHandler()); for(Object[] arr:list){//一行 //一列 for(Object obj:arr){ System.out.print(obj+"\t"); } System.out.println(); } } /** * BeanHandler: 把结果集的第一行数据封装成javabean * 约定前提: 表的字段名称和javabean的属性名称保持一致!! */ @Test public void test3() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Student student = (Student)qr.query("select * from student", new BeanHandler(Student.class)); System.out.println(student); } /** * BeanListHandler: 把结果集的每一行数据封装成javabean,把这个javabean放入LIst中 * 约定前提: 表的字段名称和javabean的属性名称保持一致!! */ @Test public void test4() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql List<Student> list = (List<Student>)qr.query("select * from student", new BeanListHandler(Student.class)); for (Student student : list) { System.out.println(student); } } /** * ScalarHandler: 把结果集的第一行第一列取出。通常用于聚合函数查询。例如(count()/max()) */ @Test public void test5() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); //2.执行sql Long count = (Long)qr.query("select count(id) from student", new ScalarHandler(1)); System.out.println("行数: "+count); } /** * 如果表的字段名称和javabean的属性名称不一致时,需要自定义ResultSetHandler的实现类 */ @Test public void test6() throws Exception{ ComboPooledDataSource ds = new ComboPooledDataSource(); //1.创建QueryRunner QueryRunner qr = new QueryRunner(ds); List<Student> list = (List<Student>)qr.query("select * from student2", new MyStudentHandler()); for (Student student : list) { System.out.println(student); } } } /** * 自定义ResultSetHandler */ class MyStudentHandler implements ResultSetHandler{ @Override public Object handle(ResultSet rs) throws SQLException { List<Student> list = new ArrayList<Student>(); while(rs.next()){ Student s = new Student(); s.setId(rs.getInt("sid")); s.setName(rs.getString("sname")); s.setAge(rs.getInt("sage")); s.setAddress(rs.getString("saddress")); list.add(s); } return list; } } |