1.数据的持久化
持久化:把数据保存到可掉电式存储设备中以供之后使用
2.JDBC的理解
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统,通用的sql数据库存取和操作的公共接口(一组Api)
简单理解,JDBC是SUN公司提供的一组Api,使用这套Api可以实现对数据库的增删改查(获取连
接,关闭连接,DML,DDL,DCL)
3.图解
好处:
面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行sql语句,获得结果)
面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用
从开发程序员角度:不需要关注具体的数据库细节
从数据库厂商:只需要提供标准的具体实现
4.数据库驱动
类似USB驱动
5.面向接口的编程思想
Java程序员只需要面向JDBC这套接口编程即可
不同的数据库厂商需要针对这套接口提供不同的实现。不同的实现的集合即为不同数据库的驱动
6.数据库的连接
获取连接:
public void testConnection5() throws Exception {
//1.加载配置文件
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsstream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
//2.读取配置信息
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//3.加载驱动
Class.forName(driverClass);
//4.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
其中,配置文件【jdbc.Properties】:此配置文件声明在src下
关闭相关资源:
public static void closs(Connection con, PreparedStatement pstmt, ResultSet rs){
if (con!=null){
try {
con.close();
} catch (sqlException throwables) {
throwables.printstacktrace();
}
}
if (pstmt!=null){
try {
pstmt.close();
} catch (sqlException throwables) {
throwables.printstacktrace();
}
}
if (rs!=null){
try {
rs.close();
} catch (sqlException throwables) {
throwables.printstacktrace();
}
}
}
7.使用Statement进行增删改,查询操作
Statement 接口中定义了下列方法用于执行 sql 语句:
int excuteUpdate(String sql):执行更新操作INSERT、UPDATE、DELETE
ResultSet executeQuery(String sql):执行查询操作SELECT
但是使用Statement操作数据表存在弊端 :
问题一:存在拼串操作,繁琐
问题二:存在sql注入问题
问题三:没办法操作Blob类型变量
问题四:实现批量操作时效率低
sql 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 sql 语句段或命令(如:SELECT user, password FROM user_table WHERE user='a' OR 1 = ' AND password = ' OR '1' = '1') ,从而利用系统的 sql 引擎完成恶意行为的做法。
8.PreparedStatement
PreparedStatement是Statement的子接口
8.1使用PreparedStatement实现通用的增删改操作
public int update(Connection conn, String sql, Object... args) {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
return pstmt.executeUpdate();
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt);
}
return 0;
}
8.2使用PreparedStatement实现通用的查询操作
//针对不同的表的通用查询操作,返回表中的一条记录
public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getobject(i + 1);
String clumnLable = rsmd.getColumnLabel(i + 1);//获取每列的别名
//给t对象指定的columnLabel属性,赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(clumnLable);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt, rs);
}
return null;
}
//针对不同的表的通用查询操作,返回表中的多条记录
public <T> List<T> getInstanceForList(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getobject(i + 1);
String clumnLable = rsmd.getColumnLabel(i + 1);//获取每列的别名
//给t对象指定的columnLabel属性,赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(clumnLable);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt, rs);
}
return null;
}
两种编程思想:
1、面向接口编程思想
2、ORM编程思想:(Object relational mapping)
两种技术:
1、使用结果集的元数据:ResultSetMetaData
>getColumnCount():获取列数
>getColumnLabel():获取类的别名
如果sql中没有给字段起别名,那getColumnLabel()获取到的就是字段名
2、反射的使用
①创建对应的运行时类的对象
查询操作的流程:
8.3PreparedStatement可以操作Blob类型的变量
Blob类型
写入操作方法:
setBlob(InputSteam is);
读取操作方法:
Blob blob = getBlob(int index);
InputStream is = blob.getBinaryStream();
具体的insert:
具体的query:
8.4使用PreparedStatement实现高效地批量插入
public void testInsert1() {
Connection conn= null;
PreparedStatement pstmt = null;
try {
conn = JDBCUtils.getConnection();
String sql="insert into good values(?)";
pstmt = conn.prepareStatement(sql);
//优化方式2:先设置禁止自动提交,等所有语句执行完后再一起提交
conn.setAutoCommit(false);
for (int i = 0; i < 20000; i++) {
pstmt.setobject(1,"name_"+i);
pstmt.addBatch();//1.攒sql
if (i+1%500==0){
pstmt.executeBatch();//2.执行batch
pstmt.clearBatch();//3.清空batch
}
//统一提交数据
conn.commit();
}
} catch (sqlException throwables) {
throwables.printstacktrace();
}finally {
JDBCUtils.closs(conn,pstmt);
}
}
总结:PreparedStatement与Statement异同?
①二者关系:接口和子接口的关系
②开发中,用PreparedStatement替代Statement
③An object that represents a precompiled sql statement(预编译)
9.数据库事务
9.1代码实现
public void testUpdateWithTx() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
conn.setAutoCommit(false);
String sql1 = "update user_table set balance = balance-100 where user=?";
update(conn, sql1, "AA");
String sql2 = "update user_table set balance = balance+100 where user=?";
update(conn, sql2, "BB");
System.out.println("转账成功");
conn.commit();
} catch (Exception e) {
e.printstacktrace();
try {
conn.rollback();
} catch (sqlException e1) {
e1.printstacktrace();
}
} finally {
try {
conn.setAutoCommit(true);
} catch (sqlException e) {
e.printstacktrace();
}
JDBCUtils.closs(conn, null);
}
}
9.2事务实现通用的增删改操作
public int testTransactionUpdate() throws Exception {
Connection conn = JDBCUtils.getConnection();
PreparedStatement pstmt = null;
conn.setAutoCommit(false);
String sql = "update user_table set balance = ? where user = ?";
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
return pstmt.executeUpdate();
} catch (Exception e) {
e.printstacktrace();
} finally {
try {
conn.setAutoCommit(true);
} catch (sqlException e) {
e.printstacktrace();
}
JDBCUtils.closs(conn, pstmt);
}
return 0;
}
9.3事务实现通用的查询操作
public void testTransactionSelect() throws Exception {
Connection conn = JDBCUtils.getConnection();
System.out.println(conn.getTransactionIsolation());//查看隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
conn.setAutoCommit(false);
String sql = "select * from user_table where user=?";
User user = getInstance(conn, User.class, sql, "CC");
System.out.println(user);
try {
conn.setAutoCommit(true);
} catch (sqlException e) {
e.printstacktrace();
}
JDBCUtils.closs(conn, null);
}
public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getobject(i + 1);
String clumnLable = rsmd.getColumnLabel(i + 1);//获取每列的别名
//给t对象指定的columnLabel属性,赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(clumnLable);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt, rs);
}
return null;
}
10.DAO:data(base) access Object 数据(库)访问对象
【BaseDAO.java】
public abstract class BaseDAO {
public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getobject(i + 1);
String clumnLable = rsmd.getColumnLabel(i + 1);//获取每列的别名
//给t对象指定的columnLabel属性,赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(clumnLable);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt, rs);
}
return null;
}
//针对不同的表的通用查询操作,返回表中的一条记录
public int update(Connection conn, String sql, Object... args) {
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
return pstmt.executeUpdate();
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt);
}
return 0;
}
//针对不同的表的通用查询操作,返回表中的多条记录构成的集合
public <T> List<T> getInstanceForList(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i + 1, args[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
T t = clazz.getDeclaredConstructor().newInstance();
for (int i = 0; i < columnCount; i++) {
Object columnValue = rs.getobject(i + 1);
String clumnLable = rsmd.getColumnLabel(i + 1);//获取每列的别名
//给t对象指定的columnLabel属性,赋值为columnValue:通过反射
Field field = clazz.getDeclaredField(clumnLable);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(null, pstmt, rs);
}
return null;
}
//用于查询特殊值的通用方法
public <E> E getValue(Connection conn,String sql,Object... args) {
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
pstmt = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
pstmt.setobject(i+1,args[i]);
}
rs = pstmt.executeQuery();
if (rs.next()){
return (E)rs.getobject(1);
}
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(null,pstmt,rs);
}
return null;
}
}
DAO子类:
以Customer为例:
public class Customer {
private int id;
private String name;
private String email;
private Date birth;
// public Customer() {
// }
public Customer(int id, String name, String email, Date birth) {
this.id = id;
this.name = name;
this.email = email;
this.birth = birth;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", birth=" + birth +
'}';
}
}
public interface CustomerDAO {
//将cust对象添加到数据库
void insert(Connection conn,Customer cust);
//根据指定id从数据库删除cust对象
void deleteById(Connection conn,int id);
//针对内存中的cust对象,修改表中指定的记录
void updateById(Connection conn,Customer cust);
//查询表中指定id的记录
Customer getById(Connection conn, int id);
//查询表中的所有记录构成的集合
List<Customer> getAll(Connection conn);
//返回数据表中的记录条目数
long getCount(Connection conn);
//返回表中最大的生日
Date getMaxBirth(Connection conn);
}
public class CustomerDAOImpl extends BaseDAO implements CustomerDAO{
@Override
public void insert(Connection conn, Customer cust) {
String sql="insert into customers (name,email,birth) values (?,?,?)";
update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth());
}
@Override
public void deleteById(Connection conn, int id) {
String sql="delete from customers where id = ?";
update(conn,sql,id);
}
@Override
public void updateById(Connection conn, Customer cust) {
String sql="update customers set name=?,email=?,birth=? where id=?";
update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId());
}
@Override
public Customer getById(Connection conn, int id) {
String sql="select id,name,email,birth from customers where id=?";
Customer cu = getInstance(conn, Customer.class, sql, id);
return cu;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql="select id,name,email,birth from customers";
List<Customer> list = getInstanceForList(conn, Customer.class, sql);
return list;
}
@Override
public long getCount(Connection conn) {
String sql="select count(*) from customers";
return getValue(conn,sql);
}
@Override
public Date getMaxBirth(Connection conn) {
String sql="select max(birth) from customers";
return getValue(conn,sql);
}
}
十一.数据库连接池
传统的模式基本是按以下步骤:
-
在主程序(如servlet、beans)中建立数据库连接
-
进行sql操作
-
断开数据库连接
-
这种模式开发,存在的问题:
-
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用。若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
-
对于每一次数据库连接,使用完后都得断开。否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
-
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池的基本思想: 就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
工作原理:
数据库连接池技术的优点:
1. 资源重用
2. 更快的系统反应速度
3. 新的资源分配手段(可以重复使用提供好的连接)
4. 统一的连接管理,避免数据库连接泄漏
测试代码:
配置文件【druid.Properties】定义在src下
dbutils:
使用jar中的QueryRunner测试增删改的操作:
//测试插入
public void testInsert() {
QueryRunner runner;
Connection conn=null;
try {
runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql="insert into customers (id,name,email,birth) values (?,?,?,?)";
int insertCount = runner.update(conn, sql, 2, "zzz", "zzz@126.com", "1997-09-08");
System.out.println(insertCount);
} catch (Exception e) {
e.printstacktrace();
} finally {
JDBCUtils.closs(conn,null);
}
}
使用现成的jar中的QueryRunner测试查询的操作:
/*
BeanHandler:是ResultSetHandler接口的实现类,返回一条数据
*/
@Test
public void testQuery() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
runner.query(conn,sql,handler,3);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
/*
BeanListHandler:是ResultSetHandler接口的实现类,返回多条数据构成的集合
*/
public void testQuery2() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
List<Customer> list=runner.query(conn,sql,handler,3);
list.forEach(System.out::println);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
/*
MapHandler:是ResultSetHandler接口的实现类,对应表中的一条记录
将字段及相应字段的值作为map的key和value
*/
@Test
public void testQuery3() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
MapHandler handler = new MapHandler();
Map<String,Object> map=runner.query(conn,sql,handler,3);
System.out.println(map);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
/*
MapListHandler:是ResultSetHandler接口的实现类,对应表中的多条记录
将字段及相应字段的值作为map的key和value,存在list集合中
*/
@Test
public void testQuery4() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
MapListHandler handler = new MapListHandler();
List<Map<String, Object>> list = runner.query(conn, sql, handler, 3);
list.forEach(System.out::println);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
/*
ScalarHandler:用于查询特殊值
*/
@Test
public void testQuery5() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
// String sql="select count(*) from customers";
String sql="select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
// long count =(long) runner.query(conn, sql, handler);
// System.out.println(count);
Date date =(Date) runner.query(conn, sql, handler);
System.out.println(date);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
//自定义ReusltSetHander实现类
@Test
public void testQuery6() {
QueryRunner runner=new QueryRunner();
Connection conn=null;
try {
conn=JDBCUtils.getConnection3();
String sql="select id,name,email,birth from customers where id=?";
ResultSetHandler<Customer> handler=new ResultSetHandler<Customer>() {
@Override
public Customer handle(ResultSet rs) throws sqlException {
return null;
}
};
Customer customer = runner.query(conn, sql, handler, 3);
System.out.println(customer);
} catch (Exception e) {
e.printstacktrace();
}finally {
JDBCUtils.closs(conn,null);
}
}
使用dbutuils.jar包中的dbutils工具类资源的关闭:
/*
使用dbutils.jar提供的dbutils工具类,实现资源关闭
*/
public static void closeResourse2(Connection conn, PreparedStatement pstmt, ResultSet rs){
dbutils.closeQuietly(conn,pstmt,rs);
}