数据持久化: 数据持久化就是将内存中的数据模型转换为存储模型,以及将存储模型转换为内存中的数据模型的统称。数据模型可以是任何数据结构或对象模型,存储模型可以是关系模型、xml、二进制流等。数据持久化意味着将内存中的数据保存到硬盘加以“固化”。
好处:
使用数据持久化有以下好处:
- 程序代码重用性强,即使更换数据库,只需要更改配置文件,不必重写程序代码。
- 业务逻辑代码可读性强,在代码中不会有大量的SQL语言,提高程序的可读性。
- 持久化技术可以自动优化,以减少对数据库的访问量,提高程序运行效率。
数据持久化对象的基本操作有:保存、更新、删除、查询等。
在Java中,数据库存储技术可分为以下几类
- JDBC(Java DataBase Connectivity)直接访问数据库
- JDO技术
- 第三方O/R工具,如:hibernate、ibatis(mybatis)等
JDBC是Java访问数据库的基石,JDO、Hibernate、ibatis等只是更好的封装了JDBC。它独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),可以连接任何提供了JDBC驱动程序的数据库系统,为访问不同的数据库提供了一种统一的途径。
JDBC接口(API)包含两个层次:
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
- 面向数据库的API:Java Driver API,供应开发商开发数据库驱动程序用
JDBC驱动程序:各个数据库厂商根据JDBC的规范制作的JDBC实现类的类库
JDBC驱动程序的类型:
- JDBC-ODBC桥
- 部分本地API部分Java的驱动程序
- JDBC网络存Java驱动程序
- 本地协议的纯Java驱动程序
本地协议的纯Java驱动程序(重要)
这种类型的驱动程序完全使用Java编写,通过与数据库建立Socket连接,采用具体与厂商的网络协议把JDBC调用转换为直接连接的网络调用
Driver接口:Java.sql.Driver接口是所有JDBC驱动程序需要实现的接口。这个接口是提供数据库厂商使用的,不同的数据库厂商提供不同的实现。在程序中不需要直接去访问实现了Driver接口的类,而是由驱动程序管理器类(java.sql.DriverManager)去调用这些Driver实现。
DriverManager接口:DriverManager是驱动的管理类。它可以通过重载getConnection()的方法获取数据库连接,较为方便;还可以同时管理多个驱动程序:若注册了多个数据库连接,则调用 getconnection()方法时传入不同的参数,即返回不同的数据库连接
操作数据库——步骤:
- 注冊驱动 (仅仅做一次)
- 建立连接(Connection)
- 创建运行SQL的语句(Statement)
- 运行语句
- 处理运行结果(ResultSet)
- 释放资源
package com.sardine.jdbc;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.SQLException;
import java.util.Properties;
import org.junit.jupiter.api.Test;
class JDBCUtil {
/***
* Driver简单连接
* @throws SQLException
*/
@Test
public void test() throws SQLException {
// 1、准备连接数据库的字符串
String url = "jdbc:mysql://127.0.0.1:3306/user";
String user = "";
String password = "";
String driverClass = "com.mysql.jdbc.Driver";
// 2.创建Driver实现类对象
Driver driver = new com.mysql.jdbc.Driver();
// 此处可以用反射机制创建,该方法充分解耦
//Driver driver = (Driver)Class.forName(driverClass).newInstance();
Properties info = new Properties();
info.put("user", user);
info.put("password", password);
// 3.调用Driver的connect接口,连接数据库
Connection connection = driver.connect(url, info);
System.out.println(connection);
// 4.关闭连接
connection.close();
}
}
package com.sardine.jdbc;
import java.sql.Connection;
import java.sql.DriverManager;
import org.junit.jupiter.api.Test;
class JDBCUtil {
/***
* DriverManager连接
* @throws SQLException
*/
@Test
public void test() throws Exception{
// 1、准备连接数据库的字符串
String url = "jdbc:mysql://127.0.0.1:3306/user";
String user = "";
String password = "";
String driverClass = "com.mysql.jdbc.Driver";
// 2、加载数据库驱动程序(注册驱动 对应的Driver实现类中有注册驱动的静态代码块)
/***
* 实际上注册驱动时,应写成 DriverManager.registerDriver((Driver)Class.forName(driverClass).newInstance());
* 但是 java.mysql.jdbc 已经在静态代码块中封装了注册方法:java.sql.DriverManager.registerDriver(new Driver());
* 所以只需要加载驱动程序加载即可
*/
Class.forName(driverClass);
// 3、调用DriverManager的getConnection接口,连接数据库
Connection connection = DriverManager.getConnection(url, user, password);
System.out.println(connection);
// 4.关闭连接
connection.close();
}
}
注册驱动方式:
- Class.forName(“com.mysql.jdbc.Driver”);(推荐这样的方式,不会对详细的驱动类产生依赖)
- DriverManager.registerDriver(com.mysql.jdbc.Driver);(会对详细的驱动类产生依赖)
- System.setProperty(“jdbc.drivers”, “driver1:driver2”);(尽管不会对详细的驱动类产生依赖;但注冊不太方便。所以非常少使用)
建立连接(Connection):
通过Connection建立连接,Connection是一个接口类。其功能是与数据库进行连接(会话)。
几种常用数据库的JDBC url:
- mysql:jdbc:mysql//localhost:3306/user
- oracle:jdbc:oracle:thin:@localhost:1521:user
- SQLServer:jdbc:microsoft:sqlserver//localhost:1433;DataBaseName=user
创建运行SQL的语句(Statement):
通过Connection的createStantement()方法获取Statement对象
executeQuery(Stringsql),该方法用于运行实现查询功能的sql语句。返回类型为ResultSet(结果集)。
executeUpdate(Stringsql),该方法用于运行实现增、删、改功能的sql语句,返回类型为int,即受影响的行数。
ResultSet对象:
ResultSet:结果集,封装了使用JDBC进行查询的结果集。
ResultSet返回的实际就是一张数据表,有一个指针指向数据表的第一行前面。可以调用next()方法检测下一行是否有效,若有效方法返回true,且指针下移。相当于Iterator对象的hashNext()和next()结合体。
当指针定位到一行时,可以通过调用getXxx(columnIndex)或getXxx(columnLabel)获取每一列的值,列索引从1开始。例如:getInt(1)、getString("name")。
Statement接口类还派生出两个接口类PreparedStatement和CallableStatement,这两个接口类对象为我们提供了更加强大的数据访问功能。
PreparedStatement:能够对SQL语句进行预编译,这样防止了 SQL注入 提高了安全性。预编译结果能够存储在PreparedStatement对象中。当多次运行SQL语句时能够提高效率。作为Statement的子类,PreparedStatement继承了Statement的全部函数。
PreparedStatement ps = connection.prepareStatement( "select * from user where id = ? and name = ?");
ps.setInt(1, 1);
ps.setString(2, "集团管理员");
ResultSet set = ps.executeQuery();
while(set.next()) {
System.out.println(set.getString("name"));
}
CallableStatement:CallableStatement类继承了PreparedStatement类,他主要用于运行SQL存储过程。在JDBC中运行SQL存储过程须要转义。
对数据库中存储过程的调用是CallableStatement对象所含的内容。有两种形式:1:形式带结果参数;2:形式不带结果参数。结果参数是一种输出参数(存储过程中的输出OUT参数),是存储过程的返回值。两种形式都有带有数量可变的输入、输出、输入和输出的参数。用问号做占位符。
- 形式带结果参数语法格式:{ ? = call 存储过程名[(?, ?, ?, ...)]};
- 形式不带结果参数语法格式:{ call 存储过程名[(?, ?, ?, ...)]};PS方括号里面的内容可有可无。
CallableStatement接口中常用的方法。
1:getInt(int parameterIndex)、getInt(String parameterName)、还有getString、getBigDecimal、getString、getDate、getURL等等都类似和PreparedStatement与Statement中的用法类似。
2:registerOutParameter(int parameterIndex, int sqlType):按顺序位置parameterIndex将OUT参数注册为JDBC类型sqlType。
3:wasNull():查询最后一个读取的OUT参数是否为SQL Null。
Class.forName(driverClass);
connection = DriverManager.getConnection(url, user, password);
/***
* 调用无参存储过程
* 例sql:create procedure findAll_user() select * from user;
*/
// 通过存储过程名称连接
CallableStatement callableStatement1 = connection.prepareCall("{call sp_name()}");
set = callableStatement1.executeQuery();
while (set.next()) {
System.out.println(set.getString("name"));
}
/***
* 带参存储过程
* 例sql:create procedure insert_user(in myid int, in myname varchar(50)) insert into user(id,name) values(myid, myname);
*/
// 通过存储过程名称连接
CallableStatement callableStatement2 = connection.prepareCall("{call insert_user(?,?)}");
callableStatement2.setInt(1, 1002);
callableStatement2.setString(2, "测试数据名称");
int state = callableStatement2.executeUpdate();
System.out.println(state);
/***
*创建有输入输出参数的存储过程
* 例sql:create procedure getNameById(in cid int, out return_name varchar(50)) select name into return_name from user where id = cid;
* 注意:1、有输出变量的时候,要给输出变量进行注册 2、创建存储过程时,需要用into指定返回值,类似于sql中as
*/
// 通过存储过程名称连接
CallableStatement callableStatement3 = connection.prepareCall("{call getNameById(?,?)}");
callableStatement3.setInt(1, 1000);
// 注册输出变量
callableStatement3.registerOutParameter(2, Types.CHAR);
boolean b = callableStatement3.execute();
System.out.println(b);
String name = callableStatement3.getString(2);
System.out.println(name);
释放资源:
数据库资源不关闭,其占用的内存不会被释放,徒耗资源,影响系统。
注意:Connection、Statement都是应用程序和数据库服务器的连接资源,使用后一定要关闭。特别注意在异常时,要在finally语句块中关闭Connection和Statement对象。关闭顺序,先关闭后获取的,即先关闭Statement,在关闭Connection。
JDBC处理元数据
DatabaseMetaData:是描述数据库的元数据对象。可以从这个对象获得有关数据库管理系统的各种信息,包括数据库中的各个表,表中的各个列,数据类型,触发器,存储过程等各方面的信息。
ResultSetMetaData:是描述结果集的元数据对象。可以知道SQL语句中查询了哪些列,以及列的别名、值等基本信息。
JDBC利用ResultSetMetaData元数据反射编写通用的查询方法
- 利用SQL进行查询得到结果集(注意:返回的字段需与Bean字段相同,可以用 as 别名)
- 得到ResultSetMetaData对象
- 反射到实体Bean对象
package com.sardine.jdbc;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
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.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
class JDBCUtil {
@Test
public void test() {
String studentSql = "select id,name,sex,birthday from student where id = ?";
Student student = getBean(Student.class, studentSql, 1);
System.out.println(student.getId());
System.out.println(student.getName());
System.out.println(student.getSex());
System.out.println(student.getBirthday());
String bookSql = "select id,book_name bookName,author,price from book where id = ?";
Book book = getBean(Book.class, bookSql, 1);
System.out.println(book.getId());
System.out.println(book.getBookName());
System.out.println(book.getAuthor());
System.out.println(book.getPrice());
}
private <T> T getBean(Class<T> clazz, String sql, Object ... args){
T entity = null;
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
// 1.连接数据库资源
connection = getConnection();
// 2.获取 ResultSet 对象
preparedStatement = connection.prepareStatement(sql);
// 3.添加参数
if(null != args) {
for(int i = 0; i < args.length; i++) {
preparedStatement.setObject(i + 1, args[i]);
}
}
resultSet = preparedStatement.executeQuery();
// 4.创建 ResultSetMetaData 对象
ResultSetMetaData rsmd = preparedStatement.getMetaData();
Map<String, Object> map = new HashMap<String, Object>();
while(resultSet.next()) {
for(int i = 0; i < rsmd.getColumnCount(); i++) {
/***
* 此处区分获取字段和字段值所用的对象不是同一个
*/
// 获取 sql 字段名称
String columnLable = rsmd.getColumnLabel(i + 1);
// 获取 sql 字段名称对应值
Object columnValue = resultSet.getString(columnLable);
map.put(columnLable, columnValue);
}
}
// 5.若 Map 不为空集, 利用反射创建 clazz 对应的对象
if(null != map){
entity = clazz.newInstance();
//6. 遍历 Map 对象, 利用反射为 Class 对象的对应的属性赋值.(也可以使用BeanUtils工具类)
for(Map.Entry<String, Object> entry: map.entrySet()){
String fieldName = entry.getKey();
Object value = entry.getValue();
// 带条件id查询,每次只存在1条数据
setFieldValue(entity, fieldName, value);
}
}
}catch (Exception e) {
e.printStackTrace();
}finally {
// 释放资源
releaseDB(resultSet, preparedStatement, connection);
}
return entity;
}
/***
* 获取数据库连接
* @return
* @throws Exception
*/
private Connection getConnection() throws Exception {
String url = "jdbc:mysql://127.0.0.1:3306/localhost_database";
String user = "";
String password = "";
String driverClass = "com.mysql.jdbc.Driver";
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(url, user, password);
return connection;
}
/***
* 释放资源
* @param resultSet
* @param statement
* @param connection
*/
private void releaseDB(ResultSet resultSet, Statement statement, Connection connection) {
if(null != resultSet){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != statement){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(null != connection){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
/******************************************************************************/
/**
* 循环向上转型, 获取对象的 DeclaredField
* @param object
* @param filedName
* @return
*/
private Field getDeclaredField(Object object, String filedName){
for(Class<?> superClass = object.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()){
try {
return superClass.getDeclaredField(filedName);
} catch (NoSuchFieldException e) {
//Field 不在当前类定义, 继续向上转型
}
}
return null;
}
/**
* 使 filed 变为可访问
* @param field
*/
private void makeAccessible(Field field){
if(!Modifier.isPublic(field.getModifiers())){
field.setAccessible(true);
}
}
/***
* 设置对象属性值
* @param object
* @param fieldName
* @param value
*/
private void setFieldValue(Object object, String fieldName, Object value){
Field field = getDeclaredField(object,fieldName);
if (field == null)
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]");
makeAccessible(field);
try {
// 需注意,反射时编译器不会进行自动装/拆箱
if(field.getType() == Long.class) {
field.set(object, Long.valueOf(value.toString()));
}
if(field.getType() == Integer.class) {
field.set(object, Integer.valueOf(value.toString()));
}
if(field.getType() == Date.class) {
field.set(object, new Date());
}
if(field.getType() == String.class) {
field.set(object, value.toString());
}
if(field.getType() == BigDecimal.class) {
field.set(object, new BigDecimal(value.toString()));
}
} catch (IllegalAccessException e) {
e.getMessage();
}
}
}
JDBC获取插入记录的主键
// prepareStatement的第二个参数为 autoGeneratedKeys,返回主键
preparedStatement = connection.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
// 必须执行,才可以获取key
preparedStatement.executeUpdate();
// 通过getGeneratedKeys()方法获取包含了新生成的主键值
// 如果使用getGeneratedKeys()方法,那么resultSet中只有一列 GENERATED_KEY,用于存放新生成的主键值
resultSet = preparedStatement.getGeneratedKeys();
JDBC中Date类型数据
创建Date型数据:new java.sql.Date(new java.util.Date().getTime())
获取Date型数据:resultSet.getDate();
JDBC查询LOB(Large Objects-大对象,用来存储大量的二进制和文本数据的一种数据类型)
- Oracle
- BOLO(二进制数据,适合用于存储大量的二进制数据,如图像,视频,音频,文件等)
- CLOB(单字节字符数据,用于存储超长的文本数据)
- NCLOB (多字节字符数据,用于存储超长的文本数据)
- Mysql
- TinyBlob(二进制大型对象,最大255)
- Blob(二进制大型对象,最大65k)
- MediumBlob(二进制大型对象,最大16M)
- LongBlob(二进制大型对象,最大4G)
/***
* bolb insert和update
*/
@Test
public void test() {
Connection connection = null;
PreparedStatement insertPreparedStatement = null;
PreparedStatement selectPreparedStatement = null;
ResultSet insertResultSet = null;
ResultSet selecttResultSet = null;
try {
// 1.连接数据库资源
connection = getConnection();
// 插入BLOB类型的数据必须使用 PreparedStatement:因为BOLB类型数据无法使用字符串拼接
String insertSql = "insert into student(id,name,sex,birthday,picture) values(?,?,?,?,?)";
// prepareStatement的第二个参数为 autoGeneratedKeys,返回主键
insertPreparedStatement = connection.prepareStatement(insertSql,Statement.RETURN_GENERATED_KEYS);
insertPreparedStatement.setObject(1, 1);
insertPreparedStatement.setObject(2, "the_sardine");
insertPreparedStatement.setObject(3, 1);
// 时间类型数据格式
insertPreparedStatement.setObject(4, new java.sql.Date(new java.util.Date().getTime()));
// 将数据转换为InputStream流对象存入
InputStream in = new FileInputStream("C:\\Users\\极光.jpg");
insertPreparedStatement.setObject(5, in);
insertPreparedStatement.executeUpdate();
insertResultSet = insertPreparedStatement.getGeneratedKeys();
int id = 0;
while (insertResultSet.next()) {
id = insertResultSet.getInt(1);
}
String selectSql = "select id,name,sex,birthday,picture from student where id = ?";
selectPreparedStatement = connection.prepareStatement(selectSql);
selectPreparedStatement.setObject(1, 1);
selecttResultSet = selectPreparedStatement.executeQuery();
while(selecttResultSet.next()) {
// 获取id
int s_id = selecttResultSet.getInt(1);
// 获取name
String s_name = selecttResultSet.getString(2);
// 获取性别
int s_sex = selecttResultSet.getInt(3);
// 获取生日
java.sql.Date s_birthday = selecttResultSet.getDate(4);
System.out.println(s_id);
System.out.println(s_name);
System.out.println(s_sex);
System.out.println(s_birthday);
// 调用 Blob 的 getBinaryStream()方法得到输入流,在使用IO操作即可。
Blob s_picture = selecttResultSet.getBlob(5);
InputStream sin = s_picture.getBinaryStream();
OutputStream out = new FileOutputStream("C:\\Users\\极光1.jpg");
byte[] buffer = new byte[1024];
int len = 0;
while((len = sin.read(buffer)) != -1) {
out.write(buffer, 0, len);
}
out.close();
sin.close();
}
}catch (Exception e) {
e.getMessage();
}finally {
// 释放资源
releaseDB(resultSet, preparedStatement, connection);
}
}
SQL注入攻击:利用某些系统没有对用户输入的数据进行充分检查,而在用户输入数据中注入非法的SQL语句段或命令,从而利用系统的SQL引擎完成恶意行为的做法。对于Java而言,要防范SQL注入,只要用PreparedStatement取代Statement即可。
例:
String url = "SELECT * from user where username = "+ username +" AND password =" + password;
-- username = "A' OR password ="
-- password = "OR '1' = '1'";
-- 这样 sql变为 SELECT * from user where username = 'A' OR password = 'AND PASSWORD = ' OR '1' = '1'
-- 但是使用 PreparedStatement 时,?不会直接赋值,所以就防止了SQL注入
-- SELECT * from user where username = ? AND password = ?
JDBC事务管理
具体步骤:
- 开始事务:取消Connection的默认提交行为
- 如果事务的操作都成功,则提交事务:connection.commit();
- 回滚事务:若出现异常,则在catch块中回滚事务,connection.rollback();
注意:多个操作,每个操作使用的是自己的单独连接,则无法保证事务
对于同时运行的多个事务,当这些事务访问数据库中相同数据时,如果没有采取必要的隔离机制,就会导致各种并发问题:
- 脏读:对于两个事务T1、T2,T1读取了已经被T2更新但还没有被提交的字段之后,若T2回滚。T1读取的内容就是临时且无效的(必须要避免的)。
- 不可重复读:对于两个事务T1、T2,T1读取了一个字段,然后T2更新了改字段之后,T1在次读取同一字段,值就不同了。
- 幻读:对于两个事务T1、T2,T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行之后,如果T1在次读取同一个表,就会多出几行。
数据库提供4中隔离级别:
- READ UNCOMMITTTED(读取提交数据):允许事务读取未被其他事务提交的变更。脏读、不可重复的和幻读都会出现。
- READ COMMITED(读已提交数据):只允许事务读取已经被其他事务提交的变更。可以避免脏读,但不可重复读和幻读问题依然存在。
- REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读。但幻读问题仍然存在。
- SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下。
Oracle 支持2种事务级别:READ COMMITED、SERIALIZABLE。Oracle默认事务隔离级别为:READ COMMITED。
Mysql 支持4种事务级别,默认的事务级别为:REPEATABLE READ。
Mysql中相关操作:
- Mysql 查看事物隔离级别的sql:select @@tx_isolation;
- Mysql 设置隔离级别的sql:set tx_isolation='事物隔离级别名称';
- Mysql 设置当前连接的隔离级别:set transaction isolation level '事物隔离级别名称';
- Mysql 设置数据库系统的全局隔离级别:set global transaction isolation level '事物隔离级别名称';
- 在JDBC中,可以通过 connection.setTransactionIsolation(int level),来设置事务级别。在调用Connection对象的setAutoCommit(false)方法之前。
JDBC批处理
Statement < PreparedStatement(预编译)
JDBC的批处理语句包括下面两个方法:
- addBatch(String):需要添加批量处理的SQL语句或是参数
- executeBath():执行批量处理命令
- clearBatch():清除批处理命令
两种批量执行SQL语句的情况:
- 多条SQL语句的批处理
- 一条SQL语句的批量传参
connection = getConnection();
statement = connection.createStatement();
String sql1 = "insert into user(id,name) values(1,'zhangsan')";
String sql2 = "insert into user(id,name) values(2,'lisi')";
String sql3 = "insert into user(id,name) values(3,'wangwu')";
String sql4 = "insert into user(id,name) values(4,'xiaoming')";
String sql5 = "update user set name='gacl' where id=1";
String sql6 = "insert into user(id,name) values(5,'adai')";
String sql7 = "delete from user where id=2";
statement.addBatch(sql1);
statement.addBatch(sql2);
statement.addBatch(sql3);
statement.addBatch(sql4);
statement.addBatch(sql5);
statement.addBatch(sql6);
statement.addBatch(sql7);
//执行批处理SQL语句
statement.executeBatch();
//清除批处理命令
statement.clearBatch();
Statement.addBatch(sql)方式实现批处理的优缺点:
- 优点:可以向数据库发送多条不同的sql语句。
- 缺点:SQL语句没有预编译。
connection = getConnection();
String sql = "insert into user(id,name) values(?,?)";
preparedStatement = connection.prepareStatement(sql);
for(int i = 0; i < 1000000; i++){
preparedStatement.setInt(1, i + 1);
preparedStatement.setString(2, "name_" + i);
// “积攒”sql( preparedStatement 会自动预编译sql,所以此处无须填写sql)
preparedStatement.addBatch();
// 当积攒到一定程度,统一执行,并清空之前积攒的sql
if((i + 1) % 300 == 0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
}
// 若总条数不是批量数值的整倍数,则还需要在额外执行异常
if(1000000 % 300 != 0){
preparedStatement.executeBatch();
preparedStatement.clearBatch();
}
PreparedStatement.addBatch()方式实现批处理的优缺点:
- 优点:发送的是预编译后的SQL语句,执行效率高。
- 缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
JDBC数据库连接池
JDBC 的数据库连接池使用 javax.sql.DataSource 来表示,DataSource 只是一个接口,该接口通常由服务器(Weblogic, WebSphere, Tomcat)提供实现,也有一些开源组织提供实现:
- DBCP 数据库连接池
- C3P0 数据库连接池
DBCP 数据源
DBCP 是 Apache 软件基金组织下的开源连接池实现,该连接池依赖该组织下的另一个开源系统:Common-pool.如需使用该连接池实现,应在系统中增加如下两个 jar文件:
- Commons-dbcp.jar:连接池的实现
- Commons-pool.jar:连接池实现的依赖库
// 1、创建DBCP数据源实例
final BasicDataSource dataSource = new BasicDataSource();
// 2、为数据源实例指定必须的属性
dataSource.setUsername("root");
dataSource.setPassword("");
dataSource.setUrl("jdbc:mysql:///jdbc?useSSL=false");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
// 3、指定数据源的一些可选的属性.
// 1). 指定数据库连接池中初始化连接数的个数
dataSource.setInitialSize(5);
// 2). 指定最大的连接数: 同一时刻可以同时向数据库申请的连接数
dataSource.setMaxActive(5);
// 3). 指定小连接数: 在数据库连接池中保存的最少的空闲连接的数量
dataSource.setMinIdle(2);
// 4).等待数据库连接池分配连接的最长时间. 单位为毫秒. 超出该时间将抛出异常.
dataSource.setMaxWait(1000 * 5);
// 4、从数据源中获取数据库连接
Connection connection = dataSource.getConnection();
/**
* 使用配置文件的方式加载 DBCP数据源实例
* 1. 加载 dbcp 的 properties 配置文件: 配置文件中的键需要来自 BasicDataSource 的属性.
* 2. 调用BasicDataSourceFactory 的 createDataSource 方法创建 DataSource 实例
* 3. 从DataSource 实例中获取数据库连接.
*/
Properties properties = new Properties();
InputStream inStream = JDBCUtil.class.getClassLoader().getResourceAsStream("dbcp.properties");
properties.load(inStream);
DataSource dataSource = BasicDataSourceFactory.createDataSource(properties);
System.out.println(dataSource.getConnection());
/**
* 文档数据
*/
#username=root
#password=password
#driverClassName=com.mysql.jdbc.Driver
#url=jdbc:mysql://localhost:3306/local_database?useUnicode=true&characterEncoding=UTF-#8&useSSL=false
#initialSize=10
#maxActive=50
#minIdle=5
#maxWait=5000
C3P0 数据源
增加如下两个 jar文件:
-
cc3p0-0.9.2.jar
- mchange-commons-java-0.2.2.jar
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass("com.mysql.jdbc.Driver"); // loads the jdbc driver
cpds.setJdbcUrl("jdbc:mysql:///jdbc?useSSL=false");
cpds.setUser("root");
cpds.setPassword("");
System.out.println(cpds.getConnection());
XML配置C3P0数据源
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<named-config name="helloc3p0">
<!-- 指定连接数据源的基本属性 -->
<property name="user">root</property>
<property name="password"></property>
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql:///jdbc?useSSL=false</property>
<!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 -->
<property name="acquireIncrement">5</property>
<!-- 初始化数据库连接池时连接的数量 -->
<property name="initialPoolSize">5</property>
<!-- 数据库连接池中的最小的数据库连接数 -->
<property name="minPoolSize">5</property>
<!-- 数据库连接池中的最大的数据库连接数 -->
<property name="maxPoolSize">10</property>
<!-- C3P0 数据库连接池可以维护的 Statement 的个数 -->
<property name="maxStatements">20</property>
<!-- 每个连接同时可以使用的 Statement 对象的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
/**
* 1. 创建 c3p0-config.xml 文件, 参考帮助文档中 Appendix B: Configuation Files 的内容
* 2. 创建 ComboPooledDataSource 实例;
* DataSource dataSource = new ComboPooledDataSource("helloc3p0");
* 3. 从 DataSource 实例中获取数据库连接.
*/
@Test
public void testC3poWithConfigFile() throws Exception {
DataSource dataSource = new ComboPooledDataSource("helloc3p0");
System.out.println(dataSource.getConnection());
ComboPooledDataSource comboPooledDataSource = (ComboPooledDataSource) dataSource;
System.out.println(comboPooledDataSource.getMaxStatements());
}
dbcp和c3p0不同之处:
dbcp | c3p0 |
spring组织推荐使用 | Hibernate组织推荐使用 |
强制关闭连接或者数据库重启后无法自动重连 | 强制关闭连接或者数据库重启可以自动连接 |
没有自动的去回收空闲连接的功能 | 自动回收空闲的功能 |
DBCP提供最大连接数 | c3p0提供最大空闲时间 |
dbcp并没用相应功能 | c3p0可以控制数据源加载的prepareedstatement数量,并且可以设置帮助线程的数量来提升JDBC操作速度 |
获取驱动jar包技巧
一般我们本机上都装有数据库(mysql、oracle),那么找到你安装数据库的目录(以mysql为例)
这里面就有JDBC(Connector J 8.0)驱动,版本还和本机的相同,点进Connector J 8.0目录,其中就有一个jar包
官网下载:官网中的JDBC下载页面的链接
进入页面后选择平台
不要点击下面的Go to Download Page,直接点击上图中的 Looking for previous GA versions?
选择一个tar压缩包或者 zip压缩包下载,上面的是tar压缩包,下面的是zip。