JDBC
01.JDBC概述
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.数据库的驱动:
数据库厂商针对于JDBC这套接口,提供的具体实现类的集合。
类似:
5.面向接口编程的思想
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同实现。不同的实现的集合,即为不同数据库的驱动。 ————面向接口编程
02.数据库的连接
前四种了解即可
//方式一:
@Test
public void testConnection() throws SQLException{
//1.获取Driver实现类对象
Driver driver = new com.mysql.jdbc.Driver();
//url:http://localhost:8080/gmall/keyboard.jpg
//jdbc:mysql:协议
//localhost:ip地址
//3306:默认mysql的端口号
//test:test数据库
String url ="jdbc:mysql://localhost:3306/test?characterEncoding=utf8";
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "123456");
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
//方式二:对方式一的迭代:在如下的程序中不出现第三方的api,使得程序可移植性更高
@Test
public void test2() throws Exception{
//1.获取Driver实现类对象:使用反射
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver = (Driver) clazz.newInstance();
//2.提供要连接的数据库
String url ="jdbc:mysql://localhost:3306/test?characterEncoding=utf8";
//3.提供连接需要的用户名和密码
Properties info = new Properties();
info.setProperty("user", "root");
info.setProperty("password", "abc123");
//4.获取连接
Connection conn = driver.connect(url, info);
System.out.println(conn);
}
//方式三:使用DriverManager替换Driver
@Test
public void test3() throws Exception{
//1.获取Driver实现类的对象
Class clazz = Class.forName("com.mysql.jdbc.Driver");
Driver driver =(Driver)clazz.newInstance();
//2.提供另外三个连接的基本信息:
String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8";
String user = "root";
String password ="123456";
//注册驱动
DriverManager.registerDriver(driver);
//获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
//方式4:可以只是加载驱动,不用显示的注册驱动了
@Test
public void test4() throws Exception{
//1、供另外三个连接的基本信息:
String url = "jdbc:mysql://localhost:3306/test?characterEncoding=utf-8";
String user = "root";
String password ="123456";
//2.加载驱动Driver
Class clazz = Class.forName("com.mysql.jdbc.Driver");
// Driver driver =(Driver)clazz.newInstance();
// //注册驱动
// DriverManager.registerDriver(driver);
//为什么可以省略上述操作呢?
/*
static {
try {
java.sql.DriverManager.registerDriver(new Driver());
} catch (SQLException E) {
throw new RuntimeException("Can't register driver!");
}
}
*/
//3.获取连接
Connection onn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
最终版掌握:
//方式五(final版):将数据库连接需要的4个基本信息声明在配置文件中,通过配置文件的方式,获取;连接
/*
* 此方法的好处:
* 1.实现了数据与代码的分离。实现了解耦
* 2.如果需要修改配置文件信息,可以避免程序重新打包
*/
@Test
public void getConnection5() throws Exception{
//1.读取配置文件中四个基本信息
InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url = pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
System.out.println(conn);
}
配置文件【jdbc.properties】:声明在工程的src下
user=root
password=123456
url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
driverClass=com.mysql.cj.jdbc.Driver
JDBCUtils.java
**
* 操作数据库的工具类
*/
public class JDBCUtils {
/*
* 获取数据库的连接
*/
public static Connection getConnection() throws Exception{
//1.读取配置文件中的四个基本信息
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
Properties pros = new Properties();
pros.load(is);
String user = pros.getProperty("user");
String password = pros.getProperty("password");
String url =pros.getProperty("url");
String driverClass = pros.getProperty("driverClass");
//2.加载驱动
Class.forName(driverClass);
//3.获取连接
Connection conn = DriverManager.getConnection(url,user,password);
return conn;
}
/*
* 关闭连接和Statement的操作
*/
public static void closeResource(Connection conn,Statement ps){
try {
if(ps !=null)
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn!=null)
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/*
* 关闭资源操作
*/
public static void closeResource(Connection conn,Statement ps,ResultSet rs){
try {
if(ps !=null)
ps.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(conn!=null)
conn.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(rs!=null)
rs.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
03.Statement接口实现CRUD操作(了解)
public class StatementTest {
// 使用Statement的弊端:需要拼写sql语句,并且存在SQL注入的问题
@Test
public void testLogin() {
Scanner scan = new Scanner(System.in);
System.out.print("用户名:");
String userName = scan.nextLine();
System.out.print("密 码:");
String password = scan.nextLine();
// SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '
// ='1' or '1' = '1';
String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
+ "'";
User user = get(sql, User.class);
if (user != null) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
}
// 使用Statement实现对数据表的查询操作
public <T> T get(String sql, Class<T> clazz) {
T t = null;
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
// 1.加载配置文件
InputStream is = StatementTest.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.获取连接
conn = DriverManager.getConnection(url, user, password);
st = conn.createStatement();
rs = st.executeQuery(sql);
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
// 获取结果集的列数
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
t = clazz.newInstance();
for (int i = 0; i < columnCount; i++) {
// //1. 获取列的名称
// String columnName = rsmd.getColumnName(i+1);
// 1. 获取列的别名
String columnName = rsmd.getColumnLabel(i + 1);
// 2. 根据列名获取对应数据表中的数据
Object columnVal = rs.getObject(columnName);
// 3. 将数据表中得到的数据,封装进对象
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnVal);
}
return t;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 关闭资源
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
return null;
}
}
Statement使用的弊端:
-
问题一:存在拼串操作,繁琐
-
问题二:存在SQL注入问题
-
SQL 注入是利用某些系统没有对用户输入的数据进行充分的检查,而在用户输入数据中注入非法的 SQL 语句段或命令(如:SELECT user, password FROM user_table WHERE user=‘a’ OR 1 = ’ AND password = ’ OR ‘1’ = ‘1’) ,从而利用系统的 SQL 引擎完成恶意行为的做法。
-
对于 Java 而言,要防范 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 取代 Statement 就可以了。
其它问题:
- Statement没办法操作Blob类型变量
- Statement实现批量插入时,效率低
04.使用PreparedStatement实现CRUD操作
1.PreparedStatement的理解:
①Statement的子接口
②预编译 (好处均源于此)
③可以解决Statement的sql注入问题,拼串问题
2.使用PreparedStatement实现增、删、改的方法:version 1.0
//通用的增删改查
public void update(String sql,Object ...args){//sql中占位符的个数与可变形参的长度一致
Connection conn =null;
PreparedStatement ps = null;
try {
//1.获取数据库的连接
conn = JDBCUtils.getConnection();
//2.预编译sql语句,返回PreparedStatement的实例
ps = conn.prepareStatement(sql);
//3.填充占位符
for(int i=0;i<args.length;i++){
ps.setObject(i+1, args[i]);//小心参数声明错误!!
}
//4.执行
ps.execute();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
//5.资源的关闭
JDBCUtils.closeResource(conn, ps);
}
}
3.使用PreparedStatement实现通用的查询操作:Version 1.0
/*
* 针对于不同表的通用查询操作,返回表中的一条记录
*
*/
public <T> T getInstance(Class<T> clazz,String sql,Object ...args){
Connection conn = null;
PreparedStatement ps =null;
ResultSet rs =null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据:ResultSetMetaData();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if(rs.next()){
T t = clazz.newInstance();
//处理结果集一行数据中的每一个列
for(int i=0;i<columnCount;i++){
//获取列值
Object columnValue = rs.getObject(i+1);
//获取每个列的列名
String columnName = rsmd.getColumnLabel(i+1);
//给对象指定的columnName属性,赋值为columnValue,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
返回多个对象构成的集合:
public <T> List<T> getForList(Class<T> clazz,String sql,Object ...args){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
//填充占位符
for(int i=0;i<args.length;i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
//获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//创建集合对象
ArrayList<T> list = new ArrayList<>();
while(rs.next()){
T t = clazz.newInstance();
//处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for(int i=0;i<columnCount;i++){
//获取列值
Object columnValue = rs.getObject(i+1);
//获取每个列的列名
String columnName = rsmd.getColumnLabel(i+1);
//给t对象指定columanName属性,赋值为columanValue
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps, rs);
}
return null;
}
总结:
两种思想:
面向接口编程的思想
ORM编程思想:(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java的一个对象
- 表中的一个字段对应表中的一个属性
两种技术:
- 使用结果集的元数据:ResultSetMetaData
- getColumnCount():获取列名
- getColumnLabel():获取列的别名
- 反射的使用
- 创建运行时类的对象
- 在运行时,动态的调用指定的运行时类指定的属性、方法
查询流程图:
05.PreparedStatement操作Blob类型的变量
MySQL BLOB类型
-
MySQL中,BLOB是一个二进制大型对象,是一个可以存储大量数据的容器,它能容纳不同大小的数据。
-
插入BLOB类型的数据必须使用PreparedStatement,因为BLOB类型的数据无法使用字符串拼接写的。
-
MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
- 实际使用中根据需要存入的数据大小定义不同的BLOB类型。
- 需要注意的是:如果存储的文件过大,数据库的性能会下降。
- 如果在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的配置参数: max_allowed_packet=16M。同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
**写入的方法:**setBlob(InputStream is);
读取操作的方法:
Blob blob = getBlob(int index);
InputStream is = blob.getBinaryStream();
写入代码:
//向customers中插入Blob类型的字段
@Test
public void testInsert(){
Connection conn = null;
PreparedStatement ps = null;
try {
conn = JDBCUtils.getConnection();
String sql ="insert into customers(name,email,birth,photo) values(?,?,?,?)";
ps = conn.prepareStatement(sql);
ps.setObject(1, "小寒");
ps.setObject(2, "xiao@qq.com");
ps.setObject(3, "1999-12-29");
FileInputStream is = new FileInputStream(new File("P(clothes).jpg"));
ps.setBlob(4, is);
ps.execute();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps);
}
}
查询代码:
//查询数据表customer中Blob类型的字段
@Test
public void testQuery() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
InputStream is=null;
FileOutputStream fos =null;
try {
conn = JDBCUtils.getConnection();
String sql ="select id,name,email,birth,photo from customers where id =?";
ps = conn.prepareStatement(sql);
ps.setInt(1, 21);
rs = ps.executeQuery();
if(rs.next()){
//方式一:
// int id = rs.getInt(1);
// String name = rs.getString(2);
// String email = rs.getString(3);
// Date birth = rs.getDate(4);
//方式二:
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer cust = new Customer(id,name,email,birth);
System.out.println(cust);
//将Blob类型的字段下载下来,以文件的方式保存在本地
Blob photo = rs.getBlob("photo");
is = photo.getBinaryStream();
fos = new FileOutputStream("xiaohan.jpg");
byte[] buffer = new byte[1024];
int len;
while((len = is.read(buffer))!=-1){
fos.write(buffer,0,len);
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps,rs);
try {
if(is!=null)
is.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
if(fos!=null)fos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
06.PreparedStatement实现高效的批量插入
1.测试使用PreparedStatement
层次一:使用Statement实现
代码略
层次二:使用PreparedStatement替换Statement
//批量插入的方式二:使用PreparedStatement
@Test
public void testInsert1(){
Connection conn =null;
PreparedStatement ps =null;
try {
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
String sql ="insert into goods(name) values(?)";
ps = conn.prepareStatement(sql);
for(int i=1;i<20000;i++){
ps.setObject(1, "name_"+i);
ps.execute();
}
long end = System.currentTimeMillis();
System.out.println("花费时间为:"+(end -start));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps);
}
}
层次三:
- 1.addBatch()、executeBatch()、clearBatch()
- 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql 开启批处理的支持 ?rewriteBatchedStatements=true写在配置文件的url后面
- 3.使用更新的mysql 驱动:mysql-connector-java-8.0-bin.jar
/*
* 批量插入方式三:
* 1.addBatch()、executeBatch()、clearBatch()
* 2.mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持
* ?rewriteBatchedStatements=true写在配置文件的url后面
* 3.使用更新的mysql 驱动:mysql-connector-java-8.0-bin.jar
*
*/
@Test
public void testInsert2(){
Connection conn =null;
PreparedStatement ps =null;
try {
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
String sql ="insert into goods(name) values(?)";
ps = conn.prepareStatement(sql);
for(int i=1;i<=1000000;i++){
ps.setObject(1, "name_"+i);
//1."攒"sql
ps.addBatch();
if(i%500 == 0){
//2.执行batch
ps.executeBatch();
//3.清空batch
ps.clearBatch();
}
}
long end = System.currentTimeMillis();
System.out.println("花费时间为:"+(end -start));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps);
}
}
层次四:设置不允许自动提交数据(最终版)
//批量插入的方式四:设置不允许自动提交数据
@Test
public void testInsert3(){
Connection conn =null;
PreparedStatement ps =null;
try {
long start = System.currentTimeMillis();
conn = JDBCUtils.getConnection();
//设置不允许自动提交数据
conn.setAutoCommit(false);
String sql ="insert into goods(name) values(?)";
ps = conn.prepareStatement(sql);
for(int i=1;i<=1000000;i++){
ps.setObject(1, "name_"+i);
//1."攒"sql
ps.addBatch();
if(i%500 == 0){
//2.执行batch
ps.executeBatch();
//3.清空batch
ps.clearBatch();
}
}
//提交数据
conn.commit();
long end = System.currentTimeMillis();
System.out.println("花费时间为:"+(end -start));
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, ps);
}
}
总结:PreparedStatement与Statement的异同?(面试题)
①PreparedStatement是Statement的子接口
②开发中,PreparedStatement替换Statement
③PreparedStatement是预编译sql语句的
④PreparedStatement可以防止sql注入
-
代码的可读性和可维护性。
-
PreparedStatement 能最大可能提高性能:
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
- (语法检查,语义检查,翻译成二进制命令,缓存)
-
PreparedStatement 可以防止 SQL 注入
07.数据库事务
1.事务:
一组逻辑操作单元,使数据从状态变换到另一种状态。
- 一组逻辑操作单元:一个或多个DML操作。
2.事务处理的原则:(笔试会考)
事务处理的原则:保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永远地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态
说明:
1.数据一旦提交,就不可回滚
4.哪些操作会导致数据库的自动提交?
- DDL操作一旦执行,都会自动提交
- set autocommit = false 对DDL操作失效
- DML默认情况下,一旦执行,就会自动提交。
- 我们可以通过set autocommit = false的方式取消DML操作的自动提交
- 默认关闭连接时,会自动的提交数据
3.代码的体现
@Test
public void testUpdateWithTx() {
Connection conn = null;
try {
conn = JDBCUtils.getConnection();
System.out.println(conn.getAutoCommit());
// 1.取消数据的自动提交
conn.setAutoCommit(false);
System.out.println(conn.getAutoCommit());
String sql = "update user_table set balance = balance -100 where user = ?";
update(conn, sql, "AA");
// 模拟网络异常
System.out.println(10 / 0);
String sql1 = "update user_table set balance = balance +100 where user = ?";
update(conn, sql1, "BB");
System.out.println("转账成功");
conn.commit();
} catch (Exception e) {
e.printStackTrace();
// 3.回滚数据
try {
conn.rollback();
System.out.println(conn.getAutoCommit());
} catch (SQLException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
} finally {
// 改回去
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JDBCUtils.closeResource(conn, null);
}
}
考虑事务以后,实现通用的增删改操作:version2.0
public int update(Connection conn, String sql, Object... args) {
// 1.预编译sql语句,返回PreparedStatement的实例
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行
return ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 4.资源关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑事务)
public <T> T getInstance(Connection conn, Class<T> clazz, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据:ResultSetMetaData();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列的列名
String columnName = rsmd.getColumnLabel(i + 1);
// 给对象指定的columnName属性,赋值为columnValue,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
4.事务的ACID属性
-
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 -
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 -
隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 -
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
5.数据操作过程中可能出现的问题:(针对隔离性)
-
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
-
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
-
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
6.数据库的四种隔离级别:(一致性和并发性:一致性越好,并发性越差)
-
数据库提供的4种事务隔离级别:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OZ7S9PVb-1658975922837)(F:/尚硅谷/jdbc/1-课件/课件-md/尚硅谷_宋红康_JDBC.assets/1555586275271.png)]
-
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
-
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。
如何查看和设置隔离级别:
-
查看当前的隔离级别:
SELECT @@tx_isolation;
-
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
-
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
08.DAO及其子类
封装了针对于数据表的通用的操作
/*
* DAO:data(base) access object
*
* 封装了针对于数据表的通用的操作
*/
public abstract class BaseDAO<T> {
private Class<T> clazz = null;
{
//获取当前BaseDAO的子类继承的父类中的泛型
Type genericSuperclass = this.getClass().getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
Type[] typeArguments = paramType.getActualTypeArguments();//获取了父类的泛型参数
clazz = (Class<T>) typeArguments[0];//泛型的第一个参数
}
// 通用的增删改操作---version 2.0(考虑事务)
public int update(Connection conn, String sql, Object... args) {
// 1.预编译sql语句,返回PreparedStatement的实例
PreparedStatement ps = null;
try {
ps = conn.prepareStatement(sql);
// 2.填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
// 3.执行
return ps.executeUpdate();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 4.资源关闭
JDBCUtils.closeResource(null, ps);
}
return 0;
}
// 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑事务)
public List<T> getForList(Connection conn, String sql, Object ...args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
// 填充占位符
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
// 创建集合对象
ArrayList<T> list = new ArrayList<>();
while (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列:给t对象指定的属性赋值
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列的列名
String columnName = rsmd.getColumnLabel(i + 1);
// 给t对象指定columanName属性,赋值为columanValue
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
list.add(t);
}
return list;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
// 通用的查询操作,用于返回数据表中的一条记录(version 2.0:考虑事务)
public T getInstance(Connection conn, String sql, Object... args) {
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JDBCUtils.getConnection();
ps = conn.prepareStatement(sql);
for (int i = 0; i < args.length; i++) {
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();
// 获取结果集的元数据:ResultSetMetaData();
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
if (rs.next()) {
T t = clazz.newInstance();
// 处理结果集一行数据中的每一个列
for (int i = 0; i < columnCount; i++) {
// 获取列值
Object columnValue = rs.getObject(i + 1);
// 获取每个列的列名
String columnName = rsmd.getColumnLabel(i + 1);
// 给对象指定的columnName属性,赋值为columnValue,通过反射
Field field = clazz.getDeclaredField(columnName);
field.setAccessible(true);
field.set(t, columnValue);
}
return t;
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
//查询特殊值的通用方法
public <E> E getValue(Connection conn,String sql,Object ...args) {
PreparedStatement ps =null;
ResultSet rs = null;
try {
ps = conn.prepareStatement(sql);
for(int i=0;i<args.length;i++){
ps.setObject(i+1, args[i]);
}
rs = ps.executeQuery();
if(rs.next()){
return (E)rs.getObject(1);
}
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(null, ps, rs);
}
return null;
}
}
CustomerDao接口:
/*
* 此接口用于规范针对于customers表的常用操作
*/
public interface CustomerDao {
/*
* 将cust对象添加到数据库中
*/
/*
* 针对于内存中的cust对象,去修改数据库中指定的记录
*/
void insert(Connection conn,Customer cust);
void deleteById(Connection conn,int id);
void update(Connection conn,Customer cust);
/*
* 针对于指定的id查询得到对应的Customer对象
*/
Customer getCustomerById(Connection conn,int id);
/*
* 查询表中的所有记录构成的集合
*/
List<Customer>getAll (Connection conn);
/*
* 返回数据表中数据的条目数
*/
Long getCount(Connection conn);
/*
* 返回数据表中最大的生日
*/
Date getMaxBirth(Connection conn);
}
CustomerDaoImpl:
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDao{
@Override
public void insert(Connection conn, Customer cust) {
// TODO Auto-generated method stub
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 update(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 getCustomerById(Connection conn, int id) {
String sql ="select id,name,email,birth from customers where id =?";
Customer customer = getInstance(conn, sql, id);
return customer;
}
@Override
public List<Customer> getAll(Connection conn) {
String sql ="select id,name,email,birth from customers";
List<Customer> list = getForList(conn, 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);
}
}
总结:考虑到事务以后的数据库操作(重点)
1.获取数据库的连接
Connection conn = JDBCUtils.getConnection();//方式1:手动获取连接;方式二:数据库连接池
conn.setAutoCommit(false); //体现事务
2.如下的多个DML操作,作为一个事务出现:
操作1:需要使用通用的增删改查操作 //通用的增删改查如何实现?
操作2:需要使用通用的增删改查操作 //方式1:手动使用PreparedStatement
操作3:需要使用通用的增删改查操作 //方式2:使用dbutils.jar中QueryRunnrt类
conn.commit();
3.如果出现异常,则:
conn.rollback();
4.关闭资源
JDBCUtils.closeResource(…); //方式1:手动关闭; 方式2:DbUtils类的关闭方法
09.数据库连接池
1.传统连接的问题:
这种模式开发,存在的问题:
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。**数据库的连接资源并没有得到很好的重复利用。**若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- **对于每一次数据库连接,使用完后都得断开。**否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
2.如何解决传统开发中的数据库连接问题
使用数据库连接池
3.数据库连接池的好处:
- 提高程序的响应速度(减少了创建连接相应的时间)
- 降低资源的消耗(可以重复使用已经提供好的连接)
- 便于连接的管理
4.实现的方式:
- DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
- C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用
- Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
5.C3P0
导入jar包:c3p0-0.9.1.2.jar
测试连接的代码:·
public class C3P0Test {
//方式一:
@Test
public void testGetConnection() throws Exception{
//获取c3p0数据库连接池
ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass( "com.mysql.cj.jdbc.Driver" ); //loads the jdbc driver
cpds.setJdbcUrl( "jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC" );
cpds.setUser("root");
cpds.setPassword("root");
//通过设置相关的参数对数据库连接池进行管理
//设置初始时数据库连接池中的连接数
cpds.setInitialPoolSize(10);
Connection conn = cpds.getConnection();
System.out.println(conn);
//销毁c3p0数据库连接池(一般不会这么操作)
// DataSources.destroy(cpds);
}
//方式二:使用配置文件
@Test
public void testGetConnection1() throws SQLException{
ComboPooledDataSource cpds = new ComboPooledDataSource("helloc3p0");
Connection conn = cpds.getConnection();
System.out.println(conn);
}
}
配置文件:在src下叫c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
<!-- This app is massive! -->
<named-config name="helloc3p0">
<!-- 提供获取连接的4个基本信息 -->
<property name="driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:13306/test?serverTimezone=UTC</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 进行数据库连接池管理的基本信息 -->
<!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务申请的连接数 -->
<property name="acquireIncrement">5</property>
<!-- c3p0数据库连接池中初始化时的连接数 -->
<property name="initialPoolSize">10</property>
<!-- c3p0数据库连接池中维护的最少的连接数 -->
<property name="minPoolSize">10</property>
<!-- c3p0数据库连接池中维护的最多的连接数 -->
<property name="maxPoolSize">100</property>
<!-- c3p0数据库连接池最多维护的Statement的个数 -->
<property name="maxStatements">50</property>
<!-- 每个连接最多使用的Statement的个数 -->
<property name="maxStatementsPerConnection">5</property>
</named-config>
</c3p0-config>
6.DBCP
到jar包:commons-dbcp-1.4.jar
public class DBCPTest {
/*
* 测试DBCP的数据库连接池技术
*/
//方式一:不推荐
@Test
public void testGetConnection() throws SQLException{
//创建了DBCP的数据库连接池
BasicDataSource source = new BasicDataSource();
//设置基本信息
source.setDriverClassName("com.mysql.cj.jdbc.Driver");
source.setUrl("jdbc:mysql://localhost:13306/test?serverTimezone=UTC");
source.setUsername("root");
source.setPassword("root");
//还可以设置其它数据库连接池管理的相关属性
source.setInitialSize(10);
source.setMaxActive(10);
//...
Connection conn = source.getConnection();
System.out.println(conn);
}
//方式二:推荐使用配置文件
@Test
public void testGetConnection1() throws Exception{
Properties pros = new Properties();
//方式1:
// InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("dbcp.properties");
//方式2:
FileInputStream is = new FileInputStream(new File("src/dbcp.properties"));
pros.load(is);
DataSource source = BasicDataSourceFactory.createDataSource(pros);
Connection conn = source.getConnection();
System.out.println(conn);
}
}
配置文件在src下:dbcp.properties
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root
initialSize=10
7.Druid
导jar包:commons-dbutils-1.3.jar
public class DruidTest {
@Test
public void getConnection() throws Exception{
Properties pros = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties");
pros.load(is);
DataSource source = DruidDataSourceFactory.createDataSource(pros);
Connection conn = source.getConnection();
System.out.println(conn);
}
}
配置文件:druid.properties
url=jdbc:mysql://localhost:13306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC
username=root
password=root
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=10
8.DBUtils提供的jar包实现CRUD操作
导入jar包:commons-dbutils-1.3.jar
使用现成的jar中的QueryRunner测试增、删、改的操作:
//测试插入
@Test
public void testInsert(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql ="insert into customers(name,email,birth)values(?,?,?)";
int i = runner.update(conn,sql, "蔡徐坤","caixukun@126.com","1997-09-08");
System.out.println("添加了"+i+"条记录");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
测试查询:
//测试
/*
* BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。
*/
@Test
public void testQuery1() {
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql ="select id,name,email,birth from customers where id =?";
BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
Customer customer = runner.query(conn,sql,handler,22);
System.out.println(customer);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
/*
* BeanLsitHandler:是ResultSetHandler接口的实现类,用于封装表中多条记录构成的集合
*/
@Test
public void testQuery2(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
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,22);
list.forEach(System.out::println);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
/*
* MapHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录
* 将字段及相应字段的值作为map中的key和value
*/
@Test
public void testQuery3(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
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,22);
System.out.println(map);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
/*
* MapListHander:是ResultSetHandler接口的实现类,用于封装表中的多条记录
* 将字段及相应字段的值作为map中的key和value,将这些map添加到list中
*/
@Test
public void testQuery4(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
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,22);
list.forEach(System.out::println);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
/*
* ScalarHandler:用于查询特殊值
*/
@Test
public void testQuery5(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql ="select count(*) from customers";
ScalarHandler handler = new ScalarHandler();
Long count = (Long)runner.query(conn,sql,handler);
System.out.println(count);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
@Test
public void testQuery6(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
conn = JDBCUtils.getConnection3();
String sql ="select max(birth) from customers";
ScalarHandler handler = new ScalarHandler();
Date maxBirth = (Date)runner.query(conn,sql,handler);
System.out.println(maxBirth);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
/*
* 自定义ResultSetHandler的实现类
*/
@Test
public void testQuery7(){
Connection conn = null;
try {
QueryRunner runner = new QueryRunner();
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 {
// System.out.println("handler");
// return null;
// return new Customer(12,"成龙","Jack@126.com",new Date(45454L));
if(rs.next()){
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
Date birth = rs.getDate("birth");
Customer customer = new Customer(id,name,email,birth);
return customer;
}
return null;
}
};
Customer customer = runner.query(conn,sql,handler,23);
System.out.println(customer);
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
JDBCUtils.closeResource(conn, null);
}
}
使用dbutils.jar包中的DbUtils工具类实现连接等资源的关闭
略